Compare commits

..

100 Commits

Author SHA1 Message Date
Harald Kuhr 488d6da71a [maven-release-plugin] prepare for next development iteration 2022-08-19 17:43:15 +02:00
Harald Kuhr b19e45a911 [maven-release-plugin] prepare release twelvemonkeys-3.8.3 2022-08-19 17:43:11 +02:00
Harald Kuhr a5b6cf898d DiscreteAlphaIndexColorModel num components fix
(cherry picked from commit 190fe87ee9)
2022-08-19 17:39:27 +02:00
Harald Kuhr ce597c437d #694: Fixed import order
(cherry picked from commit d1872ce94f)
2022-08-19 17:39:27 +02:00
Harald Kuhr fa4b3787d0 #694 BMP: Fixed subsampling for 24 bit/pixel case
(cherry picked from commit a5c52a99b4)
2022-08-19 17:39:25 +02:00
Harald Kuhr 3c18e8a510 #684 Add some tolerance for JDK 8...
(cherry picked from commit 4170b393fa)
2022-08-19 17:39:23 +02:00
Harald Kuhr 206481038e #684 Remove TODO as it's now fixed
(cherry picked from commit 53f9ba91e0)
2022-08-19 17:39:23 +02:00
Harald Kuhr cff4d88991 #684 Fix some render size issues in SVGImageReader
Bonus: Minor code clean-up.

(cherry picked from commit be2d7d5f10)
2022-08-19 17:39:22 +02:00
Harald Kuhr 8ea8e061a9 Minor code clean-up for WMFImageReader
(cherry picked from commit 00aec2c90e)
2022-08-19 17:39:22 +02:00
Harald Kuhr 101ad18f71 Minor optimizations.
(cherry picked from commit 2b04f7205c)
2022-08-19 17:39:22 +02:00
Harald Kuhr 08b441a17e #675 PSD 16/32 bit layer support pt2: Cross-platform test
(cherry picked from commit 7d401d0194)
2022-08-19 17:39:20 +02:00
Harald Kuhr b6c76d8566 #675 PSD 16/32 bit layer support
(cherry picked from commit 48691139a3)
2022-08-19 17:39:18 +02:00
Harald Kuhr 3f74b2ddf3 #681: Fix for little-endian "packed" USHORT types + rewritten stream handling
(cherry picked from commit bcb87c09d2)
2022-08-19 17:39:17 +02:00
Harald Kuhr 46b48f32c3 #683: Fix TIFF stripByteCounts computation for uncompressed data
(cherry picked from commit 84a8ceeb93)
2022-08-19 17:39:17 +02:00
Harald Kuhr a07d0285fe A new ImageInputStream adapter for InputStream.
(cherry picked from commit 0cb99feedf)
2022-08-19 17:39:15 +02:00
Harald Kuhr 98de4ad4ec #682 TIFF Lab w/alpha support
(cherry picked from commit 91493c5145)
2022-08-19 17:39:13 +02:00
Harald Kuhr aa82612765 Fix bad format in validator message.
(cherry picked from commit 6ddb799a95)
2022-08-19 17:39:12 +02:00
Harald Kuhr 9213da3184 TGAImageReader no longer reads single byte 0-terminator as Image Identification
(cherry picked from commit 8a187f6657)
2022-08-19 17:39:10 +02:00
Harald Kuhr a5e2226a5a #680 TGAImageReader now reads attribute bits with no extension area as alpha
(cherry picked from commit b7d865f2cf)
2022-08-19 17:39:09 +02:00
Harald Kuhr 773bedccca Fix bug in 0-terminated ASCII string parsing + test.
(cherry picked from commit d50fb1a51e)
2022-08-19 17:39:08 +02:00
Harald Kuhr 6bcc17a020 Documentation & details.
(cherry picked from commit 8992406f50)
2022-08-19 17:39:06 +02:00
Harald Kuhr 37d1da9b9d #678, #679: TIFF read support for YCbCr Planar with or without subsampling
(cherry picked from commit 44eebff62f)
2022-08-19 17:39:04 +02:00
Harald Kuhr 8cf1405dfc Simplified TIFF writing.
(cherry picked from commit 8c85c4ca96)
2022-08-19 17:39:02 +02:00
Harald Kuhr 8c37d19928 Added test cases for EncoderStream/DecoderStream and fixed a bug
+ code clean-up to make IntelliJ happy :-)

(cherry picked from commit fa5c77bff0)
2022-08-19 17:39:00 +02:00
Harald Kuhr 87cd506fdd PCX: Minor clean up
(cherry picked from commit d87b80deea)
2022-08-19 17:39:00 +02:00
Harald Kuhr e0c7edebbd Write LONG8 offsets for BigTIFF
(cherry picked from commit ae138c3b4e)
2022-08-19 17:39:00 +02:00
Harald Kuhr 5d13bd653f #677 Fixed integer overflow + added tests
(cherry picked from commit ab13fdd09d)
2022-08-19 17:38:59 +02:00
Harald Kuhr 2d974874a9 Fixed test typo.
(cherry picked from commit aab5b062bd)
2022-08-19 17:38:59 +02:00
Harald Kuhr f625622b10 [skip ci] Fixed some typos in comments. :-)
(cherry picked from commit 00d6acd1bf)
2022-08-19 17:38:59 +02:00
Harald Kuhr dbdd7ae3f1 Update feature_request.md
(cherry picked from commit 0f8a7ea482)
2022-08-19 17:38:59 +02:00
Harald Kuhr 73883ebf99 #672: WebPImageReader now supports unknown stream lengths
(cherry picked from commit 9fe87fe10d)
2022-08-19 17:38:59 +02:00
Harald Kuhr 970b238066 #666 Clean-up: No alpha for RGB 3/components
(cherry picked from commit 9e2f369459)
2022-08-19 17:36:49 +02:00
Harald Kuhr 6cb8ac4b68 #666 Support for TIFF RGB 2/4 bit per sample.
(cherry picked from commit d34b0b7fcf)
2022-08-19 17:36:49 +02:00
snyk-bot 1a2a4edfe8 fix: upgrade jmagick:jmagick from 6.2.4 to 6.6.9
Snyk has created this PR to upgrade jmagick:jmagick from 6.2.4 to 6.6.9.

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

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/eca06326-94ac-456d-a029-f411089e7f16?utm_source=github&utm_medium=referral&page=upgrade-pr

(cherry picked from commit 5effcb1344)
2022-08-19 17:36:49 +02:00
Oliver Schmidtmer a12a1f73b5 TIFFImageMetadata: ImageOrientation in mergeTree (#667)
TIFFImageMetadata: ImageOrientation in mergeTree
(cherry picked from commit b67d687761)
2022-08-19 17:36:49 +02:00
Harald Kuhr 46bfdd93d8 [maven-release-plugin] prepare for next development iteration 2022-02-22 14:49:45 +01:00
Harald Kuhr 447ef6b8eb [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-22 14:49:41 +01:00
Harald Kuhr c19b6ede0a Back to 3.8.2-SNAPSHOT [skip ci] 2022-02-22 14:48:06 +01:00
Harald Kuhr 401c6355a1 Attempt at adding JavaDocs + use "release" profile for release-plugin [skip ci]
(cherry picked from commit d0881c8b5c)
2022-02-22 14:47:28 +01:00
Harald Kuhr be0cf16124 [maven-release-plugin] prepare for next development iteration 2022-02-22 13:31:01 +01:00
Harald Kuhr 47b0cd6e9a [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-22 13:30:56 +01:00
Harald Kuhr b52ab149b3 Back to 3.8.2-SNAPSHOT [skip ci] 2022-02-22 13:28:52 +01:00
Harald Kuhr 900c26a5ac [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-22 13:22:39 +01:00
Harald Kuhr 7233c593ac Adding description to top-level POM [skip ci]
(cherry picked from commit 976928f48c)
2022-02-22 13:20:15 +01:00
Harald Kuhr 8159ba1245 [maven-release-plugin] prepare for next development iteration 2022-02-21 16:18:51 +01:00
Harald Kuhr 92581a077b [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-21 16:18:44 +01:00
Harald Kuhr f87b4d6748 Thank you, Travis [skip ci]
(cherry picked from commit e1c2f2ee73)
2022-02-21 16:15:35 +01:00
Harald Kuhr 0495f6e266 #661: JUnit test results take 4
(cherry picked from commit 92632fa2a3)
2022-02-21 16:15:33 +01:00
Harald Kuhr bc7c8ba20c #661: JUnit test results take 3
(cherry picked from commit 5a563e315f)
2022-02-21 16:15:31 +01:00
Harald Kuhr 0c270f8343 #661: JUnit test results take 2
(cherry picked from commit c06d47d123)
2022-02-21 16:15:28 +01:00
Harald Kuhr e1db332dca #661: JUnit test results
(cherry picked from commit fea6beb364)
2022-02-21 16:15:26 +01:00
Harald Kuhr 016977e382 PNM: New attempt at making the new header parser work on Windows.
(cherry picked from commit 4b951c06cc)
2022-02-21 16:15:23 +01:00
Harald Kuhr 134eecc59f PNM Windows issue. Temporary roll-back to working version.
(cherry picked from commit a3e6e52c95)
2022-02-21 16:15:23 +01:00
Harald Kuhr 16c78052ee PNM clean-up: Avoid leading/trailing whitespace in comments.
(cherry picked from commit 5347015cbd)
2022-02-21 16:15:20 +01:00
Harald Kuhr b51e8ccf6e PNM clean-up.
(cherry picked from commit 4d190892df)
2022-02-21 16:15:18 +01:00
Harald Kuhr 76a9ff1122 #660: Attempt at making the comment parsing more Windows-friendly...
(cherry picked from commit 60eab81709)
2022-02-21 16:15:17 +01:00
Harald Kuhr e9996f096f #660: Make sure region is within bounds of new test image...
(cherry picked from commit b400b6b157)
2022-02-21 16:15:17 +01:00
Harald Kuhr 93d42e1c24 #660: Farewell, Lena
(cherry picked from commit 499b3ef120)
2022-02-21 16:15:17 +01:00
Harald Kuhr 5824167600 IFF: Simplified aspect.
(cherry picked from commit 92bc9c73f6)
2022-02-21 16:15:14 +01:00
Harald Kuhr 126956ebd0 IFF: XS24 clean-up (again...)
(cherry picked from commit 2a77558cac)
2022-02-21 16:15:12 +01:00
Harald Kuhr d54ceba3ff IFF: XS24 clean-up
(cherry picked from commit 816cad60a8)
2022-02-21 16:15:11 +01:00
Harald Kuhr 11ee7e5e23 IFF: Thumbnail support for XS24 chunk (now without stderr output)
(cherry picked from commit 7167f81c69)
2022-02-21 16:15:11 +01:00
Harald Kuhr 954dffd213 IFF: Thumbnail support for XS24 chunk.
(cherry picked from commit f5cfa0e619)
2022-02-21 16:15:08 +01:00
Harald Kuhr b7b2a61c93 IFF: Read support for TVPaint DEEP and TVPP
+ Bonus: Massive code clean-up/refactor.

(cherry picked from commit 73ad024833)
2022-02-21 16:15:05 +01:00
Harald Kuhr 3cf6a4b836 IFF: More clean-up
(cherry picked from commit 379449b621)
2022-02-21 16:15:04 +01:00
Harald Kuhr a93be99933 IFF: Read support for Impulse (Imagine, Turbo Silver) RGB8 format.
(cherry picked from commit e17faad6fb)
2022-02-21 16:15:04 +01:00
Harald Kuhr b19bd1441f IFF clean-up.
(cherry picked from commit 1271a3d55e)
2022-02-21 16:15:04 +01:00
Harald Kuhr 482af60534 RIP: Sandbox
(cherry picked from commit 1cd594d113)
2022-02-21 16:15:00 +01:00
Harald Kuhr f55a6d30dd A little safer way to skip 6 bytes...
(cherry picked from commit b76f74e79a)
2022-02-21 16:14:56 +01:00
Harald Kuhr e5f6227479 #658: TGAImageReader now allows extension area of size 0
(cherry picked from commit 78817a489b)
2022-02-21 16:14:53 +01:00
Harald Kuhr 01d6fc8b49 #658: TGAImageReaderSpi now recognizes "true color" images with valid palette depth != 0
(cherry picked from commit b8f2a80ca6)
2022-02-21 16:14:51 +01:00
Oliver Schmidtmer f133ea7d61 findCompressionType always uses RLE if leading EOL is missing (#657)
Update of the last read byte is missing since the last update. So if only the first EOL is missing, further EOLs after the lines are not detected.

(cherry picked from commit ac8a36db1c)
2022-02-21 16:14:48 +01:00
Harald Kuhr 3d5cf0eecd #655 Experimental force raster conversion switch.
(cherry picked from commit 7e0d8922da)
2022-02-21 16:14:45 +01:00
Harald Kuhr 975e23c28f Fix for IIOInvalidTreeException: Invalid DHT node #559
(cherry picked from commit 9a6b8c9bfe)
2022-02-21 16:14:42 +01:00
Harald Kuhr be60f307f7 #656 Code clean-up + minor refactorings.
(cherry picked from commit eced5b8efd)
2022-02-21 16:14:39 +01:00
Oliver Schmidtmer 3e783fba92 Support writing ASCII array in TIFF metadata (#656)
* Support writing ASCII array in TIFF metadata

* corrected formatting and extracted string writing to method

(cherry picked from commit 74611e4e52)
2022-02-21 16:14:37 +01:00
Harald Kuhr 35ffe29e03 New CI badge + new maven badges, replaces #653
(cherry picked from commit b8614eca4d)
2022-02-21 16:14:35 +01:00
Harald Kuhr 55155aa61c #636: Correct name for shaded artifact.
(cherry picked from commit efd24456ac)
2022-02-21 16:14:32 +01:00
Gauthier 9e9decd5dd add support for Github Actions, publish snapshots to OSSRH automatically (#633)
* remove oss-parent

* add github workflow

* use java 16 for now

* disable fail fast

* add java 15

* use only java 8 and 11 for now

* snapshot deploy

* snapshot deploy

* oracle jdk

* oracle jdk

* oracle jdk

* kcms matrix

* kcms job name

* only deploy for snapshots

* try not operator

* prepare PR

* restore groupId

* Fixed Travis link + bonus project summary updates

* Readme improvements

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

* #629: Fixed build

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

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

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis

* test on java 17

* try to fix warning about maven-source-plugin

Co-authored-by: Harald Kuhr <harald.kuhr@gmail.com>
(cherry picked from commit 191b2371c8)
2022-02-21 16:14:30 +01:00
Harald Kuhr 3c0b78549e #652: Avoid OOME for large values outside TIFF, even if length is unknown
(cherry picked from commit 33419ef291)
2022-02-21 16:14:27 +01:00
Harald Kuhr 46db5a2fbf #648: (Re-)Added support for nested layer groups
(cherry picked from commit 123f0bb7fc)
2022-02-21 16:14:25 +01:00
Harald Kuhr f6a9477279 #648: Removed unnecessary parentheses.
(cherry picked from commit 99b5f28a49)
2022-02-21 16:14:24 +01:00
Harald Kuhr b7192ae857 #648: Simplified logic, code style fixes and clean up.
(cherry picked from commit b30fb4f8c3)
2022-02-21 16:14:24 +01:00
Jack Yun c0b2769e3b Support Group Layer in psd (#648)
(cherry picked from commit dc0bdcbd5b)
2022-02-21 16:14:24 +01:00
Harald Kuhr 6c27ec6b30 Updated with latest versions.
(cherry picked from commit 0cf29c167d)
2022-02-21 16:14:21 +01:00
Harald Kuhr 0c90196357 [maven-release-plugin] prepare for next development iteration 2021-12-27 09:53:44 +01:00
Harald Kuhr 48f82a159f [maven-release-plugin] prepare release twelvemonkeys-3.8.1 2021-12-27 09:53:40 +01:00
Harald Kuhr 7105738811 #651: Fix ExtraSamplesColorModel equals + hashcode to behave nicely with ImageTypeSpecifier comparison.
(cherry picked from commit 98e4b76206)
2021-12-24 13:00:15 +01:00
Harald Kuhr 10aa4ba41e Minor clean-up.
(cherry picked from commit aa4b5db054)
2021-12-24 13:00:15 +01:00
Harald Kuhr 6fb06da4d7 #651: Fix ExtraSamplesColorModel to create correct length elements array.
(cherry picked from commit 433311c10d)
2021-12-24 13:00:15 +01:00
Harald Kuhr a963e1c355 Alternative fix for #650: Allow usage in OSGi environment.
(cherry picked from commit f50178bc78)
2021-12-23 11:02:46 +01:00
Snyk bot 966a9da45d fix: upgrade commons-io:commons-io from 2.9.0 to 2.11.0 (#647)
Snyk has created this PR to upgrade commons-io:commons-io from 2.9.0 to 2.11.0.

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

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/9a1f6304-68e0-49c5-af4f-db1f87bd4f90?utm_source=github&utm_medium=referral&page=upgrade-pr

(cherry picked from commit e016e970e5)
2021-12-16 08:48:35 +01:00
Harald Kuhr 319b2c4e18 Overriding transitive dependency.
(cherry picked from commit 444aeabf21)
2021-12-15 17:01:38 +01:00
Harald Kuhr e9bf7d080c Getting rid of the dependencies too.
(cherry picked from commit 05507a59d6)
2021-12-15 16:29:53 +01:00
Harald Kuhr fb3691e2ee Delete deprecated servlet classes
(cherry picked from commit c4c89a0a25)
2021-12-15 16:24:41 +01:00
Harald Kuhr 25f9cc5c55 Delete deprecated Servlet classes
(cherry picked from commit b0ad6b2a4b)
2021-12-15 16:24:41 +01:00
Harald Kuhr 94777ddc96 #646: Spi now recognizes VP8 encoded images in VP8X ("extended format").
(cherry picked from commit 25c703f4b2)
2021-12-15 16:09:33 +01:00
Oleh Astappiev a4c12d0d64 Create jakartified package on build (#636)
* feat(servlet): create jakartified package on build

* feat(servlet): update README to include Jakarta classifier

(cherry picked from commit 529c59f93f)
2021-12-15 16:09:33 +01:00
Harald Kuhr 08a69886b1 Updated with the latest versions.
(cherry picked from commit 584b1d9b21)
2021-12-15 16:09:33 +01:00
Harald Kuhr ab85ff0ec8 Prepare for next version. 2021-12-15 16:09:06 +01:00
200 changed files with 3565 additions and 8949 deletions
-13
View File
@@ -1,13 +0,0 @@
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"
+14 -26
View File
@@ -1,14 +1,6 @@
name: CI name: CI
on: on: [ push, pull_request ]
push:
branches:
- '**'
- '!dependabot/**'
pull_request:
branches: [ 'master' ]
permissions: read-all
jobs: jobs:
test: test:
@@ -17,22 +9,20 @@ 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, 21 ] java: [ 8, 11, 17 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
checks: write
steps: steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@v2
- uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 - uses: actions/setup-java@v2
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 --batch-mode --no-transfer-progress test run: mvn test
- name: Publish Test Report - name: Publish Test Report
uses: mikepenz/action-junit-report@0831a82caad2465c31c6dd929978f640cb42556c # v4.0.3 uses: mikepenz/action-junit-report@v2
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
with: with:
report_paths: "**/target/surefire-reports/TEST*.xml" report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -41,17 +31,15 @@ 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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@v2
- 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@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 - uses: actions/setup-java@v2
with: with:
distribution: 'jdkfile' distribution: 'jdkfile'
jdkFile: ${{ runner.temp }}/java_package.tar.gz jdkFile: ${{ runner.temp }}/java_package.tar.gz
@@ -64,9 +52,9 @@ jobs:
- name: Display Java version - name: Display Java version
run: java -version run: java -version
- name: Run Tests - name: Run Tests
run: mvn --batch-mode --no-transfer-progress test run: mvn test
- name: Publish Test Report - name: Publish Test Report
uses: mikepenz/action-junit-report@0831a82caad2465c31c6dd929978f640cb42556c # v4.0.3 uses: mikepenz/action-junit-report@v2
if: ${{ !cancelled() }} if: ${{ !cancelled() }}
with: with:
report_paths: "**/target/surefire-reports/TEST*.xml" report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -78,9 +66,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@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - uses: actions/checkout@v2
- name: Set up Maven Central - name: Set up Maven Central
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0 uses: actions/setup-java@v2
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'
@@ -92,10 +80,10 @@ 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 --batch-mode help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV echo "PROJECT_VERSION=$(mvn 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 --batch-mode --no-transfer-progress deploy -P release -DskipTests run: mvn 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)
-69
View File
@@ -1,69 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "master" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "master" ]
schedule:
- cron: '26 13 * * 6'
permissions: {}
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
with:
category: "/language:${{matrix.language}}"
-62
View File
@@ -1,62 +0,0 @@
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '38 8 * * 2'
push:
branches: [ "master" ]
permissions: read-all # Declare default permissions as read only.
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
security-events: write # to upload the results to code-scanning dashboard.
id-token: write # to publish results and get a badge
steps:
- name: "Checkout code"
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# you want to enable the Branch-Protection check on the repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-fine-grained-pat-optional.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Publish Results:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
with:
sarif_file: results.sarif
-1
View File
@@ -15,4 +15,3 @@ private
profiles.xml profiles.xml
Thumbs.db Thumbs.db
.DS_Store .DS_Store
/.metadata/
+75 -65
View File
@@ -1,15 +1,9 @@
[![CI](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml/badge.svg)](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml) [![CI](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml/badge.svg)](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml)
[![CodeQL](https://github.com/haraldk/TwelveMonkeys/actions/workflows/codeql.yml/badge.svg)](https://github.com/haraldk/TwelveMonkeys/actions/workflows/codeql.yml)
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/haraldk/TwelveMonkeys/badge)](https://securityscorecards.dev/viewer/?uri=github.com/haraldk/TwelveMonkeys)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7900/badge)](https://www.bestpractices.dev/projects/7900)
[![Maven Central](https://img.shields.io/maven-central/v/com.twelvemonkeys.imageio/imageio?color=slateblue)](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio) [![Maven Central](https://img.shields.io/maven-central/v/com.twelvemonkeys.imageio/imageio?color=slateblue)](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio)
[![Maven Snapshot](https://img.shields.io/nexus/s/com.twelvemonkeys.imageio/imageio?label=development&server=https%3A%2F%2Foss.sonatype.org&color=slateblue)](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/) [![Maven Snapshot](https://img.shields.io/nexus/s/com.twelvemonkeys.imageio/imageio?label=development&server=https%3A%2F%2Foss.sonatype.org&color=slateblue)](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/)
[![StackOverflow](https://img.shields.io/badge/stack_overflow-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/twelvemonkeys) [![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.
@@ -56,7 +50,7 @@ As there is lots of legacy data out there, we see the need for open implementati
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html), **Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
and make sure you use an updated and secure version.* and make sure you use version 1.14 or later.*
Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the
[JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html). [JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html).
@@ -278,12 +272,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.9.4</version> <version>3.8.1</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.9.4</version> <version>3.8.1</version>
</dependency> </dependency>
<!-- <!--
@@ -293,7 +287,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.9.4</version> <version>3.8.1</version>
</dependency> </dependency>
<!-- <!--
@@ -302,7 +296,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.9.4</version> <version>3.8.1</version>
<classifier>jakarta</classifier> <classifier>jakarta</classifier>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -312,13 +306,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.9.4.jar twelvemonkeys-common-lang-3.8.1.jar
twelvemonkeys-common-io-3.9.4.jar twelvemonkeys-common-io-3.8.1.jar
twelvemonkeys-common-image-3.9.4.jar twelvemonkeys-common-image-3.8.1.jar
twelvemonkeys-imageio-core-3.9.4.jar twelvemonkeys-imageio-core-3.8.1.jar
twelvemonkeys-imageio-metadata-3.9.4.jar twelvemonkeys-imageio-metadata-3.8.1.jar
twelvemonkeys-imageio-jpeg-3.9.4.jar twelvemonkeys-imageio-jpeg-3.8.1.jar
twelvemonkeys-imageio-tiff-3.9.4.jar twelvemonkeys-imageio-tiff-3.8.1.jar
#### Deploying the plugins in a web app #### Deploying the plugins in a web app
@@ -384,50 +378,81 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
### Links to prebuilt binaries ### Links to prebuilt binaries
##### Latest version (3.9.4) ##### Latest version (3.8.1)
The latest version that will run on Java 7 is 3.9.4. Later versions will require Java 8 or later. Requires Java 7 or later.
Common dependencies Common dependencies
* [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-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-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-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-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) * [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)
ImageIO dependencies ImageIO dependencies
* [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-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-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-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 plugins ImageIO plugins
* [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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-thumbsdb-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.9.4/imageio-thumbsdb-3.9.4.jar) * [imageio-thumbsdb-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.8.1/imageio-thumbsdb-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-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-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-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-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-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 plugins requiring 3rd party libs ImageIO plugins requiring 3rd party libs
* [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) * [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)
Photoshop Path support for ImageIO Photoshop Path support for ImageIO
* [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) * [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)
Servlet support Servlet support
* [servlet-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.9.4/servlet-3.9.4.jar) * [servlet-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.8.1/servlet-3.8.1.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-2022, Harald Kuhr Copyright (c) 2008-2020, 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
@@ -459,9 +484,8 @@ 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, Gradle or other build tool with dependency management, a: The easiest way is to build your own project using Maven, and just add dependencies to the specific plug-ins you need.
and just add dependencies to the specific plug-ins you need. If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above.
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?
@@ -479,41 +503,27 @@ 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 to do, is to make sure you have the TwelveMonkeys ImageIO JARs in your classpath. All you have have to do, is to make sure you have the TwelveMonkeys 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`, `BMPImageReader` `TIFFImageReader`, and the Apple provided `TIFFImageReader` on OS X, the Sun/Oracle provided `JPEGImageReader` and `BMPImageReader`, 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 considered good enough as-is. a: The short answer is simply that the built-in support in ImageIO for these formats are 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.
The most obvious being: The most obvious being:
- It's not actively developed. No issue has been fixed for years. - It's not actively developed. No issues has been fixed for years.
- To get full format support, you need native libs. - To get full format support, you need native libs.
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source. Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
Some environments may also prevent deployment of native libs, which brings us back to square one. Some environments may also prevent deployment of native libs, which brings us back to square one.
-5
View File
@@ -1,5 +0,0 @@
# 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.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<groupId>com.twelvemonkeys.bom</groupId> <groupId>com.twelvemonkeys.bom</groupId>
+2 -11
View File
@@ -4,13 +4,13 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-image</artifactId> <artifactId>common-image</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: Image</name> <name>TwelveMonkeys :: Common :: Image</name>
<description> <description>
TwelveMonkeys Common image support classes. The TwelveMonkeys Common Image support
</description> </description>
<properties> <properties>
@@ -36,13 +36,4 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>
@@ -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 source the source image * @param pSource the source image
* @throws IllegalArgumentException if {@code source == null} * @throws IllegalArgumentException if {@code pSource == null}
*/ */
public BufferedImageFactory(final Image source) { public BufferedImageFactory(final Image pSource) {
this(source != null ? source.getSource() : null); this(pSource != null ? pSource.getSource() : null);
} }
/** /**
* Creates a {@code BufferedImageFactory}. * Creates a {@code BufferedImageFactory}.
* @param source the source image producer * @param pSource the source image producer
* @throws IllegalArgumentException if {@code source == null} * @throws IllegalArgumentException if {@code pSource == null}
*/ */
public BufferedImageFactory(final ImageProducer source) { public BufferedImageFactory(final ImageProducer pSource) {
Validate.notNull(source, "source"); Validate.notNull(pSource, "source");
producer = source; producer = pSource;
} }
/** /**
@@ -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 region the source region * @param pRegion the source region
*/ */
public void setSourceRegion(final Rectangle region) { public void setSourceRegion(final Rectangle pRegion) {
// Re-fetch everything, if region changed // Re-fetch everything, if region changed
if (x != region.x || y != region.y || width != region.width || height != region.height) { if (x != pRegion.x || y != pRegion.y || width != pRegion.width || height != pRegion.height) {
dispose(); dispose();
} }
x = region.x; x = pRegion.x;
y = region.y; y = pRegion.y;
width = region.width; width = pRegion.width;
height = region.height; height = pRegion.height;
} }
/** /**
* Sets the source subsampling for the new image. * Sets the source subsampling for the new image.
* *
* @param xSubsampling horizontal subsampling factor * @param pXSub horizontal subsampling factor
* @param ySubsampling vertical subsampling factor * @param pYSub vertical subsampling factor
*/ */
public void setSourceSubsampling(int xSubsampling, int ySubsampling) { public void setSourceSubsampling(int pXSub, int pYSub) {
// Re-fetch everything, if subsampling changed // Re-fetch everything, if subsampling changed
if (xSub != xSubsampling || ySub != ySubsampling) { if (xSub != pXSub || ySub != pYSub) {
dispose(); dispose();
} }
if (xSubsampling > 1) { if (pXSub > 1) {
xSub = xSubsampling; xSub = pXSub;
} }
if (ySubsampling > 1) { if (pYSub > 1) {
ySub = ySubsampling; ySub = pYSub;
} }
} }
private synchronized void doFetch(final boolean colorModelOnly) throws ImageConversionException { private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
if (!fetching && (!colorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) { if (!fetching && (!pColorModelOnly && 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 (!colorModelOnly && (xSub > 1 || ySub > 1)) { if (!pColorModelOnly && (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,41 +207,38 @@ public final class BufferedImageFactory {
// Start fetching // Start fetching
fetching = true; fetching = true;
readColorModelOnly = colorModelOnly; readColorModelOnly = pColorModelOnly;
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);
} }
} }
try {
if (consumerException != null) { if (consumerException != null) {
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException); throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
} }
if (colorModelOnly) { if (pColorModelOnly) {
createColorModel(); createColorModel();
} }
else { else {
createBuffered(); createBuffered();
} }
} }
finally {
// 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() {
@@ -256,9 +253,8 @@ public final class BufferedImageFactory {
} }
} }
if (buffered == null) { // Clean up, in case any objects are copied/cloned, so we can free resources
throw new ImageConversionException("Could not create BufferedImage"); freeResources();
}
} }
private void freeResources() { private void freeResources() {
@@ -284,27 +280,27 @@ public final class BufferedImageFactory {
/** /**
* Adds a progress listener to this factory. * Adds a progress listener to this factory.
* *
* @param listener the progress listener * @param pListener the progress listener
*/ */
public void addProgressListener(ProgressListener listener) { public void addProgressListener(ProgressListener pListener) {
if (listener == null) { if (pListener == null) {
return; return;
} }
if (listeners == null) { if (listeners == null) {
listeners = new CopyOnWriteArrayList<>(); listeners = new CopyOnWriteArrayList<ProgressListener>();
} }
listeners.add(listener); listeners.add(pListener);
} }
/** /**
* Removes a progress listener from this factory. * Removes a progress listener from this factory.
* *
* @param listener the progress listener * @param pListener the progress listener
*/ */
public void removeProgressListener(ProgressListener listener) { public void removeProgressListener(ProgressListener pListener) {
if (listener == null) { if (pListener == null) {
return; return;
} }
@@ -312,7 +308,7 @@ public final class BufferedImageFactory {
return; return;
} }
listeners.remove(listener); listeners.remove(pListener);
} }
/** /**
@@ -328,22 +324,21 @@ 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 inputPixels the pixel data to convert * @param pPixels the pixel data to convert
* @return an array of {@code short}s, same length as {@code inputPixels} * @return an array of {@code short}s, same lenght as {@code pPixels}
*/ */
private static short[] toShortPixels(int[] inputPixels) { private static short[] toShortPixels(int[] pPixels) {
short[] pixels = new short[inputPixels.length]; short[] pixels = new short[pPixels.length];
for (int i = 0; i < pixels.length; i++) { for (int i = 0; i < pixels.length; i++) {
pixels[i] = (short) (inputPixels[i] & 0xffff); pixels[i] = (short) (pPixels[i] & 0xffff);
} }
return pixels; return pixels;
@@ -356,17 +351,17 @@ public final class BufferedImageFactory {
* @see BufferedImageFactory#addProgressListener * @see BufferedImageFactory#addProgressListener
* @see BufferedImageFactory#removeProgressListener * @see BufferedImageFactory#removeProgressListener
*/ */
public interface ProgressListener extends EventListener { public static 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 factory the factory reporting the progress * @param pFactory the factory reporting the progress
* @param percentage the percentage of progress * @param pPercentage the percentage of progress
*/ */
void progress(BufferedImageFactory factory, float percentage); void progress(BufferedImageFactory pFactory, float pPercentage);
} }
private class Consumer implements ImageConsumer { private class Consumer implements ImageConsumer {
@@ -451,18 +446,18 @@ public final class BufferedImageFactory {
processProgress(pY + pHeight); processProgress(pY + pHeight);
} }
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, short[] pixels, int offset, int scanSize) { public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) {
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize); setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
} }
private void setColorModelOnce(final ColorModel colorModel) { private void setColorModelOnce(final ColorModel pModel) {
// 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 != colorModel) { if (sourceColorModel != pModel) {
if (sourcePixels == null) { if (/*sourceColorModel == null ||*/ sourcePixels == null) {
sourceColorModel = colorModel; sourceColorModel = pModel;
} }
else { else {
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported"); throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
@@ -475,16 +470,17 @@ public final class BufferedImageFactory {
} }
} }
@Override public void imageComplete(int pStatus) {
public void imageComplete(int status) {
fetching = false; fetching = false;
if (producer != null) { if (producer != null) {
producer.removeConsumer(this); producer.removeConsumer(this);
} }
if (status == ImageConsumer.IMAGEERROR) { switch (pStatus) {
case ImageConsumer.IMAGEERROR:
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR"); consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
break;
} }
synchronized (BufferedImageFactory.this) { synchronized (BufferedImageFactory.this) {
@@ -492,18 +488,16 @@ public final class BufferedImageFactory {
} }
} }
@Override public void setColorModel(ColorModel pModel) {
public void setColorModel(ColorModel colorModel) { setColorModelOnce(pModel);
setColorModelOnce(colorModel);
} }
@Override public void setDimensions(int pWidth, int pHeight) {
public void setDimensions(int w, int h) {
if (width < 0) { if (width < 0) {
width = w - x; width = pWidth - x;
} }
if (height < 0) { if (height < 0) {
height = h - y; height = pHeight - y;
} }
// Hmm.. Special case, but is it a good idea? // Hmm.. Special case, but is it a good idea?
@@ -512,31 +506,27 @@ public final class BufferedImageFactory {
} }
} }
@Override public void setHints(int pHintflags) {
public void setHints(int hintFlags) {
// ignore // ignore
} }
@Override public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, byte[] pixels, int offset, int scanSize) { setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
} }
@Override public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, int[] pixels, int offset, int scanSize) { if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) {
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(x, y, width, height, colorModel, toShortPixels(pixels), offset, scanSize); setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
} }
else { else {
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize); setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
} }
} }
@Override public void setProperties(Hashtable pProperties) {
public void setProperties(Hashtable properties) { sourceProperties = pProperties;
sourceProperties = properties;
} }
} }
@@ -844,7 +844,7 @@ public final class ImageUtil {
return false; return false;
} }
for (int i = 0; i < mapSize1; i++) { for (int i = 0; i > mapSize1; i++) {
if (icm1.getRGB(i) != icm2.getRGB(i)) { if (icm1.getRGB(i) != icm2.getRGB(i)) {
return false; return false;
} }
@@ -34,13 +34,14 @@ import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageProducer;
import java.awt.image.IndexColorModel;
import java.net.URL; import java.net.URL;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/** /**
* BufferedImageFactoryTestCase * BufferedImageFactoryTestCase
@@ -259,9 +260,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 factory, float percentage) { public void progress(BufferedImageFactory pFactory, float pPercentage) {
if (percentage > 5) { if (pPercentage > 5) {
factory.abort(); pFactory.abort();
} }
} }
}); });
@@ -342,7 +343,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 factory, float percentage) { public void progress(BufferedImageFactory pFactory, float pPercentage) {
} }
}); });
factory.getBufferedImage(); factory.getBufferedImage();
@@ -379,11 +380,11 @@ public class BufferedImageFactoryTest {
this.factory = factory; this.factory = factory;
} }
public void progress(BufferedImageFactory factory, float percentage) { public void progress(BufferedImageFactory pFactory, float pPercentage) {
assertEquals(this.factory, factory); assertEquals(factory, pFactory);
assertTrue(percentage >= progress && percentage <= 100f); assertTrue(pPercentage >= progress && pPercentage <= 100f);
progress = percentage; progress = pPercentage;
} }
+2 -10
View File
@@ -4,13 +4,13 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-io</artifactId> <artifactId>common-io</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: IO</name> <name>TwelveMonkeys :: Common :: IO</name>
<description> <description>
TwelveMonkeys Common I/O support classes. The TwelveMonkeys Common IO support
</description> </description>
<properties> <properties>
@@ -31,12 +31,4 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>
@@ -56,8 +56,8 @@ public class CompoundReader extends Reader {
private int currentReader; private int currentReader;
private int markedReader; private int markedReader;
private long mark; private int mark;
private long next; private int mNext;
/** /**
* Create a new compound reader. * Create a new compound reader.
@@ -76,7 +76,7 @@ public class CompoundReader extends Reader {
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
// reference can't change, only it's elements // reference can't change, only it's elements
readers = new ArrayList<>(); readers = new ArrayList<Reader>();
boolean markSupported = true; boolean markSupported = true;
while (pReaders.hasNext()) { while (pReaders.hasNext()) {
@@ -101,7 +101,7 @@ public class CompoundReader extends Reader {
} }
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods! // NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
next = 0; mNext = 0;
return current; return current;
} }
@@ -135,7 +135,7 @@ public class CompoundReader extends Reader {
synchronized (finalLock) { synchronized (finalLock) {
ensureOpen(); ensureOpen();
mark = next; mark = mNext;
markedReader = currentReader; markedReader = currentReader;
current.mark(pReadLimit); current.mark(pReadLimit);
@@ -158,7 +158,7 @@ public class CompoundReader extends Reader {
} }
current.reset(); current.reset();
next = mark; mNext = mark;
} }
} }
@@ -177,13 +177,13 @@ public class CompoundReader extends Reader {
return read(); // In case of 0-length readers return read(); // In case of 0-length readers
} }
next++; mNext++;
return read; return read;
} }
} }
public int read(char[] pBuffer, int pOffset, int pLength) throws IOException { public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (finalLock) { synchronized (finalLock) {
int read = current.read(pBuffer, pOffset, pLength); int read = current.read(pBuffer, pOffset, pLength);
@@ -192,7 +192,7 @@ public class CompoundReader extends Reader {
return read(pBuffer, pOffset, pLength); // In case of 0-length readers return read(pBuffer, pOffset, pLength); // In case of 0-length readers
} }
next += read; mNext += read;
return read; return read;
} }
@@ -213,7 +213,7 @@ public class CompoundReader extends Reader {
return skip(pChars); // In case of 0-length readers return skip(pChars); // In case of 0-length readers
} }
next += skipped; mNext += skipped;
return skipped; return skipped;
} }
@@ -50,8 +50,8 @@ public class StringArrayReader extends StringReader {
protected final Object finalLock; protected final Object finalLock;
private int currentSting; private int currentSting;
private int markedString; private int markedString;
private long mark; private int mark;
private long next; private int next;
/** /**
* Create a new string array reader. * Create a new string array reader.
@@ -151,7 +151,7 @@ public class StringArrayReader extends StringReader {
} }
} }
public int read(char[] pBuffer, int pOffset, int pLength) throws IOException { public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
synchronized (finalLock) { synchronized (finalLock) {
int read = current.read(pBuffer, pOffset, pLength); int read = current.read(pBuffer, pOffset, pLength);
@@ -41,20 +41,21 @@ 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 stream}. * Creates a {@code SubStream} of the given {@code pStream}.
* *
* @param stream the underlying input stream * @param pStream the underlying input stream
* @param length maximum number of bytes to read from this stream * @param pLength maximum number of bytes to read drom this stream
*/ */
public SubStream(final InputStream stream, final long length) { public SubStream(final InputStream pStream, final long pLength) {
super(Validate.notNull(stream, "stream")); super(Validate.notNull(pStream, "stream"));
bytesLeft = Validate.isTrue(length >= 0, length, "length < 0: %s"); bytesLeft = pLength;
} }
/** /**
@@ -63,23 +64,22 @@ public final class SubStream extends FilterInputStream {
*/ */
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// NOTE: Do not close the underlying stream, but consume it // NOTE: Do not close the underlying stream
while (bytesLeft > 0) { while (bytesLeft > 0) {
if (skip(bytesLeft) <= 0 && read() < 0) { //noinspection ResultOfMethodCallIgnored
break; skip(bytesLeft);
}
} }
} }
@Override @Override
public int available() throws IOException { public int available() throws IOException {
return (int) findMaxLen(super.available()); return (int) Math.min(super.available(), bytesLeft);
} }
@Override @Override
public void mark(int readLimit) { public void mark(int pReadLimit) {
super.mark(readLimit);// This either succeeds or does nothing... super.mark(pReadLimit);// This either succeeds or does nothing...
markLimit = readLimit; markLimit = pReadLimit;
} }
@Override @Override
@@ -93,42 +93,44 @@ 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 int read(byte[] bytes) throws IOException { public final int read(byte[] pBytes) throws IOException {
return read(bytes, 0, bytes.length); return read(pBytes, 0, pBytes.length);
} }
@Override @Override
public int read(final byte[] bytes, final int off, final int len) throws IOException { public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (bytesLeft <= 0) { if (bytesLeft <= 0) {
return -1; return -1;
} }
int read = super.read(bytes, off, (int) findMaxLen(len)); int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
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 length the requested length * @param pLength the requested length
* @return the maximum number of bytes to read * @return the maximum number of bytes to read
*/ */
private long findMaxLen(long length) { private long findMaxLen(long pLength) {
return bytesLeft < length ? Math.max(bytesLeft, 0) : length; if (bytesLeft < pLength) {
return (int) Math.max(bytesLeft, 0);
}
else {
return pLength;
}
}
@Override
public long skip(long pLength) throws IOException {
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
bytesLeft -= skipped;
return skipped;
} }
} }
@@ -1,114 +0,0 @@
package com.twelvemonkeys.io;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* SubStreamTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: SubStreamTest.java,v 1.0 07/11/2023 haraldk Exp$
*/
public class SubStreamTest {
private final Random rng = new Random(2918475687L);
@SuppressWarnings("resource")
@Test(expected = IllegalArgumentException.class)
public void testCreateNullStream() {
new SubStream(null, 42);
}
@Test(expected = IllegalArgumentException.class)
public void testCreateNegativeLength() {
new SubStream(new ByteArrayInputStream(new byte[1]), -1);
}
@Test
public void testReadAll() throws IOException {
byte[] buf = new byte[128];
rng.nextBytes(buf);
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
for (byte b : buf) {
assertEquals(b, (byte) stream.read());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testReadAllArray() throws IOException {
byte[] buf = new byte[128];
rng.nextBytes(buf);
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
byte[] temp = new byte[buf.length / 4];
for (int i = 0; i < 4; i++) {
assertEquals(temp.length, stream.read(temp)); // Depends on ByteArrayInputStream specifics...
assertArrayEquals(Arrays.copyOfRange(buf, i * temp.length, (i + 1) * temp.length), temp);
}
assertEquals(-1, stream.read());
}
}
@Test
public void testSkipAll() throws IOException {
byte[] buf = new byte[128];
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
assertEquals(128, stream.skip(buf.length)); // Depends on ByteArrayInputStream specifics...
assertEquals(-1, stream.read());
}
}
@SuppressWarnings("EmptyTryBlock")
@Test
public void testCloseConsumesAll() throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[128]);
try (InputStream ignore = new SubStream(stream, 128)) {
// Nothing here...
}
assertEquals(0, stream.available());
assertEquals(-1, stream.read());
}
@SuppressWarnings("EmptyTryBlock")
@Test
public void testCloseConsumesAllLongStream() throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[256]);
try (InputStream ignore = new SubStream(stream, 128)) {
// Nothing here...
}
assertEquals(128, stream.available());
assertEquals(0, stream.read());
}
@SuppressWarnings("EmptyTryBlock")
@Test(timeout = 500L)
public void testCloseConsumesAllShortStream() throws IOException {
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[13]);
try (InputStream ignore = new SubStream(stream, 42)) {
// Nothing here...
}
assertEquals(0, stream.available());
assertEquals(-1, stream.read());
}
}
+2 -10
View File
@@ -4,25 +4,17 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>common-lang</artifactId> <artifactId>common-lang</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: Language support</name> <name>TwelveMonkeys :: Common :: Language support</name>
<description> <description>
TwelveMonkeys Common language support classes. The TwelveMonkeys Common Language support
</description> </description>
<properties> <properties>
<project.jpms.module.name>com.twelvemonkeys.common.lang</project.jpms.module.name> <project.jpms.module.name>com.twelvemonkeys.common.lang</project.jpms.module.name>
</properties> </properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>
@@ -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() + " at pos " + pe.getErrorOffset()); throw new IllegalArgumentException(pe.getMessage());
} }
} }
@@ -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);
format = new SimpleDateFormat("HH:mm"); date = StringUtil.toDate("1:02 am",
date = StringUtil.toDate("1:02", format); DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US));
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.10.2-SNAPSHOT</version> <version>3.8.4-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.2</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
+3 -12
View File
@@ -4,13 +4,13 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<groupId>com.twelvemonkeys.contrib</groupId> <groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId> <artifactId>contrib</artifactId>
<name>TwelveMonkeys :: Contrib</name> <name>TwelveMonkeys :: Contrib</name>
<description> <description>
Contributions to TwelveMonkeys and code that doesn't fit anywhere else. Contributions to TwelveMonkeys which are not matching into the ImageIO plug-ins.
</description> </description>
<dependencies> <dependencies>
@@ -65,17 +65,8 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.2</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>
+3 -15
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.10.2-SNAPSHOT</version> <version>3.8.4-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.17</batik.version> <batik.version>1.14</batik.version>
</properties> </properties>
<build> <build>
@@ -33,18 +33,6 @@
</systemPropertyVariables> </systemPropertyVariables>
</configuration> </configuration>
</plugin> </plugin>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>
@@ -63,7 +51,7 @@
<dependency> <dependency>
<groupId>commons-io</groupId> <groupId>commons-io</groupId>
<artifactId>commons-io</artifactId> <artifactId>commons-io</artifactId>
<version>2.15.0</version> <version>2.11.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
@@ -33,7 +33,6 @@ package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.lang.StringUtil;
import org.apache.batik.anim.dom.SVGDOMImplementation; import org.apache.batik.anim.dom.SVGDOMImplementation;
@@ -92,10 +91,10 @@ public class SVGImageReader extends ImageReaderBase {
/** /**
* Creates an {@code SVGImageReader}. * Creates an {@code SVGImageReader}.
* *
* @param provider the provider * @param pProvider the provider
*/ */
public SVGImageReader(final ImageReaderSpi provider) { public SVGImageReader(final ImageReaderSpi pProvider) {
super(provider); super(pProvider);
} }
protected void resetMembers() { protected void resetMembers() {
@@ -109,20 +108,20 @@ public class SVGImageReader extends ImageReaderBase {
} }
@Override @Override
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { public void setInput(Object pInput, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata); super.setInput(pInput, seekForwardOnly, ignoreMetadata);
if (imageInput != null) { if (imageInput != null) {
TranscoderInput transcoderInput = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput)); TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
rasterizer.setInput(transcoderInput); rasterizer.setInput(input);
} }
} }
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException { public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
checkBounds(imageIndex); checkBounds(pIndex);
if (param instanceof SVGReadParam) { if (pParam instanceof SVGReadParam) {
SVGReadParam svgParam = (SVGReadParam) param; SVGReadParam svgParam = (SVGReadParam) pParam;
// set the external-resource-resolution preference // set the external-resource-resolution preference
allowExternalResources = svgParam.isAllowExternalResources(); allowExternalResources = svgParam.isAllowExternalResources();
@@ -140,17 +139,17 @@ public class SVGImageReader extends ImageReaderBase {
} }
Dimension size = null; Dimension size = null;
if (param != null) { if (pParam != null) {
size = param.getSourceRenderSize(); size = pParam.getSourceRenderSize();
} }
if (size == null) { if (size == null) {
size = new Dimension(getWidth(imageIndex), getHeight(imageIndex)); size = new Dimension(getWidth(pIndex), getHeight(pIndex));
} }
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), size.width, size.height); BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height);
// Read in the image, using the Batik Transcoder // Read in the image, using the Batik Transcoder
processImageStarted(imageIndex); processImageStarted(pIndex);
BufferedImage image = rasterizer.getImage(); BufferedImage image = rasterizer.getImage();
@@ -174,18 +173,18 @@ public class SVGImageReader extends ImageReaderBase {
return ex.getException() != null ? ex.getException() : ex; return ex.getException() != null ? ex.getException() : ex;
} }
private TranscodingHints paramsToHints(SVGReadParam param) throws IOException { private TranscodingHints paramsToHints(SVGReadParam pParam) throws IOException {
TranscodingHints hints = new TranscodingHints(); TranscodingHints hints = new TranscodingHints();
// Note: We must allow generic ImageReadParams, so converting to // Note: We must allow generic ImageReadParams, so converting to
// TanscodingHints should be done outside the SVGReadParam class. // TanscodingHints should be done outside the SVGReadParam class.
// Set dimensions // Set dimensions
Dimension size = param.getSourceRenderSize(); Dimension size = pParam.getSourceRenderSize();
Rectangle viewBox = rasterizer.getViewBox(); Rectangle viewBox = rasterizer.getViewBox();
if (size == null) { if (size == null) {
// SVG is not a pixel based format, but we'll scale it, according to // SVG is not a pixel based format, but we'll scale it, according to
// the subsampling for compatibility // the subsampling for compatibility
size = getSourceRenderSizeFromSubsamping(param, viewBox.getSize()); size = getSourceRenderSizeFromSubsamping(pParam, viewBox.getSize());
} }
if (size != null) { if (size != null) {
@@ -194,7 +193,7 @@ public class SVGImageReader extends ImageReaderBase {
} }
// Set area of interest // Set area of interest
Rectangle region = param.getSourceRegion(); Rectangle region = pParam.getSourceRegion();
if (region != null) { if (region != null) {
hints.put(ImageTranscoder.KEY_AOI, region); hints.put(ImageTranscoder.KEY_AOI, region);
@@ -218,7 +217,7 @@ public class SVGImageReader extends ImageReaderBase {
} }
// Background color // Background color
Paint bg = param.getBackgroundColor(); Paint bg = pParam.getBackgroundColor();
if (bg != null) { if (bg != null) {
hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg); hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg);
} }
@@ -226,10 +225,10 @@ public class SVGImageReader extends ImageReaderBase {
return hints; return hints;
} }
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam param, Dimension origSize) { private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) {
if (param.getSourceXSubsampling() > 1 || param.getSourceYSubsampling() > 1) { if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
return new Dimension((int) (origSize.width / (float) param.getSourceXSubsampling()), return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()),
(int) (origSize.height / (float) param.getSourceYSubsampling())); (int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
} }
return null; return null;
} }
@@ -238,19 +237,19 @@ public class SVGImageReader extends ImageReaderBase {
return new SVGReadParam(); return new SVGReadParam();
} }
public int getWidth(int imageIndex) throws IOException { public int getWidth(int pIndex) throws IOException {
checkBounds(imageIndex); checkBounds(pIndex);
return rasterizer.getDefaultWidth(); return rasterizer.getDefaultWidth();
} }
public int getHeight(int imageIndex) throws IOException { public int getHeight(int pIndex) throws IOException {
checkBounds(imageIndex); checkBounds(pIndex);
return rasterizer.getDefaultHeight(); return rasterizer.getDefaultHeight();
} }
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) { public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
return Collections.singleton(ImageTypeSpecifiers.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator(); return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
} }
/** /**
@@ -602,7 +601,6 @@ public class SVGImageReader extends ImageReaderBase {
initialized = true; initialized = true;
try { try {
super.addTranscodingHint(SVGAbstractTranscoder.KEY_ALLOW_EXTERNAL_RESOURCES, allowExternalResources);
super.transcode(transcoderInput, null); super.transcode(transcoderInput, null);
} }
catch (TranscoderException e) { catch (TranscoderException e) {
@@ -635,8 +633,8 @@ public class SVGImageReader extends ImageReaderBase {
return viewBox.getBounds(); return viewBox.getBounds();
} }
void setInput(final TranscoderInput input) { void setInput(final TranscoderInput pInput) {
transcoderInput = input; transcoderInput = pInput;
} }
@Override @Override
@@ -60,22 +60,22 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
super(new SVGProviderInfo()); super(new SVGProviderInfo());
} }
public boolean canDecodeInput(final Object source) throws IOException { public boolean canDecodeInput(final Object pSource) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source); return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
} }
@SuppressWarnings("StatementWithEmptyBody") @SuppressWarnings("StatementWithEmptyBody")
private static boolean canDecode(final ImageInputStream input) throws IOException { private static boolean canDecode(final ImageInputStream pInput) throws IOException {
// NOTE: This test is quite quick as it does not involve any parsing, // NOTE: This test is quite quick as it does not involve any parsing,
// however it may not recognize all kinds of SVG documents. // however it may not recognize all kinds of SVG documents.
try { try {
input.mark(); pInput.mark();
// TODO: This is not ok for UTF-16 and other wide encodings // TODO: This is not ok for UTF-16 and other wide encodings
// TODO: Use an XML (encoding) aware Reader instance instead // TODO: Use an XML (encoding) aware Reader instance instead
// Need to figure out pretty fast if this is XML or not // Need to figure out pretty fast if this is XML or not
int b; int b;
while (Character.isWhitespace((char) (b = input.read()))) { while (Character.isWhitespace((char) (b = pInput.read()))) {
// Skip over leading WS // Skip over leading WS
} }
@@ -95,30 +95,30 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
byte[] buffer = new byte[4]; byte[] buffer = new byte[4];
while (true) { while (true) {
input.readFully(buffer); pInput.readFully(buffer);
if (buffer[0] == '?') { if (buffer[0] == '?') {
// This is the XML declaration or a processing instruction // This is the XML declaration or a processing instruction
while (!((input.readByte() & 0xFF) == '?' && input.read() == '>')) { while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) {
// Skip until end of XML declaration or processing instruction or EOF // Skip until end of XML declaration or processing instruction or EOF
} }
} }
else if (buffer[0] == '!') { else if (buffer[0] == '!') {
if (buffer[1] == '-' && buffer[2] == '-') { if (buffer[1] == '-' && buffer[2] == '-') {
// This is a comment // This is a comment
while (!((input.readByte() & 0xFF) == '-' && input.read() == '-' && input.read() == '>')) { while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) {
// Skip until end of comment or EOF // Skip until end of comment or EOF
} }
} }
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C' else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
&& input.read() == 'T' && input.read() == 'Y' && pInput.read() == 'T' && pInput.read() == 'Y'
&& input.read() == 'P' && input.read() == 'E') { && pInput.read() == 'P' && pInput.read() == 'E') {
// This is the DOCTYPE declaration // This is the DOCTYPE declaration
while (Character.isWhitespace((char) (b = input.read()))) { while (Character.isWhitespace((char) (b = pInput.read()))) {
// Skip over WS // Skip over WS
} }
if (b == 's' && input.read() == 'v' && input.read() == 'g') { if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
// It's SVG, identified by DOCTYPE // It's SVG, identified by DOCTYPE
return true; return true;
} }
@@ -142,7 +142,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
return false; return false;
} }
while ((input.readByte() & 0xFF) != '<') { while ((pInput.readByte() & 0xFF) != '<') {
// Skip over, until next begin tag or EOF // Skip over, until next begin tag or EOF
} }
} }
@@ -153,7 +153,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
} }
finally { finally {
//noinspection ThrowFromFinallyBlock //noinspection ThrowFromFinallyBlock
input.reset(); pInput.reset();
} }
} }
@@ -51,16 +51,16 @@ public class SVGReadParam extends ImageReadParam {
return background; return background;
} }
public void setBackgroundColor(Paint color) { public void setBackgroundColor(Paint pColor) {
background = color; background = pColor;
} }
public String getBaseURI() { public String getBaseURI() {
return baseURI; return baseURI;
} }
public void setBaseURI(String baseURI) { public void setBaseURI(String pBaseURI) {
this.baseURI = baseURI; baseURI = pBaseURI;
} }
public void setAllowExternalResources(boolean allow) { public void setAllowExternalResources(boolean allow) {
+1 -20
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.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-bmp</artifactId> <artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name> <name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -26,23 +26,4 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi,
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageWriterSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
@@ -81,8 +81,8 @@ public final class BMPImageReader extends ImageReaderBase {
super(new BMPImageReaderSpi()); super(new BMPImageReaderSpi());
} }
BMPImageReader(final ImageReaderSpi provider) { BMPImageReader(final ImageReaderSpi pProvider) {
super(provider); super(pProvider);
} }
@Override @Override
@@ -129,7 +129,6 @@ public final class BMPImageReader extends ImageReaderBase {
// Read DIB header // Read DIB header
header = DIBHeader.read(imageInput); header = DIBHeader.read(imageInput);
// System.out.println("header = " + header);
if (pixelOffset < header.size + DIB.BMP_FILE_HEADER_SIZE) { if (pixelOffset < header.size + DIB.BMP_FILE_HEADER_SIZE) {
throw new IIOException("Invalid pixel offset: " + pixelOffset); throw new IIOException("Invalid pixel offset: " + pixelOffset);
@@ -187,30 +186,30 @@ public final class BMPImageReader extends ImageReaderBase {
} }
@Override @Override
public int getWidth(int imageIndex) throws IOException { public int getWidth(int pImageIndex) throws IOException {
checkBounds(imageIndex); checkBounds(pImageIndex);
return header.getWidth(); return header.getWidth();
} }
@Override @Override
public int getHeight(int imageIndex) throws IOException { public int getHeight(int pImageIndex) throws IOException {
checkBounds(imageIndex); checkBounds(pImageIndex);
return header.getHeight(); return header.getHeight();
} }
@Override @Override
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException { public Iterator<ImageTypeSpecifier> getImageTypes(int pImageIndex) throws IOException {
checkBounds(imageIndex); checkBounds(pImageIndex);
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB // TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
return Collections.singletonList(getRawImageType(imageIndex)).iterator(); return Collections.singletonList(getRawImageType(pImageIndex)).iterator();
} }
@Override @Override
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException { public ImageTypeSpecifier getRawImageType(int pImageIndex) throws IOException {
checkBounds(imageIndex); checkBounds(pImageIndex);
if (header.getPlanes() != 1) { if (header.getPlanes() != 1) {
throw new IIOException("Multiple planes not supported"); throw new IIOException("Multiple planes not supported");
@@ -686,8 +685,8 @@ public final class BMPImageReader extends ImageReaderBase {
} }
@SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" }) @SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" })
static <T extends Throwable> void throwAs(final Class<T> type, final Throwable throwable) throws T { static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
throw (T) throwable; throw (T) pThrowable;
} }
private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener { private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
@@ -65,16 +65,16 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
} }
} }
public boolean canDecodeInput(final Object source) throws IOException { public boolean canDecodeInput(final Object pSource) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source); return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
} }
private static boolean canDecode(final ImageInputStream input) throws IOException { private static boolean canDecode(final ImageInputStream pInput) throws IOException {
byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes) byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes)
try { try {
input.mark(); pInput.mark();
input.readFully(fileHeader); pInput.readFully(fileHeader);
// Magic: BM // Magic: BM
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') { if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
@@ -112,15 +112,15 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
} }
} }
finally { finally {
input.reset(); pInput.reset();
} }
} }
public ImageReader createReaderInstance(final Object extension) { public ImageReader createReaderInstance(final Object pExtension) {
return new BMPImageReader(this); return new BMPImageReader(this);
} }
public String getDescription(final Locale locale) { public String getDescription(final Locale pLocale) {
return "Windows Device Independent Bitmap Format (BMP) Reader"; return "Windows Device Independent Bitmap Format (BMP) Reader";
} }
} }
@@ -47,7 +47,7 @@ import java.nio.ByteOrder;
* BMPImageWriter * BMPImageWriter
*/ */
public final class BMPImageWriter extends DIBImageWriter { public final class BMPImageWriter extends DIBImageWriter {
BMPImageWriter(ImageWriterSpi provider) { protected BMPImageWriter(ImageWriterSpi provider) {
super(provider); super(provider);
} }
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.AbstractMetadata; import com.twelvemonkeys.imageio.AbstractMetadata;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
@@ -142,7 +141,7 @@ final class BMPMetadata extends AbstractMetadata {
@Override @Override
protected IIOMetadataNode getStandardChromaNode() { protected IIOMetadataNode getStandardChromaNode() {
// NOTE: BMP files may contain a color map, even if true color... // NOTE: BMP files may contain a color map, even if true color...
// Not sure if this is a good idea to expose to the metadata, // Not sure if this is a good idea to expose to the meta data,
// as it might be unexpected... Then again... // as it might be unexpected... Then again...
if (colorMap != null) { if (colorMap != null) {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma"); IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
@@ -29,11 +29,11 @@
*/ */
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.*;
import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
import java.awt.image.BufferedImage;
import java.io.IOException;
/** /**
* Describes a bitmap structure. * Describes a bitmap structure.
* *
@@ -47,9 +47,9 @@ abstract class BitmapDescriptor {
protected BufferedImage image; protected BufferedImage image;
protected BitmapMask mask; protected BitmapMask mask;
public BitmapDescriptor(final DirectoryEntry entry, final DIBHeader header) { public BitmapDescriptor(final DirectoryEntry pEntry, final DIBHeader pHeader) {
this.entry = notNull(entry, "entry"); entry = notNull(pEntry, "entry");;
this.header = notNull(header, "header"); header = notNull(pHeader, "header");
} }
abstract public BufferedImage getImage() throws IOException; abstract public BufferedImage getImage() throws IOException;
@@ -75,7 +75,7 @@ abstract class BitmapDescriptor {
return getClass().getSimpleName() + "[" + entry + ", " + header + "]"; return getClass().getSimpleName() + "[" + entry + ", " + header + "]";
} }
final void setMask(final BitmapMask mask) { public final void setMask(final BitmapMask mask) {
this.mask = mask; this.mask = mask;
} }
@@ -29,7 +29,10 @@
*/ */
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.util.Hashtable; import java.util.Hashtable;
/** /**
@@ -38,13 +41,12 @@ import java.util.Hashtable;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ * @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/ */
final class BitmapIndexed extends BitmapDescriptor { class BitmapIndexed extends BitmapDescriptor {
final int[] bits; protected final int[] bits;
final int[] colors; protected final int[] colors;
public BitmapIndexed(final DirectoryEntry entry, final DIBHeader header) {
super(entry, header);
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
super(pEntry, pHeader);
bits = new int[getWidth() * getHeight()]; bits = new int[getWidth() * getHeight()];
// NOTE: We're adding space for one extra color, for transparency // NOTE: We're adding space for one extra color, for transparency
@@ -57,16 +59,20 @@ final class BitmapIndexed extends BitmapDescriptor {
IndexColorModel icm = createColorModel(); IndexColorModel icm = createColorModel();
// We add cursor hotspot as a property to images created from CUR format. // This is slightly obscure, and should probably be moved..
// This is slightly obscure, and should probably be moved...
Hashtable<String, Object> properties = null; Hashtable<String, Object> properties = null;
if (entry instanceof DirectoryEntry.CUREntry) { if (entry instanceof DirectoryEntry.CUREntry) {
properties = new Hashtable<>(1); properties = new Hashtable<>(1);
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot()); properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
} }
WritableRaster raster = icm.createCompatibleWritableRaster(getWidth(), getHeight()); BufferedImage image = new BufferedImage(
BufferedImage image = new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), properties); icm,
icm.createCompatibleWritableRaster(getWidth(), getHeight()),
icm.isAlphaPremultiplied(), properties
);
WritableRaster raster = image.getRaster();
// Make pixels transparent according to mask // Make pixels transparent according to mask
final int trans = icm.getTransparentPixel(); final int trans = icm.getTransparentPixel();
@@ -99,7 +105,7 @@ final class BitmapIndexed extends BitmapDescriptor {
int index = findTransparentIndexMaybeRemap(this.colors, this.bits); int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
if (index == -1) { if (index == -1) {
// No duplicate found, increase bit count // No duplicate found, increase bitcount
bits++; bits++;
transparent = this.colors.length - 1; transparent = this.colors.length - 1;
} }
@@ -111,8 +117,10 @@ final class BitmapIndexed extends BitmapDescriptor {
} }
// NOTE: Setting hasAlpha to true, makes things work on 1.2 // NOTE: Setting hasAlpha to true, makes things work on 1.2
return new IndexColorModel(bits, colors, this.colors, 0, true, transparent, return new IndexColorModel(
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT); bits, colors, this.colors, 0, true, transparent,
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
);
} }
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) { private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
@@ -30,7 +30,7 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.image.*; import java.awt.image.BufferedImage;
/** /**
@@ -39,17 +39,17 @@ import java.awt.image.*;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ * @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/ */
final class BitmapMask extends BitmapDescriptor { class BitmapMask extends BitmapDescriptor {
final BitmapIndexed bitMask; protected final BitmapIndexed bitMask;
public BitmapMask(final DirectoryEntry parent, final DIBHeader header) { public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
super(parent, header); super(pParent, pHeader);
bitMask = new BitmapIndexed(parent, header); bitMask = new BitmapIndexed(pParent, pHeader);
} }
boolean isTransparent(final int x, final int y) { boolean isTransparent(final int pX, final int pY) {
// NOTE: 1: Fully transparent, 0: Opaque... // NOTE: 1: Fully transparent, 0: Opaque...
return bitMask.bits[x + y * getWidth()] != 0; return bitMask.bits[pX + pY * getWidth()] != 0;
} }
public BufferedImage getImage() { public BufferedImage getImage() {
@@ -31,7 +31,8 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import java.awt.*; import java.awt.*;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
/** /**
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel). * Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
@@ -39,10 +40,10 @@ import java.awt.image.*;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ * @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/ */
final class BitmapRGB extends BitmapDescriptor { class BitmapRGB extends BitmapDescriptor {
public BitmapRGB(final DirectoryEntry entry, final DIBHeader header) { public BitmapRGB(final DirectoryEntry pEntry, final DIBHeader pHeader) {
super(entry, header); super(pEntry, pHeader);
} }
@Override @Override
@@ -70,7 +71,7 @@ final class BitmapRGB extends BitmapDescriptor {
WritableRaster alphaRaster = masked.getAlphaRaster(); WritableRaster alphaRaster = masked.getAlphaRaster();
byte[] trans = {0x00}; byte[] trans = {0x0};
for (int y = 0; y < getHeight(); y++) { for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) { for (int x = 0; x < getWidth(); x++) {
if (mask.isTransparent(x, y)) { if (mask.isTransparent(x, y)) {
@@ -30,10 +30,11 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException; import java.awt.image.BufferedImage;
import java.awt.image.*;
import java.io.IOException; import java.io.IOException;
import javax.imageio.IIOException;
/** /**
* Represents bitmap structures we can't read. * Represents bitmap structures we can't read.
* Allows for deferred exception handling, and allowing clients to read all images that can be read. * Allows for deferred exception handling, and allowing clients to read all images that can be read.
@@ -41,13 +42,13 @@ import java.io.IOException;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$ * @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
*/ */
final class BitmapUnsupported extends BitmapDescriptor { class BitmapUnsupported extends BitmapDescriptor {
private final String message; private String message;
public BitmapUnsupported(final DirectoryEntry entry, DIBHeader header, final String message) { public BitmapUnsupported(final DirectoryEntry pEntry, DIBHeader header, final String pMessage) {
super(entry, header); super(pEntry, header);
this.message = message; message = pMessage;
} }
@Override @Override
@@ -48,22 +48,22 @@ public final class CURImageReader extends DIBImageReader {
super(new CURImageReaderSpi()); super(new CURImageReaderSpi());
} }
CURImageReader(final ImageReaderSpi provider) { protected CURImageReader(final ImageReaderSpi pProvider) {
super(provider); super(pProvider);
} }
/** /**
* Returns the hot spot location for the cursor. * Returns the hot spot location for the cursor.
* *
* @param imageIndex the index of the cursor in the current input. * @param pImageIndex the index of the cursor in the current input.
* @return the hot spot location for the cursor * @return the hot spot location for the cursor
* *
* @throws java.io.IOException if an I/O exception occurs during reading of image meta data * @throws java.io.IOException if an I/O exception occurs during reading of image meta data
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to * @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
* the number of cursors in the file * the number of cursors in the file
*/ */
public Point getHotSpot(final int imageIndex) throws IOException { public final Point getHotSpot(final int pImageIndex) throws IOException {
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(imageIndex); DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(pImageIndex);
return entry.getHotspot(); return entry.getHotspot();
} }
} }
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
@@ -48,15 +49,15 @@ public final class CURImageReaderSpi extends ImageReaderSpiBase {
super(new CURProviderInfo()); super(new CURProviderInfo());
} }
public boolean canDecodeInput(final Object source) throws IOException { public boolean canDecodeInput(final Object pSource) throws IOException {
return source instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) source, DIB.TYPE_CUR); return pSource instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) pSource, DIB.TYPE_CUR);
} }
public CURImageReader createReaderInstance(final Object extension) { public ImageReader createReaderInstance(final Object pExtension) throws IOException {
return new CURImageReader(this); return new CURImageReader(this);
} }
public String getDescription(final Locale locale) { public String getDescription(final Locale pLocale) {
return "Windows Cursor Format (CUR) Reader"; return "Windows Cursor Format (CUR) Reader";
} }
} }
@@ -30,11 +30,12 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException;
import java.io.DataInput; import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
import javax.imageio.IIOException;
/** /**
* Represents the DIB (Device Independent Bitmap) Information header structure. * Represents the DIB (Device Independent Bitmap) Information header structure.
* *
@@ -90,17 +91,17 @@ abstract class DIBHeader {
protected DIBHeader() { protected DIBHeader() {
} }
public static DIBHeader read(final DataInput stream) throws IOException { public static DIBHeader read(final DataInput pStream) throws IOException {
int size = stream.readInt(); int size = pStream.readInt();
DIBHeader header = createHeader(size); DIBHeader header = createHeader(size);
header.read(size, stream); header.read(size, pStream);
return header; return header;
} }
private static DIBHeader createHeader(final int size) throws IOException { private static DIBHeader createHeader(final int pSize) throws IOException {
switch (size) { switch (pSize) {
case DIB.BITMAP_CORE_HEADER_SIZE: case DIB.BITMAP_CORE_HEADER_SIZE:
return new BitmapCoreHeader(); return new BitmapCoreHeader();
case DIB.OS2_V2_HEADER_16_SIZE: case DIB.OS2_V2_HEADER_16_SIZE:
@@ -117,12 +118,11 @@ abstract class DIBHeader {
case DIB.BITMAP_V5_INFO_HEADER_SIZE: case DIB.BITMAP_V5_INFO_HEADER_SIZE:
return new BitmapV5InfoHeader(); return new BitmapV5InfoHeader();
default: default:
throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", size)); throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", pSize));
} }
} }
protected abstract void read(int size, DataInput stream) throws IOException; protected abstract void read(int pSize, DataInput pStream) throws IOException;
protected abstract void write(final DataOutput stream) throws IOException;
public final int getSize() { public final int getSize() {
return size; return size;
@@ -189,12 +189,12 @@ abstract class DIBHeader {
); );
} }
private static int[] readMasks(final DataInput stream, final boolean hasAlphaMask) throws IOException { private static int[] readMasks(final DataInput pStream, final boolean hasAlphaMask) throws IOException {
int maskCount = hasAlphaMask ? 4 : 3; int maskCount = hasAlphaMask ? 4 : 3;
int[] masks = new int[4]; int[] masks = new int[4];
for (int i = 0; i < maskCount; i++) { for (int i = 0; i < maskCount; i++) {
masks[i] = stream.readInt(); masks[i] = pStream.readInt();
} }
return masks; return masks;
@@ -205,30 +205,24 @@ abstract class DIBHeader {
// TODO: Get rid of code duplication below... // TODO: Get rid of code duplication below...
static final class BitmapCoreHeader extends DIBHeader { static final class BitmapCoreHeader extends DIBHeader {
@Override protected void read(final int pSize, final DataInput pStream) throws IOException {
protected void read(final int size, final DataInput stream) throws IOException { if (pSize != DIB.BITMAP_CORE_HEADER_SIZE) {
if (size != DIB.BITMAP_CORE_HEADER_SIZE) { throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_CORE_HEADER_SIZE));
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_CORE_HEADER_SIZE));
} }
this.size = size; size = pSize;
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)! // NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
width = stream.readUnsignedShort(); width = pStream.readUnsignedShort();
height = stream.readShort(); height = pStream.readShort();
if (height < 0) { if (height < 0) {
height = -height; height = -height;
topDown = true; topDown = true;
} }
planes = stream.readUnsignedShort(); planes = pStream.readUnsignedShort();
bitCount = stream.readUnsignedShort(); bitCount = pStream.readUnsignedShort();
}
@Override
protected void write(DataOutput stream) {
throw new UnsupportedOperationException();
} }
public String getBMPVersion() { public String getBMPVersion() {
@@ -248,51 +242,45 @@ abstract class DIBHeader {
*/ */
static final class BitmapCoreHeaderV2 extends DIBHeader { static final class BitmapCoreHeaderV2 extends DIBHeader {
@SuppressWarnings("unused") @SuppressWarnings("unused")
@Override protected void read(final int pSize, final DataInput pStream) throws IOException {
protected void read(final int size, final DataInput stream) throws IOException { if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) {
if (size != DIB.OS2_V2_HEADER_SIZE && size != DIB.OS2_V2_HEADER_16_SIZE) { throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE));
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.OS2_V2_HEADER_SIZE));
} }
this.size = size; size = pSize;
width = stream.readInt(); width = pStream.readInt();
height = stream.readInt(); height = pStream.readInt();
if (height < 0) { if (height < 0) {
height = -height; height = -height;
topDown = true; topDown = true;
} }
planes = stream.readUnsignedShort(); planes = pStream.readUnsignedShort();
bitCount = stream.readUnsignedShort(); bitCount = pStream.readUnsignedShort();
if (size != DIB.OS2_V2_HEADER_16_SIZE) { if (pSize != DIB.OS2_V2_HEADER_16_SIZE) {
compression = stream.readInt(); compression = pStream.readInt();
imageSize = stream.readInt(); imageSize = pStream.readInt();
xPixelsPerMeter = stream.readInt(); xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = stream.readInt(); yPixelsPerMeter = pStream.readInt();
colorsUsed = stream.readInt(); colorsUsed = pStream.readInt();
colorsImportant = stream.readInt(); colorsImportant = pStream.readInt();
} }
// TODO: Use? These fields are not reflected in metadata as per now... // TODO: Use? These fields are not reflected in metadata as per now...
int units = stream.readShort(); int units = pStream.readShort();
int reserved = stream.readShort(); int reserved = pStream.readShort();
int recording = stream.readShort(); // Recording algorithm int recording = pStream.readShort(); // Recording algorithm
int rendering = stream.readShort(); // Halftoning algorithm int rendering = pStream.readShort(); // Halftoning algorithm
int size1 = stream.readInt(); // Reserved for halftoning use int size1 = pStream.readInt(); // Reserved for halftoning use
int size2 = stream.readInt(); // Reserved for halftoning use int size2 = pStream.readInt(); // Reserved for halftoning use
int colorEncoding = stream.readInt(); // Color model used in bitmap int colorEncoding = pStream.readInt(); // Color model used in bitmap
int identifier = stream.readInt(); // Reserved for application use int identifier = pStream.readInt(); // Reserved for application use
}
@Override
protected void write(DataOutput stream) {
throw new UnsupportedOperationException();
} }
public String getBMPVersion() { public String getBMPVersion() {
@@ -300,6 +288,7 @@ abstract class DIBHeader {
} }
} }
/** /**
* Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure. * Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure.
* This is the common format for persistent DIB structures, even if Windows * This is the common format for persistent DIB structures, even if Windows
@@ -315,46 +304,44 @@ abstract class DIBHeader {
* @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>. * @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>.
*/ */
static final class BitmapInfoHeader extends DIBHeader { static final class BitmapInfoHeader extends DIBHeader {
@Override protected void read(final int pSize, final DataInput pStream) throws IOException {
protected void read(final int size, final DataInput stream) throws IOException { if (!(pSize == DIB.BITMAP_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V2_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V3_INFO_HEADER_SIZE)) {
if (!(size == DIB.BITMAP_INFO_HEADER_SIZE || size == DIB.BITMAP_V2_INFO_HEADER_SIZE || size == DIB.BITMAP_V3_INFO_HEADER_SIZE)) { throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_INFO_HEADER_SIZE));
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_INFO_HEADER_SIZE));
} }
this.size = size; size = pSize;
width = stream.readInt(); width = pStream.readInt();
height = stream.readInt(); height = pStream.readInt();
if (height < 0) { if (height < 0) {
height = -height; height = -height;
topDown = true; topDown = true;
} }
planes = stream.readUnsignedShort(); planes = pStream.readUnsignedShort();
bitCount = stream.readUnsignedShort(); bitCount = pStream.readUnsignedShort();
compression = stream.readInt(); compression = pStream.readInt();
imageSize = stream.readInt(); imageSize = pStream.readInt();
xPixelsPerMeter = stream.readInt(); xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = stream.readInt(); yPixelsPerMeter = pStream.readInt();
colorsUsed = stream.readInt(); colorsUsed = pStream.readInt();
colorsImportant = stream.readInt(); colorsImportant = pStream.readInt();
// Read masks if we have V2 or V3 // Read masks if we have V2 or V3
// or if we have compression BITFIELDS or ALPHA_BITFIELDS // or if we have compression BITFIELDS or ALPHA_BITFIELDS
if (this.size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) { if (size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) {
masks = readMasks(stream, false); masks = readMasks(pStream, false);
} }
else if (this.size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) { else if (size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) {
masks = readMasks(stream, true); masks = readMasks(pStream, true);
} }
} }
@Override void write(final DataOutput stream) throws IOException {
protected void write(final DataOutput stream) throws IOException {
stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE); stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE);
stream.writeInt(width); stream.writeInt(width);
@@ -372,7 +359,7 @@ abstract class DIBHeader {
stream.writeInt(colorsUsed); stream.writeInt(colorsUsed);
stream.writeInt(colorsImportant); stream.writeInt(colorsImportant);
// TODO: Write masks, if COMPRESSION_BITFIELDS/COMPRESSION_ALPHA_BITFIELDS // TODO: Write masks, if bitfields
} }
public String getBMPVersion() { public String getBMPVersion() {
@@ -389,160 +376,105 @@ abstract class DIBHeader {
* Represents the BITMAPV4INFOHEADER structure. * Represents the BITMAPV4INFOHEADER structure.
*/ */
static final class BitmapV4InfoHeader extends DIBHeader { static final class BitmapV4InfoHeader extends DIBHeader {
@Override protected void read(final int pSize, final DataInput pStream) throws IOException {
protected void read(final int size, final DataInput stream) throws IOException { if (pSize != DIB.BITMAP_V4_INFO_HEADER_SIZE) {
if (size != DIB.BITMAP_V4_INFO_HEADER_SIZE) { throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V4_INFO_HEADER_SIZE));
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_V4_INFO_HEADER_SIZE));
} }
this.size = size; size = pSize;
width = stream.readInt(); width = pStream.readInt();
height = stream.readInt(); height = pStream.readInt();
if (height < 0) { if (height < 0) {
height = -height; height = -height;
topDown = true; topDown = true;
} }
planes = stream.readUnsignedShort(); planes = pStream.readUnsignedShort();
bitCount = stream.readUnsignedShort(); bitCount = pStream.readUnsignedShort();
compression = stream.readInt(); compression = pStream.readInt();
imageSize = stream.readInt(); imageSize = pStream.readInt();
xPixelsPerMeter = stream.readInt(); xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = stream.readInt(); yPixelsPerMeter = pStream.readInt();
colorsUsed = stream.readInt(); colorsUsed = pStream.readInt();
colorsImportant = stream.readInt(); colorsImportant = pStream.readInt();
masks = readMasks(stream, true); masks = readMasks(pStream, true);
colorSpaceType = stream.readInt(); // Should be 0 for V4 colorSpaceType = pStream.readInt(); // Should be 0 for V4
cieXYZEndpoints = new double[9]; cieXYZEndpoints = new double[9];
for (int i = 0; i < cieXYZEndpoints.length; i++) { for (int i = 0; i < cieXYZEndpoints.length; i++) {
cieXYZEndpoints[i] = stream.readInt(); // TODO: Hmmm...? cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
} }
gamma = new int[3]; gamma = new int[3];
for (int i = 0; i < gamma.length; i++) { for (int i = 0; i < gamma.length; i++) {
gamma[i] = stream.readInt(); gamma[i] = pStream.readInt();
} }
} }
public String getBMPVersion() { public String getBMPVersion() {
return "BMP v. 4.x"; return "BMP v. 4.x";
} }
@Override
protected void write(DataOutput stream) throws IOException {
stream.writeInt(DIB.BITMAP_V4_INFO_HEADER_SIZE);
stream.writeInt(width);
stream.writeInt(topDown ? -height : height);
stream.writeShort(planes);
stream.writeShort(bitCount);
stream.writeInt(compression);
stream.writeInt(imageSize);
stream.writeInt(xPixelsPerMeter);
stream.writeInt(yPixelsPerMeter);
stream.writeInt(colorsUsed);
stream.writeInt(colorsImportant);
// Red, Green, Blue, Alpha masks
stream.writeInt(masks[0]);
stream.writeInt(masks[1]);
stream.writeInt(masks[2]);
stream.writeInt(masks[3]);
// color space ("sRGB" LITTLE endian)
stream.writeInt(DIB.LCS_sRGB);
// 36 bytes CIE XYZ triples, unused for sRGB
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
// Red gamma, unused for sRGB
// Green gamma, unused for sRGB
// Blue gamma, unused for sRGB
stream.writeInt(0);
stream.writeInt(0);
stream.writeInt(0);
}
} }
/** /**
* Represents the BITMAPV5INFOHEADER structure. * Represents the BITMAPV5INFOHEADER structure.
*/ */
static final class BitmapV5InfoHeader extends DIBHeader { static final class BitmapV5InfoHeader extends DIBHeader {
protected void read(final int size, final DataInput stream) throws IOException { protected void read(final int pSize, final DataInput pStream) throws IOException {
if (size != DIB.BITMAP_V5_INFO_HEADER_SIZE) { if (pSize != DIB.BITMAP_V5_INFO_HEADER_SIZE) {
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_V5_INFO_HEADER_SIZE)); throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V5_INFO_HEADER_SIZE));
} }
this.size = size; size = pSize;
width = stream.readInt(); width = pStream.readInt();
height = stream.readInt(); height = pStream.readInt();
if (height < 0) { if (height < 0) {
height = -height; height = -height;
topDown = true; topDown = true;
} }
planes = stream.readUnsignedShort(); planes = pStream.readUnsignedShort();
bitCount = stream.readUnsignedShort(); bitCount = pStream.readUnsignedShort();
compression = stream.readInt(); compression = pStream.readInt();
imageSize = stream.readInt(); imageSize = pStream.readInt();
xPixelsPerMeter = stream.readInt(); xPixelsPerMeter = pStream.readInt();
yPixelsPerMeter = stream.readInt(); yPixelsPerMeter = pStream.readInt();
colorsUsed = stream.readInt(); colorsUsed = pStream.readInt();
colorsImportant = stream.readInt(); colorsImportant = pStream.readInt();
masks = readMasks(stream, true); masks = readMasks(pStream, true);
colorSpaceType = stream.readInt(); colorSpaceType = pStream.readInt();
cieXYZEndpoints = new double[9]; cieXYZEndpoints = new double[9];
for (int i = 0; i < cieXYZEndpoints.length; i++) { for (int i = 0; i < cieXYZEndpoints.length; i++) {
cieXYZEndpoints[i] = stream.readInt(); // TODO: Hmmm...? cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
} }
gamma = new int[3]; gamma = new int[3];
for (int i = 0; i < gamma.length; i++) { for (int i = 0; i < gamma.length; i++) {
gamma[i] = stream.readInt(); gamma[i] = pStream.readInt();
} }
intent = stream.readInt(); // TODO: Verify if this is same as ICC intent intent = pStream.readInt(); // TODO: Verify if this is same as ICC intent
profileData = stream.readInt() & 0xffffffffL; profileData = pStream.readInt() & 0xffffffffL;
profileSize = stream.readInt() & 0xffffffffL; profileSize = pStream.readInt() & 0xffffffffL;
stream.readInt(); // Reserved pStream.readInt(); // Reserved
}
@Override
protected void write(DataOutput stream) {
throw new UnsupportedOperationException();
} }
public String getBMPVersion() { public String getBMPVersion() {
@@ -30,23 +30,10 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.util.WeakWeakMap;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.event.*; import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.*; import java.awt.image.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -57,6 +44,21 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.WeakHashMap; import java.util.WeakHashMap;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.swing.*;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.util.WeakWeakMap;
/** /**
* ImageReader for Microsoft Windows ICO (icon) format. * ImageReader for Microsoft Windows ICO (icon) format.
* 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit * 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit
@@ -79,13 +81,13 @@ abstract class DIBImageReader extends ImageReaderBase {
private Directory directory; private Directory directory;
// TODO: Review these, make sure we don't have a memory leak // TODO: Review these, make sure we don't have a memory leak
private final Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>(); private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
private final Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>(); private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
private ImageReader pngImageReader; private ImageReader pngImageReader;
protected DIBImageReader(final ImageReaderSpi provider) { protected DIBImageReader(final ImageReaderSpi pProvider) {
super(provider); super(pProvider);
} }
protected void resetMembers() { protected void resetMembers() {
@@ -100,8 +102,8 @@ abstract class DIBImageReader extends ImageReaderBase {
} }
} }
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException { public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
DirectoryEntry entry = getEntry(imageIndex); DirectoryEntry entry = getEntry(pImageIndex);
// NOTE: Delegate to PNG reader // NOTE: Delegate to PNG reader
if (isPNG(entry)) { if (isPNG(entry)) {
@@ -153,39 +155,39 @@ abstract class DIBImageReader extends ImageReaderBase {
return getDirectory().count(); return getDirectory().count();
} }
public int getWidth(final int imageIndex) throws IOException { public int getWidth(final int pImageIndex) throws IOException {
return getEntry(imageIndex).getWidth(); return getEntry(pImageIndex).getWidth();
} }
public int getHeight(final int imageIndex) throws IOException { public int getHeight(final int pImageIndex) throws IOException {
return getEntry(imageIndex).getHeight(); return getEntry(pImageIndex).getHeight();
} }
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException { public BufferedImage read(final int pImageIndex, final ImageReadParam pParam) throws IOException {
checkBounds(imageIndex); checkBounds(pImageIndex);
processImageStarted(imageIndex); processImageStarted(pImageIndex);
DirectoryEntry entry = getEntry(imageIndex); DirectoryEntry entry = getEntry(pImageIndex);
BufferedImage destination; BufferedImage destination;
if (isPNG(entry)) { if (isPNG(entry)) {
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header... // NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
destination = readPNG(entry, param); destination = readPNG(entry, pParam);
} }
else { else {
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later, // NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
// to allow for storing the cursor hotspot for CUR images // to allow for storing the cursor hotspot for CUR images
destination = hasExplicitDestination(param) ? destination = hasExplicitDestination(pParam) ?
getDestination(param, getImageTypes(imageIndex), getWidth(imageIndex), getHeight(imageIndex)) : null; getDestination(pParam, getImageTypes(pImageIndex), getWidth(pImageIndex), getHeight(pImageIndex)) : null;
BufferedImage image = readBitmap(entry); BufferedImage image = readBitmap(entry);
// TODO: Handle AOI and subsampling inline, probably not of big importance... // TODO: Handle AOI and subsampling inline, probably not of big importance...
if (param != null) { if (pParam != null) {
image = fakeAOI(image, param); image = fakeAOI(image, pParam);
image = ImageUtil.toBuffered(fakeSubsampling(image, param)); image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
} }
if (destination == null) { if (destination == null) {
@@ -211,10 +213,10 @@ abstract class DIBImageReader extends ImageReaderBase {
return destination; return destination;
} }
private boolean isPNG(final DirectoryEntry entry) throws IOException { private boolean isPNG(final DirectoryEntry pEntry) throws IOException {
long magic; long magic;
imageInput.seek(entry.getOffset()); imageInput.seek(pEntry.getOffset());
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN); imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
try { try {
@@ -227,20 +229,22 @@ abstract class DIBImageReader extends ImageReaderBase {
return magic == DIB.PNG_MAGIC; return magic == DIB.PNG_MAGIC;
} }
private BufferedImage readPNG(final DirectoryEntry entry, final ImageReadParam param) throws IOException { private BufferedImage readPNG(final DirectoryEntry pEntry, final ImageReadParam pParam) throws IOException {
// TODO: Consider delegating listener calls // TODO: Consider delegating listener calls
return initPNGReader(entry).read(0, param); return initPNGReader(pEntry).read(0, pParam);
} }
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry entry) throws IOException { private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry pEntry) throws IOException {
return initPNGReader(entry).getImageTypes(0); return initPNGReader(pEntry).getImageTypes(0);
} }
private ImageReader initPNGReader(final DirectoryEntry entry) throws IOException { private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException {
ImageReader pngReader = getPNGReader(); ImageReader pngReader = getPNGReader();
imageInput.seek(entry.getOffset()); imageInput.seek(pEntry.getOffset());
ImageInputStream stream = new SubImageInputStream(imageInput, entry.getSize()); // InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize());
// ImageInputStream stream = ImageIO.createImageInputStream(inputStream);
ImageInputStream stream = new SubImageInputStream(imageInput, pEntry.getSize());
// NOTE: Will throw IOException on later reads if input is not PNG // NOTE: Will throw IOException on later reads if input is not PNG
pngReader.setInput(stream); pngReader.setInput(stream);
@@ -267,31 +271,31 @@ abstract class DIBImageReader extends ImageReaderBase {
return pngImageReader; return pngImageReader;
} }
private DIBHeader getHeader(final DirectoryEntry entry) throws IOException { private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException {
if (!headers.containsKey(entry)) { if (!headers.containsKey(pEntry)) {
imageInput.seek(entry.getOffset()); imageInput.seek(pEntry.getOffset());
DIBHeader header = DIBHeader.read(imageInput); DIBHeader header = DIBHeader.read(imageInput);
headers.put(entry, header); headers.put(pEntry, header);
} }
return headers.get(entry); return headers.get(pEntry);
} }
private BufferedImage readBitmap(final DirectoryEntry entry) throws IOException { private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException {
// TODO: Get rid of the caching, as the images are mutable // TODO: Get rid of the caching, as the images are mutable
BitmapDescriptor descriptor = descriptors.get(entry); BitmapDescriptor descriptor = descriptors.get(pEntry);
if (descriptor == null || !descriptors.containsKey(entry)) { if (descriptor == null || !descriptors.containsKey(pEntry)) {
DIBHeader header = getHeader(entry); DIBHeader header = getHeader(pEntry);
int offset = entry.getOffset() + header.getSize(); int offset = pEntry.getOffset() + header.getSize();
if (offset != imageInput.getStreamPosition()) { if (offset != imageInput.getStreamPosition()) {
imageInput.seek(offset); imageInput.seek(offset);
} }
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8 // TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
if (header.getCompression() != DIB.COMPRESSION_RGB) { if (header.getCompression() != DIB.COMPRESSION_RGB) {
descriptor = new BitmapUnsupported(entry, header, String.format("Unsupported compression: %d", header.getCompression())); descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported compression: %d", header.getCompression()));
} }
else { else {
int bitCount = header.getBitCount(); int bitCount = header.getBitCount();
@@ -301,75 +305,75 @@ abstract class DIBImageReader extends ImageReaderBase {
case 1: case 1:
case 4: case 4:
case 8: // TODO: Gray! case 8: // TODO: Gray!
descriptor = new BitmapIndexed(entry, header); descriptor = new BitmapIndexed(pEntry, header);
readBitmapIndexed((BitmapIndexed) descriptor); readBitmapIndexed((BitmapIndexed) descriptor);
break; break;
// RGB style // RGB style
case 16: case 16:
descriptor = new BitmapRGB(entry, header); descriptor = new BitmapRGB(pEntry, header);
readBitmap16(descriptor); readBitmap16(descriptor);
break; break;
case 24: case 24:
descriptor = new BitmapRGB(entry, header); descriptor = new BitmapRGB(pEntry, header);
readBitmap24(descriptor); readBitmap24(descriptor);
break; break;
case 32: case 32:
descriptor = new BitmapRGB(entry, header); descriptor = new BitmapRGB(pEntry, header);
readBitmap32(descriptor); readBitmap32(descriptor);
break; break;
default: default:
descriptor = new BitmapUnsupported(entry, header, String.format("Unsupported bit count %d", bitCount)); descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported bit count %d", bitCount));
} }
} }
descriptors.put(entry, descriptor); descriptors.put(pEntry, descriptor);
} }
return descriptor.getImage(); return descriptor.getImage();
} }
private void readBitmapIndexed(final BitmapIndexed bitmap) throws IOException { private void readBitmapIndexed(final BitmapIndexed pBitmap) throws IOException {
readColorMap(bitmap); readColorMap(pBitmap);
switch (bitmap.getBitCount()) { switch (pBitmap.getBitCount()) {
case 1: case 1:
readBitmapIndexed1(bitmap, false); readBitmapIndexed1(pBitmap, false);
break; break;
case 4: case 4:
readBitmapIndexed4(bitmap); readBitmapIndexed4(pBitmap);
break; break;
case 8: case 8:
readBitmapIndexed8(bitmap); readBitmapIndexed8(pBitmap);
break; break;
} }
BitmapMask mask = new BitmapMask(bitmap.entry, bitmap.header); BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
readBitmapIndexed1(mask.bitMask, true); readBitmapIndexed1(mask.bitMask, true);
bitmap.setMask(mask); pBitmap.setMask(mask);
} }
private void readColorMap(final BitmapIndexed bitmap) throws IOException { private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
int colorCount = bitmap.getColorCount(); int colorCount = pBitmap.getColorCount();
for (int i = 0; i < colorCount; i++) { for (int i = 0; i < colorCount; i++) {
// aRGB (a is "Reserved") // aRGB (a is "Reserved")
bitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000; pBitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
} }
} }
private void readBitmapIndexed1(final BitmapIndexed bitmap, final boolean asMask) throws IOException { private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
int width = adjustToPadding((bitmap.getWidth() + 7) >> 3); int width = adjustToPadding((pBitmap.getWidth() + 7) >> 3);
byte[] row = new byte[width]; byte[] row = new byte[width];
for (int y = 0; y < bitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
imageInput.readFully(row, 0, width); imageInput.readFully(row, 0, width);
int rowPos = 0; int rowPos = 0;
int xOrVal = 0x80; int xOrVal = 0x80;
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth(); int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
for (int x = 0; x < bitmap.getWidth(); x++) { for (int x = 0; x < pBitmap.getWidth(); x++) {
bitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF; pBitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
if (xOrVal == 1) { if (xOrVal == 1) {
xOrVal = 0x80; xOrVal = 0x80;
@@ -380,29 +384,29 @@ abstract class DIBImageReader extends ImageReaderBase {
} }
} }
// NOTE: If we are reading the mask, we can't abort or report progress // NOTE: If we are reading the mask, we don't abort or report progress
if (!asMask) { if (!pAsMask) {
if (abortRequested()) { if (abortRequested()) {
processReadAborted(); processReadAborted();
break; break;
} }
processImageProgress(100 * y / (float) bitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
} }
} }
private void readBitmapIndexed4(final BitmapIndexed bitmap) throws IOException { private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
int width = adjustToPadding((bitmap.getWidth() + 1) >> 1); int width = adjustToPadding((pBitmap.getWidth() + 1) >> 1);
byte[] row = new byte[width]; byte[] row = new byte[width];
for (int y = 0; y < bitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
imageInput.readFully(row, 0, width); imageInput.readFully(row, 0, width);
int rowPos = 0; int rowPos = 0;
boolean high4 = true; boolean high4 = true;
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth(); int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
for (int x = 0; x < bitmap.getWidth(); x++) { for (int x = 0; x < pBitmap.getWidth(); x++) {
int value; int value;
if (high4) { if (high4) {
@@ -413,7 +417,7 @@ abstract class DIBImageReader extends ImageReaderBase {
rowPos++; rowPos++;
} }
bitmap.bits[pos++] = value & 0xFF; pBitmap.bits[pos++] = value & 0xFF;
high4 = !high4; high4 = !high4;
} }
@@ -422,22 +426,22 @@ abstract class DIBImageReader extends ImageReaderBase {
break; break;
} }
processImageProgress(100 * y / (float) bitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
} }
private void readBitmapIndexed8(final BitmapIndexed bitmap) throws IOException { private void readBitmapIndexed8(final BitmapIndexed pBitmap) throws IOException {
int width = adjustToPadding(bitmap.getWidth()); int width = adjustToPadding(pBitmap.getWidth());
byte[] row = new byte[width]; byte[] row = new byte[width];
for (int y = 0; y < bitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
imageInput.readFully(row, 0, width); imageInput.readFully(row, 0, width);
int rowPos = 0; int rowPos = 0;
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth(); int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
for (int x = 0; x < bitmap.getWidth(); x++) { for (int x = 0; x < pBitmap.getWidth(); x++) {
bitmap.bits[pos++] = row[rowPos++] & 0xFF; pBitmap.bits[pos++] = row[rowPos++] & 0xFF;
} }
if (abortRequested()) { if (abortRequested()) {
@@ -445,41 +449,40 @@ abstract class DIBImageReader extends ImageReaderBase {
break; break;
} }
processImageProgress(100 * y / (float) bitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
} }
/** /**
* @param width Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 2) * @param pWidth Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 1)
* @return padded width * @return padded width
*/ */
private static int adjustToPadding(final int width) { private static int adjustToPadding(final int pWidth) {
if ((width & 0x03) != 0) { if ((pWidth & 0x03) != 0) {
return (width & ~0x03) + 4; return (pWidth & ~0x03) + 4;
}
return pWidth;
} }
return width; private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
} short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
private void readBitmap16(final BitmapDescriptor bitmap) throws IOException {
short[] pixels = new short[bitmap.getWidth() * bitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts // TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
// Will create TYPE_USHORT_555 // Will create TYPE_USHORT_555
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F); DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new DataBufferUShort(pixels, pixels.length); DataBuffer buffer = new DataBufferUShort(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster( WritableRaster raster = Raster.createPackedRaster(
buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth(), cm.getMasks(), null buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
); );
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < bitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (bitmap.getHeight() - y - 1) * bitmap.getWidth(); int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
imageInput.readFully(pixels, offset, bitmap.getWidth()); imageInput.readFully(pixels, offset, pBitmap.getWidth());
// Skip to 32 bit boundary // Skip to 32 bit boundary
if (bitmap.getWidth() % 2 != 0) { if (pBitmap.getWidth() % 2 != 0) {
imageInput.readShort(); imageInput.readShort();
} }
@@ -488,14 +491,14 @@ abstract class DIBImageReader extends ImageReaderBase {
break; break;
} }
processImageProgress(100 * y / (float) bitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
// TODO: Might be mask!? // TODO: Might be mask!?
} }
private void readBitmap24(final BitmapDescriptor bitmap) throws IOException { private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
byte[] pixels = new byte[bitmap.getWidth() * bitmap.getHeight() * 3]; byte[] pixels = new byte[pBitmap.getWidth() * pBitmap.getHeight() * 3];
// Create TYPE_3BYTE_BGR // Create TYPE_3BYTE_BGR
DataBuffer buffer = new DataBufferByte(pixels, pixels.length); DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
@@ -506,17 +509,17 @@ abstract class DIBImageReader extends ImageReaderBase {
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
); );
int scanlineStride = bitmap.getWidth() * 3; int scanlineStride = pBitmap.getWidth() * 3;
// BMP rows are padded to 4 byte boundary // BMP rows are padded to 4 byte boundary
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4; int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
WritableRaster raster = Raster.createInterleavedRaster( WritableRaster raster = Raster.createInterleavedRaster(
buffer, bitmap.getWidth(), bitmap.getHeight(), scanlineStride, 3, bOffs, null buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
); );
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < bitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (bitmap.getHeight() - y - 1) * scanlineStride; int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
imageInput.readFully(pixels, offset, scanlineStride); imageInput.readFully(pixels, offset, scanlineStride);
imageInput.skipBytes(rowSizeBytes - scanlineStride); imageInput.skipBytes(rowSizeBytes - scanlineStride);
@@ -525,38 +528,38 @@ abstract class DIBImageReader extends ImageReaderBase {
break; break;
} }
processImageProgress(100 * y / (float) bitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
// 24 bit icons usually have a bit mask // 24 bit icons usually have a bit mask
if (bitmap.hasMask()) { if (pBitmap.hasMask()) {
BitmapMask mask = new BitmapMask(bitmap.entry, bitmap.header); BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
readBitmapIndexed1(mask.bitMask, true); readBitmapIndexed1(mask.bitMask, true);
bitmap.setMask(mask); pBitmap.setMask(mask);
} }
} }
private void readBitmap32(final BitmapDescriptor bitmap) throws IOException { private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; int[] pixels = new int[pBitmap.getWidth() * pBitmap.getHeight()];
// Will create TYPE_INT_ARGB // Will create TYPE_INT_ARGB
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault(); DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
DataBuffer buffer = new DataBufferInt(pixels, pixels.length); DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
WritableRaster raster = Raster.createPackedRaster( WritableRaster raster = Raster.createPackedRaster(
buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth(), cm.getMasks(), null buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
); );
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null); pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
for (int y = 0; y < bitmap.getHeight(); y++) { for (int y = 0; y < pBitmap.getHeight(); y++) {
int offset = (bitmap.getHeight() - y - 1) * bitmap.getWidth(); int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
imageInput.readFully(pixels, offset, bitmap.getWidth()); imageInput.readFully(pixels, offset, pBitmap.getWidth());
if (abortRequested()) { if (abortRequested()) {
processReadAborted(); processReadAborted();
break; break;
} }
processImageProgress(100 * y / (float) bitmap.getHeight()); processImageProgress(100 * y / (float) pBitmap.getHeight());
} }
// There might be a mask here as well, but we'll ignore it, // There might be a mask here as well, but we'll ignore it,
@@ -587,18 +590,18 @@ abstract class DIBImageReader extends ImageReaderBase {
directory = Directory.read(type, imageCount, imageInput); directory = Directory.read(type, imageCount, imageInput);
} }
final DirectoryEntry getEntry(final int imageIndex) throws IOException { final DirectoryEntry getEntry(final int pImageIndex) throws IOException {
Directory directory = getDirectory(); Directory directory = getDirectory();
if (imageIndex < 0 || imageIndex >= directory.count()) { if (pImageIndex < 0 || pImageIndex >= directory.count()) {
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", imageIndex, directory.count())); throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", pImageIndex, directory.count()));
} }
return directory.getEntry(imageIndex); return directory.getEntry(pImageIndex);
} }
/// Test code below, ignore.. :-) /// Test code below, ignore.. :-)
public static void main(final String[] args) throws IOException { public static void main(final String[] pArgs) throws IOException {
if (args.length == 0) { if (pArgs.length == 0) {
System.err.println("Please specify the icon file name"); System.err.println("Please specify the icon file name");
System.exit(1); System.exit(1);
} }
@@ -610,7 +613,7 @@ abstract class DIBImageReader extends ImageReaderBase {
// Ignore // Ignore
} }
String title = new File(args[0]).getName(); String title = new File(pArgs[0]).getName();
JFrame frame = createWindow(title); JFrame frame = createWindow(title);
JPanel root = new JPanel(new FlowLayout()); JPanel root = new JPanel(new FlowLayout());
JScrollPane scroll = JScrollPane scroll =
@@ -626,7 +629,7 @@ abstract class DIBImageReader extends ImageReaderBase {
ImageReader reader = readers.next(); ImageReader reader = readers.next();
for (String arg : args) { for (String arg : pArgs) {
JPanel panel = new JPanel(null); JPanel panel = new JPanel(null);
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
readImagesInFile(arg, reader, panel); readImagesInFile(arg, reader, panel);
@@ -637,28 +640,28 @@ abstract class DIBImageReader extends ImageReaderBase {
frame.setVisible(true); frame.setVisible(true);
} }
private static void readImagesInFile(String fileName, ImageReader reader, final Container container) throws IOException { private static void readImagesInFile(String pFileName, ImageReader pReader, final Container pContainer) throws IOException {
File file = new File(fileName); File file = new File(pFileName);
if (!file.isFile()) { if (!file.isFile()) {
System.err.println(fileName + " not found, or is no file"); System.err.println(pFileName + " not found, or is no file");
} }
reader.setInput(ImageIO.createImageInputStream(file)); pReader.setInput(ImageIO.createImageInputStream(file));
int imageCount = reader.getNumImages(true); int imageCount = pReader.getNumImages(true);
for (int i = 0; i < imageCount; i++) { for (int i = 0; i < imageCount; i++) {
try { try {
addImage(container, reader, i); addImage(pContainer, pReader, i);
} }
catch (Exception e) { catch (Exception e) {
System.err.println("FileName: " + fileName); System.err.println("FileName: " + pFileName);
System.err.println("Icon: " + i); System.err.println("Icon: " + i);
e.printStackTrace(); e.printStackTrace();
} }
} }
} }
private static JFrame createWindow(final String title) { private static JFrame createWindow(final String pTitle) {
JFrame frame = new JFrame(title); JFrame frame = new JFrame(pTitle);
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() { frame.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) { public void windowClosed(WindowEvent e) {
@@ -668,15 +671,15 @@ abstract class DIBImageReader extends ImageReaderBase {
return frame; return frame;
} }
private static void addImage(final Container parent, final ImageReader reader, final int imageNo) throws IOException { private static void addImage(final Container pParent, final ImageReader pReader, final int pImageNo) throws IOException {
final JButton button = new JButton(); final JButton button = new JButton();
BufferedImage image = reader.read(imageNo); BufferedImage image = pReader.read(pImageNo);
button.setIcon(new ImageIcon(image) { button.setIcon(new ImageIcon(image) {
TexturePaint texture; TexturePaint texture;
private void createTexture(final GraphicsConfiguration graphicsConfiguration) { private void createTexture(final GraphicsConfiguration pGraphicsConfiguration) {
BufferedImage pattern = graphicsConfiguration.createCompatibleImage(20, 20); BufferedImage pattern = pGraphicsConfiguration.createCompatibleImage(20, 20);
Graphics2D g = pattern.createGraphics(); Graphics2D g = pattern.createGraphics();
try { try {
g.setColor(Color.LIGHT_GRAY); g.setColor(Color.LIGHT_GRAY);
@@ -711,6 +714,6 @@ abstract class DIBImageReader extends ImageReaderBase {
String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) : String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
"TrueColor")); "TrueColor"));
parent.add(button); pParent.add(button);
} }
} }
@@ -44,25 +44,24 @@ import java.util.List;
class Directory { class Directory {
private final List<DirectoryEntry> entries; private final List<DirectoryEntry> entries;
private Directory(int imageCount) { private Directory(int pImageCount) {
entries = Arrays.asList(new DirectoryEntry[imageCount]); entries = Arrays.asList(new DirectoryEntry[pImageCount]);
} }
public static Directory read(final int type, final int imageCount, final DataInput stream) throws IOException { public static Directory read(final int pType, final int pImageCount, final DataInput pStream) throws IOException {
Directory directory = new Directory(imageCount); Directory directory = new Directory(pImageCount);
directory.readEntries(type, stream); directory.readEntries(pType, pStream);
return directory; return directory;
} }
private void readEntries(final int type, final DataInput stream) throws IOException { private void readEntries(final int pType, final DataInput pStream) throws IOException {
for (int i = 0; i < entries.size(); i++) { for (int i = 0; i < entries.size(); i++) {
entries.set(i, DirectoryEntry.read(type, stream)); entries.set(i, DirectoryEntry.read(pType, pStream));
} }
} }
public DirectoryEntry getEntry(final int entryIndex) { public DirectoryEntry getEntry(final int pEntryIndex) {
return entries.get(entryIndex); return entries.get(pEntryIndex);
} }
public int count() { public int count() {
@@ -32,7 +32,8 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import java.awt.*; import java.awt.*;
import java.awt.image.*; import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.io.DataInput; import java.io.DataInput;
import java.io.DataOutput; import java.io.DataOutput;
import java.io.IOException; import java.io.IOException;
@@ -57,43 +58,47 @@ abstract class DirectoryEntry {
DirectoryEntry() { DirectoryEntry() {
} }
public static DirectoryEntry read(final int type, final DataInput stream) throws IOException { public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException {
DirectoryEntry entry = createEntry(type); DirectoryEntry entry = createEntry(pType);
entry.read(stream); entry.read(pStream);
return entry; return entry;
} }
private static DirectoryEntry createEntry(int type) throws IIOException { private static DirectoryEntry createEntry(int pType) throws IIOException {
switch (type) { switch (pType) {
case DIB.TYPE_ICO: case DIB.TYPE_ICO:
return new ICOEntry(); return new ICOEntry();
case DIB.TYPE_CUR: case DIB.TYPE_CUR:
return new CUREntry(); return new CUREntry();
default: default:
throw new IIOException(String.format("Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)", type, DIB.TYPE_ICO, DIB.TYPE_CUR)); throw new IIOException(
String.format(
"Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)",
pType, DIB.TYPE_ICO, DIB.TYPE_CUR
)
);
} }
} }
protected void read(final DataInput stream) throws IOException { protected void read(final DataInput pStream) throws IOException {
// Width/height = 0, means 256 // Width/height = 0, means 256
int w = stream.readUnsignedByte(); int w = pStream.readUnsignedByte();
width = w == 0 ? 256 : w; width = w == 0 ? 256 : w;
int h = stream.readUnsignedByte(); int h = pStream.readUnsignedByte();
height = h == 0 ? 256 : h; height = h == 0 ? 256 : h;
// Color count = 0, means 256 or more colors // Color count = 0, means 256 or more colors
colorCount = stream.readUnsignedByte(); colorCount = pStream.readUnsignedByte();
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia // Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
stream.readUnsignedByte(); pStream.readUnsignedByte();
planes = stream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR planes = pStream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
bitCount = stream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR bitCount = pStream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
// Size of bitmap in bytes // Size of bitmap in bytes
size = stream.readInt(); size = pStream.readInt();
offset = stream.readInt(); offset = pStream.readInt();
} }
void write(final DataOutput output) throws IOException { void write(final DataOutput output) throws IOException {
@@ -152,8 +157,8 @@ abstract class DirectoryEntry {
private int yHotspot; private int yHotspot;
@Override @Override
protected void read(final DataInput stream) throws IOException { protected void read(final DataInput pStream) throws IOException {
super.read(stream); super.read(pStream);
// NOTE: This is a hack... // NOTE: This is a hack...
xHotspot = planes; xHotspot = planes;
@@ -46,7 +46,7 @@ public final class ICOImageReader extends DIBImageReader {
super(new ICOImageReaderSpi()); super(new ICOImageReaderSpi());
} }
ICOImageReader(final ImageReaderSpi provider) { protected ICOImageReader(final ImageReaderSpi pProvider) {
super(provider); super(pProvider);
} }
} }
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase; import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Locale; import java.util.Locale;
@@ -48,32 +49,32 @@ public final class ICOImageReaderSpi extends ImageReaderSpiBase {
super(new ICOProviderInfo()); super(new ICOProviderInfo());
} }
public boolean canDecodeInput(final Object source) throws IOException { public boolean canDecodeInput(final Object pSource) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source, DIB.TYPE_ICO); return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource, DIB.TYPE_ICO);
} }
static boolean canDecode(final ImageInputStream input, final int type) throws IOException { static boolean canDecode(final ImageInputStream pInput, final int pType) throws IOException {
byte[] signature = new byte[4]; byte[] signature = new byte[4];
try { try {
input.mark(); pInput.mark();
input.readFully(signature); pInput.readFully(signature);
int count = input.readByte() + (input.readByte() << 8); int count = pInput.readByte() + (pInput.readByte() << 8);
return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == type return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == pType
&& signature[3] == 0x0 && count > 0); && signature[3] == 0x0 && count > 0);
} }
finally { finally {
input.reset(); pInput.reset();
} }
} }
public ICOImageReader createReaderInstance(final Object extension) { public ImageReader createReaderInstance(final Object pExtension) throws IOException {
return new ICOImageReader(this); return new ICOImageReader(this);
} }
public String getDescription(final Locale locale) { public String getDescription(final Locale pLocale) {
return "Windows Icon Format (ICO) Reader"; return "Windows Icon Format (ICO) Reader";
} }
} }
@@ -33,19 +33,15 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream; import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import javax.imageio.IIOException; import javax.imageio.*;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Iterator; import java.util.Iterator;
@@ -68,7 +64,7 @@ public final class ICOImageWriter extends DIBImageWriter {
private ImageWriter pngDelegate; private ImageWriter pngDelegate;
ICOImageWriter(final ImageWriterSpi provider) { protected ICOImageWriter(final ImageWriterSpi provider) {
super(provider); super(provider);
} }
@@ -128,7 +124,7 @@ public final class ICOImageWriter extends DIBImageWriter {
} }
@Override @Override
public void endWriteSequence() { public void endWriteSequence() throws IOException {
assertOutput(); assertOutput();
if (sequenceIndex < 0) { if (sequenceIndex < 0) {
@@ -30,14 +30,24 @@
package com.twelvemonkeys.imageio.plugins.bmp; package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest; import static org.junit.Assert.*;
import com.twelvemonkeys.xml.XMLSerializer; import static org.junit.Assume.assumeNoException;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import org.junit.Ignore; import java.awt.*;
import org.junit.Test; import java.awt.image.BufferedImage;
import org.mockito.InOrder; import java.io.ByteArrayOutputStream;
import org.w3c.dom.Node; import java.io.IOException;
import org.w3c.dom.NodeList; import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
@@ -47,25 +57,16 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.event.IIOReadProgressListener; import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.*; import org.junit.Ignore;
import static org.junit.Assume.assumeNoException; import org.junit.Test;
import static org.mockito.ArgumentMatchers.anyFloat; import org.mockito.InOrder;
import static org.mockito.ArgumentMatchers.eq; import org.w3c.dom.Node;
import static org.mockito.Mockito.atLeastOnce; import org.w3c.dom.NodeList;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock; import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import com.twelvemonkeys.xml.XMLSerializer;
/** /**
* BMPImageReaderTest * BMPImageReaderTest
@@ -325,8 +326,10 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
public void testMetadataEqualsJRE() throws IOException { public void testMetadataEqualsJRE() throws IOException {
ImageReader jreReader; ImageReader jreReader;
try { try {
ImageReaderSpi provider = (ImageReaderSpi) IIORegistry.getDefaultInstance().getServiceProviderByClass(Class.forName("com.sun.imageio.plugins.bmp.BMPImageReaderSpi")); @SuppressWarnings("unchecked")
jreReader = provider.createReaderInstance(); Class<ImageReader> jreReaderClass = (Class<ImageReader>) Class.forName("com.sun.imageio.plugins.bmp.BMPImageReader");
Constructor<ImageReader> constructor = jreReaderClass.getConstructor(ImageReaderSpi.class);
jreReader = constructor.newInstance(new Object[] {null});
} }
catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
+1 -10
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.10.2-SNAPSHOT</version> <version>3.8.4-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>
@@ -32,13 +32,4 @@
<artifactId>imageio-metadata</artifactId> <artifactId>imageio-metadata</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>
</project> </project>
@@ -42,7 +42,6 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.*; import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
@@ -308,7 +307,7 @@ public final class Paths {
throw new IllegalArgumentException("output == null!"); throw new IllegalArgumentException("output == null!");
} }
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromRenderedImage(image); ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image);
Iterator<ImageWriter> writers = ImageIO.getImageWriters(type, formatName); Iterator<ImageWriter> writers = ImageIO.getImageWriters(type, formatName);
if (writers.hasNext()) { if (writers.hasNext()) {
+1 -20
View File
@@ -4,13 +4,10 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-core</artifactId> <artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name> <name>TwelveMonkeys :: ImageIO :: Core</name>
<description>
TwelveMonkeys ImageIO core support classes.
</description>
<properties> <properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.core</project.jpms.module.name> <project.jpms.module.name>com.twelvemonkeys.imageio.core</project.jpms.module.name>
@@ -31,20 +28,4 @@
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageInputStreamSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
@@ -1,627 +0,0 @@
package com.twelvemonkeys.imageio;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* Base class for easy read-only implementation of the standard image metadata format.
* Chroma, Data and Transparency nodes values are based on the required
* {@link ImageTypeSpecifier}.
* Other values or overrides may be specified using the builder.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public class StandardImageMetadataSupport extends AbstractMetadata {
// The only required field, most standard metadata can be extracted from the type
private final ImageTypeSpecifier type;
protected final ColorSpaceType colorSpaceType;
protected final boolean blackIsZero;
private final IndexColorModel palette;
protected final String compressionName;
protected final boolean compressionLossless;
protected final PlanarConfiguration planarConfiguration;
private final int[] bitsPerSample;
private final int[] significantBits;
private final int[] sampleMSB;
protected final Double pixelAspectRatio;
protected final ImageOrientation orientation;
protected final String formatVersion;
protected final SubimageInterpretation subimageInterpretation;
private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API...
private final Collection<TextEntry> textEntries;
protected StandardImageMetadataSupport(Builder builder) {
notNull(builder, "builder");
// Baseline
type = builder.type;
// Chroma
colorSpaceType = builder.colorSpaceType;
blackIsZero = builder.blackIsZero;
palette = builder.palette;
// Compression
compressionName = builder.compressionName;
compressionLossless = builder.compressionLossless;
// Data
planarConfiguration = builder.planarConfiguration;
bitsPerSample = builder.bitsPerSample;
significantBits = builder.significantBits;
sampleMSB = builder.sampleMSB;
// Dimension
orientation = builder.orientation;
pixelAspectRatio = builder.pixelAspectRatio;
// Document
formatVersion = builder.formatVersion;
documentCreationTime = builder.documentCreationTime;
subimageInterpretation = builder.subimageInterpretation;
// Text
textEntries = builder.textEntries;
}
public static Builder builder(ImageTypeSpecifier type) {
return new Builder(type);
}
public static class Builder {
private final ImageTypeSpecifier type;
private ColorSpaceType colorSpaceType;
private boolean blackIsZero = true;
private IndexColorModel palette;
private String compressionName;
private boolean compressionLossless = true;
private PlanarConfiguration planarConfiguration;
public int[] bitsPerSample;
private int[] significantBits;
private int[] sampleMSB;
private Double pixelAspectRatio;
private ImageOrientation orientation = ImageOrientation.Normal;
private String formatVersion;
private SubimageInterpretation subimageInterpretation;
private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type
private final Collection<TextEntry> textEntries = new ArrayList<>();
protected Builder(ImageTypeSpecifier type) {
this.type = notNull(type, "type");
}
public Builder withColorSpaceType(ColorSpaceType colorSpaceType) {
this.colorSpaceType = colorSpaceType;
return this;
}
public Builder withBlackIsZero(boolean blackIsZero) {
this.blackIsZero = blackIsZero;
return this;
}
public Builder withPalette(IndexColorModel palette) {
this.palette = palette;
return this;
}
public Builder withCompressionTypeName(String compressionName) {
this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName;
return this;
}
public Builder withCompressionLossless(boolean lossless) {
this.compressionLossless = isTrue(lossless || compressionName != null, lossless, "Lossy compression requires compression name");
return this;
}
public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) {
this.planarConfiguration = planarConfiguration;
return this;
}
public Builder withBitsPerSample(int... bitsPerSample) {
this.bitsPerSample = bitsPerSample;
return this;
}
public Builder withSignificantBitsPerSample(int... significantBits) {
this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(),
significantBits,
String.format("single value or %d values expected", type.getNumBands()));
return this;
}
public Builder withSampleMSB(int... sampleMSB) {
this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(),
sampleMSB,
String.format("single value or %d values expected", type.getNumBands()));
return this;
}
public Builder withPixelAspectRatio(Double pixelAspectRatio) {
this.pixelAspectRatio = pixelAspectRatio;
return this;
}
public Builder withOrientation(ImageOrientation orientation) {
this.orientation = notNull(orientation, "orientation");
return this;
}
public Builder withFormatVersion(String formatVersion) {
this.formatVersion = notNull(formatVersion, "formatVersion");
return this;
}
public Builder withSubimageInterpretation(SubimageInterpretation interpretation) {
this.subimageInterpretation = interpretation;
return this;
}
public Builder withDocumentCreationTime(Calendar creationTime) {
this.documentCreationTime = creationTime;
return this;
}
public Builder withTextEntries(Map<String, String> entries) {
return withTextEntries(toTextEntries(notNull(entries, "entries").entrySet()));
}
private Collection<TextEntry> toTextEntries(Collection<Map.Entry<String, String>> entries) {
TextEntry[] result = new TextEntry[entries.size()];
int i = 0;
for (Map.Entry<String, String> entry : entries) {
result[i++] = new TextEntry(entry.getKey(), entry.getValue());
}
return Arrays.asList(result);
}
public Builder withTextEntries(Collection<TextEntry> entries) {
this.textEntries.addAll(notNull(entries, "entries"));
return this;
}
public Builder withTextEntry(String keyword, String value) {
if (value != null && !value.isEmpty()) {
this.textEntries.add(new TextEntry(notNull(keyword, "keyword"), value));
}
return this;
}
public IIOMetadata build() {
return new StandardImageMetadataSupport(this);
}
}
protected enum ColorSpaceType {
XYZ(3),
Lab(3),
Luv(3),
YCbCr(3),
Yxy(3),
YCCK(4),
PhotoYCC(3),
RGB(3),
GRAY(1),
HSV(3),
HLS(3),
CMYK(3),
CMY(3),
// Generic types (so much extra work, because Java names can't start with a number, phew...)
GENERIC_2CLR(2, "2CLR"),
GENERIC_3CLR(3, "3CLR"),
GENERIC_4CLR(4, "4CLR"),
GENERIC_5CLR(5, "5CLR"),
GENERIC_6CLR(6, "6CLR"),
GENERIC_7CLR(7, "7CLR"),
GENERIC_8CLR(8, "8CLR"),
GENERIC_9CLR(9, "9CLR"),
GENERIC_ACLR(0xA, "ACLR"),
GENERIC_BCLR(0xB, "BCLR"),
GENERIC_CCLR(0xC, "CCLR"),
GENERIC_DCLR(0xD, "DCLR"),
GENERIC_ECLR(0xE, "ECLR"),
GENERIC_FCLR(0xF, "FCLR");
final int numChannels;
private final String nameOverride;
ColorSpaceType(int numChannels) {
this(numChannels, null);
}
ColorSpaceType(int numChannels, String nameOverride) {
this.numChannels = numChannels;
this.nameOverride = nameOverride;
}
@Override
public String toString() {
return nameOverride != null ? nameOverride : super.toString();
}
}
protected enum PlanarConfiguration {
PixelInterleaved,
PlaneInterleaved,
LineInterleaved,
TileInterleaved
}
protected enum ImageOrientation {
Normal,
Rotate90,
Rotate180,
Rotate270,
FlipH,
FlipV,
FlipHRotate90,
FlipVRotate90
}
protected enum SubimageInterpretation {
Standalone,
SinglePage,
FullResolution,
ReducedResolution,
PyramidLayer,
Preview,
VolumeSlice,
ObjectView,
Panorama,
AnimationFrame,
TransparencyMask,
CompositingLayer,
SpectralSlice,
Unknown
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma");
ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel();
ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace());
int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents();
IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType");
chromaNode.appendChild(colorSpaceTypeNode);
colorSpaceTypeNode.setAttribute("name", csType.toString());
IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels");
numChannelsNode.setAttribute("value", String.valueOf(numComponents));
chromaNode.appendChild(numChannelsNode);
IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero");
blackIsZeroNode.setAttribute("value", booleanString(blackIsZero));
chromaNode.appendChild(blackIsZeroNode);
if (colorModel instanceof IndexColorModel || palette != null) {
IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel;
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
chromaNode.appendChild(paletteNode);
for (int i = 0; i < colorMap.getMapSize(); i++) {
IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry");
paletteNode.appendChild(paletteEntryNode);
paletteEntryNode.setAttribute("index", Integer.toString(i));
paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i)));
paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
// Assumption: BITMASK transparency will use single transparent pixel
if (colorMap.getTransparency() == Transparency.TRANSLUCENT) {
paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i)));
}
}
if (colorMap.getTransparentPixel() != -1) {
IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex");
chromaNode.appendChild(backgroundIndexNode);
backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
}
}
// TODO: BackgroundColor?
return chromaNode;
}
private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) {
switch (colorSpace.getType()) {
case ColorSpace.TYPE_XYZ:
return XYZ;
case ColorSpace.TYPE_Lab:
return Lab;
case ColorSpace.TYPE_Luv:
return Luv;
case ColorSpace.TYPE_YCbCr:
return YCbCr;
case ColorSpace.TYPE_Yxy:
return Yxy;
// Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace
case ColorSpace.TYPE_RGB:
return RGB;
case ColorSpace.TYPE_GRAY:
return GRAY;
case ColorSpace.TYPE_HSV:
return HSV;
case ColorSpace.TYPE_HLS:
return HLS;
case ColorSpace.TYPE_CMYK:
return CMYK;
case ColorSpace.TYPE_CMY:
return CMY;
default:
int numComponents = colorSpace.getNumComponents();
if (numComponents == 1) {
return GRAY;
}
else if (numComponents < 16) {
return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR");
}
}
throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace);
}
protected static final class TextEntry {
static final List<String> COMPRESSIONS = Arrays.asList("none", "lzw", "zip", "bzip", "other");
final String keyword;
final String value;
final String language;
final String encoding;
final String compression;
public TextEntry(final String keyword, final String value) {
this(keyword, value, null, null, null);
}
public TextEntry(final String keyword, final String value, final String language, final String encoding, final String compression) {
this.keyword = keyword;
this.value = notNull(value, "value");
this.language = language;
this.encoding = encoding;
this.compression = isTrue(compression == null || COMPRESSIONS.contains(compression), compression, String.format("Unknown compression: %s (expected: %s)", compression, COMPRESSIONS));
}
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (compressionName == null) {
return null;
}
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", compressionName);
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", booleanString(compressionLossless));
node.appendChild(lossless);
return node;
}
protected static String booleanString(boolean booleanValue) {
return booleanValue ? "TRUE" : "FALSE";
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration");
dataNode.appendChild(planarConfigurationNode);
planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() :
(type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved"));
String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel
? "Index"
: sampleFormat(type.getSampleModel());
if (sampleFormatValue != null) {
IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat");
sampleFormatNode.setAttribute("value", sampleFormatValue);
dataNode.appendChild(sampleFormatNode);
}
int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize();
IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample");
bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample));
dataNode.appendChild(bitsPerSampleNode);
if (significantBits != null) {
String significantBitsValue = createListValue(type.getNumBands(), significantBits);
if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) {
IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample");
significantBitsPerSampleNode.setAttribute("value", significantBitsValue);
dataNode.appendChild(significantBitsPerSampleNode);
}
}
if (sampleMSB != null) {
// TODO: Only if different from default!
IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB");
sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB));
dataNode.appendChild(sampleMSBNode);
}
return dataNode;
}
private static String createListValue(final int itemCount, final int... values) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < itemCount; i++) {
if (buffer.length() > 0) {
buffer.append(' ');
}
buffer.append(values[i % values.length]);
}
return buffer.toString();
}
private static String sampleFormat(SampleModel sampleModel) {
switch (sampleModel.getDataType()) {
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_INT:
if (sampleModel instanceof ComponentSampleModel) {
return "SignedIntegral";
}
// Otherwise fall-through, most likely a *PixelPackedSampleModel
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
return "UnsignedIntegral";
case DataBuffer.TYPE_FLOAT:
case DataBuffer.TYPE_DOUBLE:
return "Real";
default:
return null;
}
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
if (pixelAspectRatio != null) {
IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio));
dimensionNode.appendChild(pixelAspectRatioNode);
}
IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation");
imageOrientationNode.setAttribute("value", orientation.toString());
dimensionNode.appendChild(imageOrientationNode);
return dimensionNode.hasChildNodes() ? dimensionNode : null;
}
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode documentNode = new IIOMetadataNode("Document");
if (formatVersion != null) {
IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion");
documentNode.appendChild(formatVersionNode);
formatVersionNode.setAttribute("value", formatVersion);
}
if (subimageInterpretation != null) {
IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation");
documentNode.appendChild(subImageInterpretationNode);
subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString());
}
if (documentCreationTime != null) {
IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime");
documentNode.appendChild(imageCreationTimeNode);
imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR)));
imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1));
imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH)));
imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY)));
imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE)));
imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND)));
}
return documentNode.hasChildNodes() ? documentNode : null;
}
@Override
protected IIOMetadataNode getStandardTextNode() {
if (textEntries.isEmpty()) {
return null;
}
IIOMetadataNode textNode = new IIOMetadataNode("Text");
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
for (TextEntry entry : textEntries) {
IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry");
textNode.appendChild(textEntryNode);
if (entry.keyword != null) {
textEntryNode.setAttribute("keyword", entry.keyword);
}
textEntryNode.setAttribute("value", entry.value);
if (entry.language != null) {
textEntryNode.setAttribute("language", entry.language);
}
if (entry.encoding != null) {
textEntryNode.setAttribute("encoding", entry.encoding);
}
if (entry.compression != null) {
textEntryNode.setAttribute("compression", entry.compression);
}
}
return textNode;
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency");
ColorModel colorModel = type.getColorModel();
IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha");
transparencyNode.appendChild(alphaNode);
alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none");
if (colorModel instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel) colorModel;
if (icm.getTransparentPixel() != -1) {
IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex");
transparencyNode.appendChild(transparentIndexNode);
transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel()));
}
}
return transparencyNode;
}
}
@@ -188,7 +188,7 @@ public final class ColorSpaces {
// Will throw IllegalArgumentException or CMMException if the profile is bad // Will throw IllegalArgumentException or CMMException if the profile is bad
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f}); cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
// This breaks *sometimes* after validation of bad profiles, // This breaks *some times* after validation of bad profiles,
// we'll let it blow up early in this case // we'll let it blow up early in this case
cs.getProfile().getData(); cs.getProfile().getData();
} }
@@ -1,348 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import javax.imageio.stream.ImageInputStreamImpl;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.max;
/**
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
* and provides greatly improved performance
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
* for shorter reads, like single byte or bit reads.
*/
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private static final Closeable CLOSEABLE_STUB = new Closeable() {
@Override public void close() {}
};
static final int DEFAULT_BUFFER_SIZE = 8192;
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
private byte[] buffer = byteBuffer.array();
private int bufferPos;
private int bufferLimit;
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
private final byte[] integralCacheArray = integralCache.array();
private SeekableByteChannel channel;
private Closeable closeable;
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
*
* @param file a {@code File} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
* @throws IOException if an I/O error occurs while opening the file.
*/
public BufferedChannelImageInputStream(final File file) throws IOException {
this(notNull(file, "file").toPath());
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
*
* @param file a {@code Path} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
* @throws IOException if an I/O error occurs while opening the file.
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
*/
public BufferedChannelImageInputStream(final Path file) throws IOException {
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
*
* @param file a {@code RandomAccessFile} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
*/
public BufferedChannelImageInputStream(final RandomAccessFile file) {
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
this(notNull(file, "file").getChannel(), true);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
* <p>
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
* </p>
*
* @param inputStream a {@code FileInputStream} to read from.
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
*/
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
this(notNull(inputStream, "inputStream").getChannel(), false);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
* <p>
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
* </p>
*
* @param channel a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}.
*/
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
this(notNull(channel, "channel"), false);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
* <p>
* Closing this stream will close the {@code Cache}.
* </p>
*
* @param cache a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}.
*/
BufferedChannelImageInputStream(final Cache cache) {
this(notNull(cache, "cache"), true);
}
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
this.channel = notNull(channel, "channel");
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean fillBuffer() throws IOException {
byteBuffer.rewind();
int length = channel.read(byteBuffer);
bufferPos = 0;
bufferLimit = max(length, 0);
return bufferLimit > 0;
}
private boolean bufferEmpty() {
return bufferPos >= bufferLimit;
}
@Override
public void setByteOrder(ByteOrder byteOrder) {
super.setByteOrder(byteOrder);
integralCache.order(byteOrder);
}
@Override
public int read() throws IOException {
checkClosed();
if (bufferEmpty() && !fillBuffer()) {
return -1;
}
bitOffset = 0;
streamPos++;
return buffer[bufferPos++] & 0xff;
}
@Override
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
checkClosed();
bitOffset = 0;
if (bufferEmpty()) {
// Bypass buffer if buffer is empty for reads longer than buffer
if (length >= buffer.length) {
return readDirect(bytes, offset, length);
}
else if (!fillBuffer()) {
return -1;
}
}
int fromBuffer = readBuffered(bytes, offset, length);
if (length > fromBuffer) {
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
// we'll read as much as possible from the buffer, and the rest directly after
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
}
return fromBuffer;
}
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
bufferLimit = 0;
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
int read = 0;
while (wrapped.hasRemaining()) {
int count = channel.read(wrapped);
if (count == -1) {
if (read == 0) {
return -1;
}
break;
}
read += count;
}
streamPos += read;
return read;
}
private int readBuffered(final byte[] bytes, final int offset, final int length) {
// Read as much as possible from buffer
int available = Math.min(bufferLimit - bufferPos, length);
if (available > 0) {
System.arraycopy(buffer, bufferPos, bytes, offset, available);
bufferPos += available;
streamPos += available;
}
return available;
}
public long length() {
// WTF?! This method is allowed to throw IOException in the interface...
try {
checkClosed();
return channel.size();
}
catch (IOException ignore) {
}
return -1;
}
public void close() throws IOException {
super.close();
buffer = null;
byteBuffer = null;
channel = null;
try {
closeable.close();
}
finally {
closeable = null;
}
}
// Need to override the readShort(), readInt() and readLong() methods,
// because the implementations in ImageInputStreamImpl expects the
// read(byte[], int, int) to always read the expected number of bytes,
// causing uninitialized values, alignment issues and EOFExceptions at
// random places...
// Notes:
// * readUnsignedXx() is covered by their signed counterparts
// * readChar() is covered by readShort()
// * readFloat() and readDouble() is covered by readInt() and readLong()
// respectively.
// * readLong() may be covered by two readInt()s, we'll override to be safe
@Override
public short readShort() throws IOException {
readFully(integralCacheArray, 0, 2);
return integralCache.getShort(0);
}
@Override
public int readInt() throws IOException {
readFully(integralCacheArray, 0, 4);
return integralCache.getInt(0);
}
@Override
public long readLong() throws IOException {
readFully(integralCacheArray, 0, 8);
return integralCache.getLong(0);
}
@Override
public void seek(long position) throws IOException {
checkClosed();
if (position < flushedPos) {
throw new IndexOutOfBoundsException("position < flushedPos!");
}
bitOffset = 0;
if (streamPos == position) {
return;
}
// Optimized to not invalidate buffer if new position is within current buffer
long newBufferPos = bufferPos + position - streamPos;
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
bufferPos = (int) newBufferPos;
}
else {
// Will invalidate buffer
bufferLimit = 0;
channel.position(position);
}
streamPos = position;
}
@Override
public void flushBefore(final long pos) throws IOException {
super.flushBefore(pos);
if (channel instanceof Cache) {
// In case of memory cache, free up memory
((Cache) channel).flushBefore(pos);
}
}
}
@@ -49,7 +49,6 @@ import static java.lang.Math.max;
* {@link File} or {@link RandomAccessFile} can be used as input. * {@link File} or {@link RandomAccessFile} can be used as input.
* *
* @see javax.imageio.stream.FileImageInputStream * @see javax.imageio.stream.FileImageInputStream
* @deprecated Use {@link BufferedChannelImageInputStream} instead.
*/ */
// TODO: Create a memory-mapped version? // TODO: Create a memory-mapped version?
// Or not... From java.nio.channels.FileChannel.map: // Or not... From java.nio.channels.FileChannel.map:
@@ -58,7 +57,6 @@ import static java.lang.Math.max;
// 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.
@Deprecated
public final class BufferedFileImageInputStream extends ImageInputStreamImpl { public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
static final int DEFAULT_BUFFER_SIZE = 8192; static final int DEFAULT_BUFFER_SIZE = 8192;
@@ -192,10 +190,10 @@ public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
public void close() throws IOException { public void close() throws IOException {
super.close(); super.close();
buffer = null;
raf.close(); raf.close();
raf = null; raf = null;
buffer = null;
} }
// Need to override the readShort(), readInt() and readLong() methods, // Need to override the readShort(), readInt() and readLong() methods,
@@ -37,19 +37,18 @@ import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Iterator; import java.util.Iterator;
import java.util.Locale; import java.util.Locale;
/** /**
* BufferedFileImageInputStreamSpi * BufferedFileImageInputStreamSpi
* Experimental
* *
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a> * @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ * @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/ */
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi { public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedFileImageInputStreamSpi() { public BufferedFileImageInputStreamSpi() {
this(new StreamProviderInfo()); this(new StreamProviderInfo());
} }
@@ -70,13 +69,12 @@ public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
} }
} }
@Override public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof File) { if (input instanceof File) {
try { try {
return new BufferedChannelImageInputStream((File) input); return new BufferedFileImageInputStream((File) input);
} }
catch (FileNotFoundException | NoSuchFileException e) { catch (FileNotFoundException e) {
// For consistency with the JRE bundled SPIs, we'll return null here, // For consistency with the JRE bundled SPIs, we'll return null here,
// even though the spec does not say that's allowed. // even though the spec does not say that's allowed.
// The problem is that the SPIs can only declare that they support an input type like a File, // The problem is that the SPIs can only declare that they support an input type like a File,
@@ -93,8 +91,7 @@ public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
return false; return false;
} }
@Override public String getDescription(final Locale pLocale) {
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from a File"; return "Service provider that instantiates an ImageInputStream from a File";
} }
@@ -1,78 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.Iterator;
import java.util.Locale;
/**
* BufferedInputStreamImageInputStreamSpi.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
*/
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedInputStreamImageInputStreamSpi() {
this(new StreamProviderInfo());
}
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
}
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
while (providers.hasNext()) {
ImageInputStreamSpi provider = providers.next();
if (provider != this) {
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
}
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof InputStream) {
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
if (channel instanceof SeekableByteChannel) {
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
}
// Otherwise, create a cache for backwards seeking
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
}
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
}
@Override
public boolean canUseCacheFile() {
return true;
}
@Override
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from an InputStream";
}
private static class InputStreamFilter implements ServiceRegistry.Filter {
@Override
public boolean filter(final Object provider) {
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
}
}
}
@@ -48,7 +48,7 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ * @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/ */
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi { public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedRAFImageInputStreamSpi() { public BufferedRAFImageInputStreamSpi() {
this(new StreamProviderInfo()); this(new StreamProviderInfo());
} }
@@ -69,10 +69,9 @@ public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
} }
} }
@Override public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
if (input instanceof RandomAccessFile) { if (input instanceof RandomAccessFile) {
return new BufferedChannelImageInputStream((RandomAccessFile) input); return new BufferedFileImageInputStream((RandomAccessFile) input);
} }
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input); throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
@@ -83,8 +82,7 @@ public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
return false; return false;
} }
@Override public String getDescription(final Locale pLocale) {
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile"; return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
} }
@@ -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[] data) { public ByteArrayImageInputStream(final byte[] pData) {
this(data, 0, data != null ? data.length : -1); this(pData, 0, pData != null ? pData.length : -1);
} }
public ByteArrayImageInputStream(final byte[] data, int offset, int length) { public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
this.data = notNull(data, "data"); data = notNull(pData, "data");
dataOffset = isMax(data.length, offset, "offset"); dataOffset = isBetween(0, pData.length, offset, "offset");
dataLength = isMax(data.length - offset, length, "length"); dataLength = isBetween(0, pData.length - offset, length, "length");
} }
private static int isMax(final int high, final int value, final String name) { private static int isBetween(final int low, final int high, final int value, final String name) {
return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value)); return isTrue(value >= low && value <= high, value, String.format("%s out of range [%d, %d]: %d", name, low, 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[] buffer, int offset, int len) throws IOException { public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
if (streamPos >= dataLength) { if (streamPos >= dataLength) {
return -1; return -1;
} }
int length = (int) Math.min(dataLength - streamPos, len); int length = (int) Math.min(this.dataLength - streamPos, pLength);
bitOffset = 0; bitOffset = 0;
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length); System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
streamPos += length; streamPos += length;
return length; return length;
@@ -45,7 +45,7 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$ * @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
*/ */
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi { public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
public ByteArrayImageInputStreamSpi() { public ByteArrayImageInputStreamSpi() {
this(new StreamProviderInfo()); this(new StreamProviderInfo());
@@ -55,17 +55,16 @@ public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class); super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
} }
@Override public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) { if (pInput instanceof byte[]) {
if (input instanceof byte[]) { return new ByteArrayImageInputStream((byte[]) pInput);
return new ByteArrayImageInputStream((byte[]) input); }
else {
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
}
} }
throw new IllegalArgumentException("Expected input of type byte[]: " + input); public String getDescription(Locale pLocale) {
}
@Override
public String getDescription(Locale locale) {
return "Service provider that instantiates an ImageInputStream from a byte array"; return "Service provider that instantiates an ImageInputStream from a byte array";
} }
@@ -1,7 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import java.nio.channels.SeekableByteChannel;
interface Cache extends SeekableByteChannel {
void flushBefore(long pos);
}
@@ -125,7 +125,7 @@ public final class DirectImageInputStream extends ImageInputStreamImpl {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// We could seek to EOF here, but the usual case is we know where the next chunk of data is // We could seek to EOF here, but the usual case
stream.close(); stream.close();
super.close(); super.close();
@@ -1,112 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.max;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
// Note: We could consider creating a memory-mapped version...
// But, from java.nio.channels.FileChannel.map:
// For most operating systems, mapping a file into memory is more
// expensive than reading or writing a few tens of kilobytes of data via
// the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively
// large files into memory.
final class FileCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private final FileChannel cache;
private final ReadableByteChannel channel;
// TODO: Perhaps skip this constructor?
FileCache(InputStream stream, File cacheDir) throws IOException {
// Stream will be closed with channel, documented behavior
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
}
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
this.channel = notNull(channel, "channel");
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
// Create a temp file to hold our cache,
// will be deleted when this channel is closed, as we close the cache
Path cacheFile = cacheDir == null
? Files.createTempFile("imageio", ".tmp")
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
}
@SuppressWarnings("StatementWithEmptyBody")
void fetch() throws IOException {
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
// Continue transfer...
}
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
cache.close();
}
@Override
public int read(ByteBuffer dest) throws IOException {
fetch();
if (cache.position() >= cache.size()) {
return -1;
}
return cache.read(dest);
}
@Override
public long position() throws IOException {
return cache.position();
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
cache.position(newPosition);
return this;
}
@Override
public long size() {
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
return -1;
}
@Override
public int write(ByteBuffer src) {
throw new NonWritableChannelException();
}
@Override
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override public void flushBefore(long pos) {
}
}
@@ -1,165 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.min;
final class MemoryCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private static final byte[] NULL_BLOCK = new byte[0];
private final List<byte[]> cache = new ArrayList<>();
private final ReadableByteChannel channel;
private int maxBlock = Integer.MAX_VALUE;
private long length;
private long position;
private long start;
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
MemoryCache(InputStream stream) {
this(Channels.newChannel(notNull(stream, "stream")));
}
public MemoryCache(ReadableByteChannel channel) {
this.channel = notNull(channel, "channel");
}
byte[] fetchBlock() throws IOException {
long currPos = position;
long index = currPos / BLOCK_SIZE;
if (index >= Integer.MAX_VALUE) {
throw new IOException("Memory cache max size exceeded");
}
if (index > maxBlock) {
return NULL_BLOCK;
}
while (index >= cache.size()) {
byte[] block;
try {
block = new byte[BLOCK_SIZE];
}
catch (OutOfMemoryError e) {
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
}
cache.add(block);
int bytesRead = readBlock(block);
length += bytesRead;
if (bytesRead < BLOCK_SIZE) {
// Last block, EOF found
maxBlock = (int) index;
return block;
}
}
return cache.get((int) index);
}
private int readBlock(final byte[] block) throws IOException {
ByteBuffer wrapped = ByteBuffer.wrap(block);
while (wrapped.hasRemaining()) {
int count = channel.read(wrapped);
if (count == -1) {
// Last block, EOF found
break;
}
}
return wrapped.position();
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
cache.clear();
}
@Override
public int read(ByteBuffer dest) throws IOException {
byte[] buffer = fetchBlock();
if (position >= length) {
return -1;
}
int bufferPos = (int) (position % BLOCK_SIZE);
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
dest.put(buffer, bufferPos, len);
position += len;
return len;
}
@Override
public long position() throws IOException {
return position;
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
if (newPosition < start) {
throw new IOException("Seek before flush position");
}
this.position = newPosition;
return this;
}
@Override
public long size() throws IOException {
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
return -1;
}
@Override
public int write(ByteBuffer src) {
throw new NonWritableChannelException();
}
@Override
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override
public void flushBefore(long pos) {
if (pos < start) {
throw new IndexOutOfBoundsException("pos < flushed position");
}
if (pos > position) {
throw new IndexOutOfBoundsException("pos > current position");
}
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
// Clear blocks no longer needed
for (int i = 0; i < blocks; i++) {
cache.set(i, null);
}
start = pos;
}
}
@@ -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 stream the underlying stream * @param pStream the underlying stream
* @param length the maximum length to read from the stream. * @param pLength the maximum length to read from the stream.
* Note that {@code stream} may contain less than this maximum number of bytes. * Note that {@code pStream} may contain less than this maximum number of bytes.
* *
* @throws IOException if {@code stream}'s position can't be determined. * @throws IOException if {@code pStream}'s position can't be determined.
* @throws IllegalArgumentException if {@code stream == null} or {@code length < 0} * @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
*/ */
public SubImageInputStream(final ImageInputStream stream, final long length) throws IOException { public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
Validate.notNull(stream, "stream"); Validate.notNull(pStream, "stream");
Validate.isTrue(length >= 0, length, "length < 0: %d"); Validate.isTrue(pLength >= 0, pLength, "length < 0: %d");
this.stream = stream; stream = pStream;
this.startPos = stream.getStreamPosition(); startPos = pStream.getStreamPosition();
this.length = length; length = pLength;
} }
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[] bytes, final int off, final int len) throws IOException { public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (streamPos >= length) { // Local EOF if (streamPos >= length) { // Local EOF
return -1; return -1;
} }
// Safe cast, as len can never cause int overflow // Safe cast, as pLength can never cause int overflow
int length = (int) Math.min(len, this.length - streamPos); int length = (int) Math.min(pLength, this.length - streamPos);
int count = stream.read(bytes, off, length); int count = stream.read(pBytes, pOffset, 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 position) throws IOException { public void seek(final long pPosition) throws IOException {
if (position < getFlushedPosition()) { if (pPosition < getFlushedPosition()) {
throw new IndexOutOfBoundsException("pos < flushedPosition"); throw new IndexOutOfBoundsException("pos < flushedPosition");
} }
stream.seek(startPos + position); stream.seek(startPos + pPosition);
streamPos = position; streamPos = pPosition;
} }
@SuppressWarnings("MethodDoesntCallSuperMethod") @SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
@Override @Override
protected void finalize() { protected void finalize() throws Throwable {
// 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)
} }
} }
@@ -33,7 +33,9 @@ package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo; import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.FileCacheImageInputStream;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@@ -50,7 +52,7 @@ import java.util.Locale;
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ * @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/ */
// TODO: URI instead of URL? // TODO: URI instead of URL?
public final class URLImageInputStreamSpi extends ImageInputStreamSpi { public class URLImageInputStreamSpi extends ImageInputStreamSpi {
public URLImageInputStreamSpi() { public URLImageInputStreamSpi() {
this(new StreamProviderInfo()); this(new StreamProviderInfo());
} }
@@ -62,28 +64,53 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats // TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks // The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi.. // The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
@Override public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException { if (pInput instanceof URL) {
if (input instanceof URL) { URL url = (URL) pInput;
URL url = (URL) input;
// Special case for file protocol, a lot faster than FileCacheImageInputStream // Special case for file protocol, a lot faster than FileCacheImageInputStream
if ("file".equals(url.getProtocol())) { if ("file".equals(url.getProtocol())) {
try { try {
return new BufferedChannelImageInputStream(new File(url.toURI())); return new BufferedFileImageInputStream(new File(url.toURI()));
} }
catch (URISyntaxException shouldNeverHappen) { catch (URISyntaxException ignore) {
// This should never happen, but if it does, we'll fall back to using the stream // This should never happen, but if it does, we'll fall back to using the stream
shouldNeverHappen.printStackTrace(); ignore.printStackTrace();
} }
} }
// Otherwise revert to cached // Otherwise revert to cached
InputStream urlStream = url.openStream(); final InputStream urlStream = url.openStream();
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream)); if (pUseCache) {
return new FileCacheImageInputStream(urlStream, pCacheDir) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
}
}
};
}
else {
return new MemoryCacheImageInputStream(urlStream) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
}
}
};
}
}
else {
throw new IllegalArgumentException("Expected input of type URL: " + pInput);
} }
throw new IllegalArgumentException("Expected input of type URL: " + input);
} }
@Override @Override
@@ -91,7 +118,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
return true; return true;
} }
public String getDescription(final Locale locale) { public String getDescription(final Locale pLocale) {
return "Service provider that instantiates an ImageInputStream from a URL"; return "Service provider that instantiates an ImageInputStream from a URL";
} }
} }
@@ -30,15 +30,22 @@
package com.twelvemonkeys.imageio.util; package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.lang.Validate.isTrue; import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.SampleModel;
import javax.imageio.ImageTypeSpecifier;
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
/** /**
* Factory class for creating {@code ImageTypeSpecifier}s. * Factory class for creating {@code ImageTypeSpecifier}s.
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but * Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
@@ -51,52 +58,28 @@ import static com.twelvemonkeys.lang.Validate.notNull;
*/ */
public final class ImageTypeSpecifiers { public final class ImageTypeSpecifiers {
private static final ImageTypeSpecifier TYPE_INT_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24, private ImageTypeSpecifiers() {}
0xFF0000,
0x00FF00, public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
0x0000FF, switch (bufferedImageType) {
0x0, // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types
DataBuffer.TYPE_INT, case BufferedImage.TYPE_USHORT_565_RGB:
false); return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
0x0000FF,
0x00FF00,
0xFF0000,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16,
0xF800, 0xF800,
0x07E0, 0x07E0,
0x001F, 0x001F,
0x0, 0x0,
DataBuffer.TYPE_USHORT, DataBuffer.TYPE_USHORT,
false); false);
private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15,
case BufferedImage.TYPE_USHORT_555_RGB:
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0x7C00, 0x7C00,
0x03E0, 0x03E0,
0x001F, 0x001F,
0x0, 0x0,
DataBuffer.TYPE_USHORT, DataBuffer.TYPE_USHORT,
false); false);
private ImageTypeSpecifiers() {}
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
switch (bufferedImageType) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types
case BufferedImage.TYPE_INT_RGB:
return TYPE_INT_RGB;
case BufferedImage.TYPE_INT_BGR:
return TYPE_INT_BGR;
case BufferedImage.TYPE_USHORT_565_RGB:
return TYPE_USHORT_565_RGB;
case BufferedImage.TYPE_USHORT_555_RGB:
return TYPE_USHORT_555_RGB;
default: default:
} }
@@ -107,41 +90,23 @@ public final class ImageTypeSpecifiers {
final int redMask, final int greenMask, final int redMask, final int greenMask,
final int blueMask, final int alphaMask, final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) { final int transferType, boolean isAlphaPremultiplied) {
int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask); if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
if (bits != 32) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
private static int calculateRequiredBits(int mask) {
// See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
int r = 1;
while ((mask >>>= 1) != 0) {
r++;
}
return r;
}
static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits,
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround
notNull(colorSpace, "colorSpace"); notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB"); isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set"); isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType); isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
} }
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace, public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace,
final int[] bandOffsets, final int[] bandOffsets,
final int dataType, final int dataType,
@@ -257,35 +222,17 @@ public final class ImageTypeSpecifiers {
return createFromIndexColorModel(new IndexColorModel(bits, colors.length, colors, 0, hasAlpha, transIndex, dataType)); return createFromIndexColorModel(new IndexColorModel(bits, colors.length, colors, 0, hasAlpha, transIndex, dataType));
} }
public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel colorModel) { public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) {
return new IndexedImageTypeSpecifier(colorModel); return new IndexedImageTypeSpecifier(pColorModel);
} }
public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel colorModel) { public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) {
ColorModel discreteAlphaIndexColorModel = new DiscreteAlphaIndexColorModel(colorModel); ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel);
return new ImageTypeSpecifier(discreteAlphaIndexColorModel, discreteAlphaIndexColorModel.createCompatibleSampleModel(1, 1)); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
} }
public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel colorModel, int extraSamples, boolean hasAlpha) { public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel pColorModel, int extraSamples, boolean hasAlpha) {
ColorModel discreteAlphaIndexColorModel = new DiscreteAlphaIndexColorModel(colorModel, extraSamples, hasAlpha); ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
return new ImageTypeSpecifier(discreteAlphaIndexColorModel, discreteAlphaIndexColorModel.createCompatibleSampleModel(1, 1)); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
if (image == null) {
throw new IllegalArgumentException("image == null!");
}
if (image instanceof BufferedImage) {
int bufferedImageType = ((BufferedImage) image).getType();
if (bufferedImageType != BufferedImage.TYPE_CUSTOM &&
// Need to retain the actual palette in the color model for IndexColorModel
bufferedImageType != BufferedImage.TYPE_BYTE_BINARY && bufferedImageType != BufferedImage.TYPE_BYTE_INDEXED) {
return createFromBufferedImageType(bufferedImageType);
}
}
return new ImageTypeSpecifier(image);
} }
} }
@@ -52,12 +52,12 @@ final class IndexedImageTypeSpecifier extends ImageTypeSpecifier {
} }
@Override @Override
public BufferedImage createBufferedImage(final int width, final int height) { public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) {
try { try {
// This is a fix for the super-method, that first creates a sample model, and then // This is a fix for the super-method, that first creates a sample model, and then
// creates a raster from it, using Raster.createWritableRaster. The problem with // creates a raster from it, using Raster.createWritableRaster. The problem with
// that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images. // that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images.
WritableRaster raster = colorModel.createCompatibleWritableRaster(width, height); WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight);
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null); return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
} }
catch (NegativeArraySizeException e) { catch (NegativeArraySizeException e) {
@@ -1,5 +1,4 @@
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedInputStreamImageInputStreamSpi
# Use SPI loading as a hook for early profile activation # Use SPI loading as a hook for early profile activation
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
@@ -1,333 +0,0 @@
package com.twelvemonkeys.imageio;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ImageOrientation;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.PlanarConfiguration;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.SubimageInterpretation;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.TextEntry;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import org.junit.Test;
import org.w3c.dom.NodeList;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.builder;
import static org.junit.Assert.*;
public class StandardImageMetadataSupportTest {
@Test(expected = IllegalArgumentException.class)
public void createNullBuilder() {
new StandardImageMetadataSupport(null);
}
@Test(expected = IllegalArgumentException.class)
public void createNullType() {
new StandardImageMetadataSupport(builder(null));
}
@Test(expected = IllegalArgumentException.class)
public void builderNullType() {
builder(null).build();
}
@Test
public void createValid() {
IIOMetadata metadata = new StandardImageMetadataSupport(builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
assertNotNull(metadata);
}
@Test
public void builderValid() {
IIOMetadata metadata = builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB))
.build();
assertNotNull(metadata);
}
@Test
public void compressionValuesUnspecified() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.build();
assertNull(metadata.getStandardCompressionNode());
}
@Test
public void compressionValuesNone() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionTypeName("nOnE") // Case-insensitive
.build();
assertNull(metadata.getStandardCompressionNode());
}
@Test
public void compressionValuesName() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionTypeName("foo")
.build();
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
assertNotNull(compressionNode);
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
assertEquals("foo", compressionName.getAttribute("value"));
// Defaults to lossless true
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
assertEquals("TRUE", compressionLossless.getAttribute("value"));
}
@Test(expected = IllegalArgumentException.class)
public void withCompressionLossyIllegal() {
builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionLossless(false);
}
@Test
public void compressionValuesLossy() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionTypeName("bar")
.withCompressionLossless(false)
.build();
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
assertNotNull(compressionNode);
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
assertEquals("bar", compressionName.getAttribute("value"));
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
assertEquals("FALSE", compressionLossless.getAttribute("value"));
}
@Test
public void withDocumentValuesDefault() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.build();
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
assertNull(documentNode);
}
@Test
public void withDocumentValues() {
Calendar creationTime = Calendar.getInstance();
creationTime.set(2022, Calendar.SEPTEMBER, 8, 14, 5, 0);
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withFormatVersion("42")
.withDocumentCreationTime(creationTime)
.build();
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
assertNotNull(documentNode);
IIOMetadataNode formatVersion = (IIOMetadataNode) documentNode.getElementsByTagName("FormatVersion").item(0);
assertEquals("42", formatVersion.getAttribute("value"));
IIOMetadataNode imageCreationTime = (IIOMetadataNode) documentNode.getElementsByTagName("ImageCreationTime").item(0);
assertEquals("2022", imageCreationTime.getAttribute("year"));
assertEquals("9", imageCreationTime.getAttribute("month"));
assertEquals("8", imageCreationTime.getAttribute("day"));
assertEquals("14", imageCreationTime.getAttribute("hour"));
assertEquals("5", imageCreationTime.getAttribute("minute"));
assertEquals("0", imageCreationTime.getAttribute("second"));
}
@Test
public void withTextValuesDefault() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNull(textNode);
}
@Test
public void withTextValuesSingle() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withTextEntry("foo", "bar")
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNotNull(textNode);
IIOMetadataNode textEntry = (IIOMetadataNode) textNode.getElementsByTagName("TextEntry").item(0);
assertEquals("foo", textEntry.getAttribute("keyword"));
assertEquals("bar", textEntry.getAttribute("value"));
}
@Test
public void withTextValuesMap() {
Map<String, String> entries = new LinkedHashMap<>();
entries.put("foo", "bar");
entries.put("bar", "xyzzy");
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withTextEntries(entries)
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNotNull(textNode);
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
assertEquals(entries.size(), textEntries.getLength());
int i = 0;
for (Entry<String, String> entry : entries.entrySet()) {
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
assertEquals(entry.getKey(), textEntry.getAttribute("keyword"));
assertEquals(entry.getValue(), textEntry.getAttribute("value"));
i++;
}
}
@Test
public void withTextValuesList() {
List<TextEntry> entries = Arrays.asList(
new TextEntry(null, "foo"), // No key allowed
new TextEntry("foo", "bar"),
new TextEntry("bar", "xyzzy"),
new TextEntry("bar", "nothing happens..."), // Duplicates allowed
new TextEntry("everything", "válüè", "unknown", "UTF-8", "zip")
);
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withTextEntries(entries)
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNotNull(textNode);
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
assertEquals(entries.size(), textEntries.getLength());
for (int i = 0; i < entries.size(); i++) {
TextEntry entry = entries.get(i);
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
assertAttributeEqualOrAbsent(entry.keyword, textEntry, "keyword");
assertEquals(entry.value, textEntry.getAttribute("value"));
assertAttributeEqualOrAbsent(entry.language, textEntry, "language");
assertAttributeEqualOrAbsent(entry.encoding, textEntry, "encoding");
assertAttributeEqualOrAbsent(entry.compression, textEntry, "compression");
}
}
private static void assertAttributeEqualOrAbsent(final String expectedValue, IIOMetadataNode node, final String attribute) {
if (expectedValue != null) {
assertEquals(expectedValue, node.getAttribute(attribute));
}
else {
assertFalse(node.hasAttribute(attribute));
}
}
@Test
public void withPlanarColorspaceType() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList(
"XYZ", "Lab", "Luv", "YCbCr", "Yxy", "YCCK", "PhotoYCC",
"RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY",
"2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR",
"9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR"
);
for (ColorSpaceType value : ColorSpaceType.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withColorSpaceType(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardChromaNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ColorSpaceType").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("name")); // Format oddity: Why is this not "value"?
assertTrue(allowedValues.contains(value.toString()));
}
}
@Test
public void withPlanarConfiguration() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList("PixelInterleaved", "PlaneInterleaved", "LineInterleaved", "TileInterleaved");
for (PlanarConfiguration value : PlanarConfiguration.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR))
.withPlanarConfiguration(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardDataNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("PlanarConfiguration").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
assertTrue(allowedValues.contains(value.toString()));
}
}
@Test
public void withImageOrientation() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList("Normal", "Rotate90", "Rotate180", "Rotate270", "FlipH", "FlipV", "FlipHRotate90", "FlipVRotate90");
for (ImageOrientation value : ImageOrientation.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withOrientation(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardDimensionNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ImageOrientation").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
assertTrue(allowedValues.contains(value.toString()));
}
}
@Test
public void withSubimageInterpretation() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList(
"Standalone", "SinglePage", "FullResolution", "ReducedResolution", "PyramidLayer",
"Preview", "VolumeSlice", "ObjectView", "Panorama", "AnimationFrame",
"TransparencyMask", "CompositingLayer", "SpectralSlice", "Unknown"
);
for (SubimageInterpretation value : SubimageInterpretation.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB))
.withSubimageInterpretation(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("SubimageInterpretation").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
assertTrue(allowedValues.contains(value.toString()));
}
}
}
@@ -36,11 +36,9 @@ 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 {
@@ -58,8 +56,6 @@ 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);
@@ -73,17 +69,6 @@ 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,7 +34,6 @@ 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;
@@ -47,8 +46,6 @@ 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);
@@ -1,434 +0,0 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
/**
* BufferedFileImageInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @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!
public class BufferedChannelImageInputStreamFileCacheTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) {
random.nextBytes(data);
return new ByteArrayInputStream(data);
}
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(new ByteArrayInputStream(new byte[0]), null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
}
}
@Test
public void testCreateNullStream() throws IOException {
try {
new FileCache((InputStream) null, null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testCreateNullChannel() throws IOException {
try {
new FileCache((ReadableByteChannel) null, null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9];
for (int i = 0; i < data.length / result.length; i++) {
// Read backwards
long newPos = data.length - result.length - i * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@Test
public void testReadOutsideDataSeek0Read() throws IOException {
byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2];
stream.read(buffer);
stream.seek(0);
assertNotEquals(-1, stream.read());
assertNotEquals(-1, stream.read(buffer));
}
}
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2 % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testClose() throws IOException {
// Create wrapper stream
Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close();
verify(cache, only()).close();
}
@Test
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
// See #606 for details.
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
// Ie: Relies on read to return all bytes at once, without blocking
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
assertEquals(size, len + head);
assertArrayEquals(bytes, result);
}
}
}
@@ -1,452 +0,0 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
/**
* BufferedFileImageInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
// TODO: Remove this test, and instead test the memory cache directly!
public class BufferedChannelImageInputStreamMemoryCacheTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) {
random.nextBytes(data);
return new ByteArrayInputStream(data);
}
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(new ByteArrayInputStream(new byte[0])))) {
assertEquals("Stream length should be unknown", -1, stream.length());
}
}
@Test
public void testCreateNullStream() {
try {
new MemoryCache((InputStream) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testCreateNullChannel() {
try {
new MemoryCache((ReadableByteChannel) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9];
for (int i = 0; i < data.length / result.length; i++) {
// Read backwards
long newPos = data.length - result.length - i * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@Test
public void testReadOutsideDataSeek0Read() throws IOException {
byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2];
stream.read(buffer);
stream.seek(0);
assertNotEquals(-1, stream.read());
assertNotEquals(-1, stream.read(buffer));
}
}
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2 % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
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
public void testClose() throws IOException {
// Create wrapper stream
Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close();
verify(cache, only()).close();
}
@Test
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
// See #606 for details.
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
// Ie: Relies on read to return all bytes at once, without blocking
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
assertEquals(size, len + head);
assertArrayEquals(bytes, result);
}
}
}
@@ -1,431 +0,0 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* BufferedFileImageInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
public class BufferedChannelImageInputStreamTest {
private final Random random = new Random(170984354357234566L);
private File randomDataToFile(byte[] data) throws IOException {
random.nextBytes(data);
File file = File.createTempFile("read", ".tmp");
Files.write(file.toPath(), data);
return file;
}
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(File.createTempFile("empty", ".tmp")))) {
assertEquals("Data length should be same as stream length", 0, stream.length());
}
}
@Test
public void testCreateNullFileInputStream() {
try {
new BufferedChannelImageInputStream((FileInputStream) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("inputstream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testCreateNullByteChannel() {
try {
new BufferedChannelImageInputStream((SeekableByteChannel) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 1024];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[1024 * 18];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] result = new byte[9];
for (int i = 0; i < data.length / result.length; i++) {
// Read backwards
long newPos = stream.length() - result.length - i * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@Test
public void testReadOutsideDataSeek0Read() throws IOException {
byte[] data = new byte[256];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] buffer = new byte[data.length * 2];
stream.read(buffer);
stream.seek(0);
assertNotEquals(-1, stream.read());
assertNotEquals(-1, stream.read(buffer));
}
}
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
File file = randomDataToFile(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
File file = randomDataToFile(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
File file = randomDataToFile(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2 % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
File file = randomDataToFile(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
File file = randomDataToFile(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
File file = randomDataToFile(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
File file = randomDataToFile(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testCloseChannel() throws IOException {
SeekableByteChannel channel = mock(SeekableByteChannel.class);
ImageInputStream stream = new BufferedChannelImageInputStream(channel);
stream.close();
verify(channel, never()).close();
}
@Test
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
// See #606 for details.
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
// Ie: Relies on read to return all bytes at once, without blocking
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
byte[] bytes = new byte[size];
File file = randomDataToFile(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
assertEquals(size, len + head);
assertArrayEquals(bytes, result);
}
}
}
@@ -45,9 +45,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.*;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
/** /**
* BufferedFileImageInputStreamTestCase * BufferedFileImageInputStreamTestCase
@@ -56,7 +54,6 @@ import static org.mockito.Mockito.verify;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @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$
*/ */
@Deprecated
public class BufferedFileImageInputStreamTest { public class BufferedFileImageInputStreamTest {
private final Random random = new Random(170984354357234566L); private final Random random = new Random(170984354357234566L);
@@ -75,7 +72,6 @@ public class BufferedFileImageInputStreamTest {
} }
} }
@SuppressWarnings("resource")
@Test @Test
public void testCreateNullFile() throws IOException { public void testCreateNullFile() throws IOException {
try { try {
@@ -1,26 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.spi.ImageInputStreamSpi;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* BufferedInputStreamImageInputStreamSpiTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
*/
public class BufferedFileInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
@Override
protected ImageInputStreamSpi createProvider() {
return new BufferedInputStreamImageInputStreamSpi();
}
@Override
protected InputStream createInput() throws IOException {
return Files.newInputStream(File.createTempFile("test-", ".tst").toPath());
}
}
@@ -1,24 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.spi.ImageInputStreamSpi;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* BufferedInputStreamImageInputStreamSpiTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
*/
public class BufferedInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
@Override
protected ImageInputStreamSpi createProvider() {
return new BufferedInputStreamImageInputStreamSpi();
}
@Override
protected InputStream createInput() {
return new ByteArrayInputStream(new byte[0]);
}
}
@@ -31,7 +31,6 @@
package com.twelvemonkeys.imageio.util; package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi; import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import com.twelvemonkeys.lang.Validate;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@@ -39,18 +38,14 @@ import org.mockito.InOrder;
import org.mockito.invocation.InvocationOnMock; import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import javax.imageio.IIOException; import javax.imageio.*;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.event.IIOReadProgressListener; import javax.imageio.event.IIOReadProgressListener;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.IIORegistry; import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.geom.*; import java.awt.geom.AffineTransform;
import java.awt.image.*; import java.awt.image.*;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@@ -58,7 +53,6 @@ import java.lang.reflect.ParameterizedType;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@@ -608,7 +602,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
assertReadWithSourceRegionParamEqualImage(new Rectangle(3, 3, 9, 9), getTestData().get(0), 0); assertReadWithSourceRegionParamEqualImage(new Rectangle(3, 3, 9, 9), getTestData().get(0), 0);
} }
protected void assertReadWithSourceRegionParamEqualImage(final Rectangle r, final TestData data, @SuppressWarnings("SameParameterValue") final int imageIndex) throws IOException { protected void assertReadWithSourceRegionParamEqualImage(final Rectangle r, final TestData data, final int imageIndex) throws IOException {
ImageReader reader = createReader(); ImageReader reader = createReader();
try (ImageInputStream inputStream = data.getInputStream()) { try (ImageInputStream inputStream = data.getInputStream()) {
reader.setInput(inputStream); reader.setInput(inputStream);
@@ -1834,51 +1828,52 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
private final List<Dimension> sizes; private final List<Dimension> sizes;
private final List<BufferedImage> images; private final List<BufferedImage> images;
public TestData(final Object input, final Dimension... dimensions) { public TestData(final Object pInput, final Dimension... pSizes) {
this(input, Arrays.asList(dimensions), null); this(pInput, Arrays.asList(pSizes), null);
} }
public TestData(final Object input, final BufferedImage... images) { public TestData(final Object pInput, final BufferedImage... pImages) {
this(input, null, Arrays.asList(images)); this(pInput, null, Arrays.asList(pImages));
} }
public TestData(final Object input, final List<Dimension> dimensions, final List<BufferedImage> images) { public TestData(final Object pInput, final List<Dimension> pSizes, final List<BufferedImage> pImages) {
Validate.notNull(input, "input"); if (pInput == null) {
Validate.isTrue(dimensions != null || images != null, "Need either dimensions or image"); throw new IllegalArgumentException("input == null");
}
List<Dimension> combinedDimensions; sizes = new ArrayList<>();
if (dimensions == null) { images = new ArrayList<>();
// Copy dimensions from images
combinedDimensions = new ArrayList<>(images.size());
for (BufferedImage image : images) { List<Dimension> sizes = pSizes;
combinedDimensions.add(new Dimension(image.getWidth(), image.getHeight())); if (sizes == null) {
sizes = new ArrayList<>();
if (pImages != null) {
for (BufferedImage image : pImages) {
sizes.add(new Dimension(image.getWidth(), image.getHeight()));
} }
} }
else { else {
// Validate equal dimensions throw new IllegalArgumentException("Need either size or image");
if (images != null) { }
if (images.size() != dimensions.size()) { }
throw new IllegalArgumentException("Dimensions and images parameter's size differs"); else if (pImages != null) {
if (pImages.size() != pSizes.size()) {
throw new IllegalArgumentException("Size parameter and image size differs");
}
for (int i = 0; i < sizes.size(); i++) {
if (!new Dimension(pImages.get(i).getWidth(), pImages.get(i).getHeight()).equals(sizes.get(i))) {
throw new IllegalArgumentException("Size parameter and image size differs");
} }
for (int i = 0; i < dimensions.size(); i++) {
if (!new Dimension(images.get(i).getWidth(), images.get(i).getHeight()).equals(dimensions.get(i))) {
throw new IllegalArgumentException("Dimensions and images parameter's dimensions differ");
}
} }
} }
combinedDimensions = new ArrayList<>(dimensions); this.sizes.addAll(sizes);
if (pImages != null) {
images.addAll(pImages);
} }
this.sizes = Collections.unmodifiableList(combinedDimensions); input = pInput;
this.images = images != null
? Collections.unmodifiableList(new ArrayList<>(images))
: Collections.<BufferedImage>emptyList();
this.input = input;
} }
public Object getInput() { public Object getInput() {
@@ -1903,13 +1898,13 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
return sizes.size(); return sizes.size();
} }
public Dimension getDimension(final int index) { public Dimension getDimension(final int pIndex) {
return sizes.get(index); return sizes.get(pIndex);
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
public BufferedImage getImage(final int index) { public BufferedImage getImage(final int pIndex) {
return images.get(index); return images.get(pIndex);
} }
@Override @Override
@@ -30,16 +30,20 @@
package com.twelvemonkeys.imageio.util; package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.lang.Validate; import static org.junit.Assert.assertEquals;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import javax.imageio.ImageTypeSpecifier;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageTypeSpecifier; import com.twelvemonkeys.lang.Validate;
import java.awt.color.*;
import java.awt.image.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
public class ImageTypeSpecifiersTest { public class ImageTypeSpecifiersTest {
@@ -66,19 +70,12 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier expected; ImageTypeSpecifier expected;
switch (type) { switch (type) {
// Special handling for INT_RGB and BGR, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits)
case BufferedImage.TYPE_INT_RGB:
expected = createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false);
break;
case BufferedImage.TYPE_INT_BGR:
expected = createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false);
break;
// Special handling for USHORT_565 and 555, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits) // Special handling for USHORT_565 and 555, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits)
case BufferedImage.TYPE_USHORT_565_RGB: case BufferedImage.TYPE_USHORT_565_RGB:
expected = createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); expected = createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
break; break;
case BufferedImage.TYPE_USHORT_555_RGB: case BufferedImage.TYPE_USHORT_555_RGB:
expected = createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false); expected = createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
break; break;
default: default:
expected = ImageTypeSpecifier.createFromBufferedImageType(type); expected = ImageTypeSpecifier.createFromBufferedImageType(type);
@@ -89,24 +86,12 @@ public class ImageTypeSpecifiersTest {
} }
@Test @Test
public void testCreatePacked24() { public void testCreatePacked32() {
// TYPE_INT_RGB // TYPE_INT_RGB
assertEquals( assertEquals(
createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false), ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false) ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false)
); );
// TYPE_INT_BGR
assertEquals(
createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
);
// Extra: Make sure color models bits is actually 24 (ImageTypeSpecifier equivalent returns 32)
assertEquals(24, ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false).getColorModel().getPixelSize());
}
@Test
public void testCreatePacked32() {
// TYPE_INT_ARGB // TYPE_INT_ARGB
assertEquals( assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, false), ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, false),
@@ -117,36 +102,35 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true), ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true),
ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true) ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true)
); );
} // TYPE_INT_BGR
@Test
public void testCreatePacked15() {
// TYPE_USHORT_555_RGB
assertEquals( assertEquals(
createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false) ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
); );
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
// Extra: Make sure color models bits is actually 15 (ImageTypeSpecifier equivalent returns 32)
assertEquals(15, ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
} }
@Test @Test
public void testCreatePacked16() { public void testCreatePacked16() {
// TYPE_USHORT_555_RGB
assertEquals(
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
// TYPE_USHORT_565_RGB // TYPE_USHORT_565_RGB
assertEquals( assertEquals(
createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false), createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false) ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
); );
// "USHORT 4444 ARGB" // "USHORT 4444 ARGB"
assertEquals( assertEquals(
createPacked(sRGB, 16,0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false), createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false) ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
); );
// "USHORT 4444 ARGB PRE" // "USHORT 4444 ARGB PRE"
assertEquals( assertEquals(
createPacked(sRGB, 16, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true), createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true) ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
); );
@@ -158,17 +142,17 @@ public class ImageTypeSpecifiersTest {
public void testCreatePacked8() { public void testCreatePacked8() {
// "BYTE 332 RGB" // "BYTE 332 RGB"
assertEquals( assertEquals(
createPacked(sRGB, 8, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false), createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false) ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
); );
// "BYTE 2222 ARGB" // "BYTE 2222 ARGB"
assertEquals( assertEquals(
createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false), createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false) ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
); );
// "BYTE 2222 ARGB PRE" // "BYTE 2222 ARGB PRE"
assertEquals( assertEquals(
createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true), createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true) ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
); );
@@ -176,12 +160,15 @@ public class ImageTypeSpecifiersTest {
assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize()); assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
} }
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, final int bits, private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
final int redMask, final int greenMask, final int blueMask, final int alphaMask, final int redMask, final int greenMask, final int blueMask, final int alphaMask,
final int transferType, final boolean isAlphaPremultiplied) { final int transferType, final boolean isAlphaPremultiplied) {
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT || transferType == DataBuffer.TYPE_INT, transferType, "transferType: %s"); Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType); int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel =
new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1)); return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
} }
@@ -729,63 +716,10 @@ public class ImageTypeSpecifiersTest {
); );
} }
@Test
public void testCreateFromBufferedImageTypeShouldEqualConstructor() {
for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) {
BufferedImage image = new BufferedImage(1, 1, type);
ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image);
ImageTypeSpecifier fromType = ImageTypeSpecifiers.createFromBufferedImageType(type);
assertEquals(fromConstructor.getColorModel(), fromType.getColorModel());
assertEquals(fromConstructor.getSampleModel(), fromType.getSampleModel());
}
}
@Test
public void testCreateFromRenderedImageShouldEqualConstructor() {
for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) {
BufferedImage image = new BufferedImage(1, 1, type);
ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image);
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(fromConstructor.getColorModel(), fromImage.getColorModel());
assertEquals(fromConstructor.getSampleModel(), fromImage.getSampleModel());
}
}
@Test
public void testCreateFromRenderedImageIndexedBinaryShouldRetainPalette() {
IndexColorModel whiteIsZero = new IndexColorModel(1, 2, new int[]{0xFFFFFFFF, 0xFF000000}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY, whiteIsZero);
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(whiteIsZero, fromImage.getColorModel());
assertSame(whiteIsZero, fromImage.getColorModel()); // Note: This can be relaxed to asserting the LUTs are equal
assertEquals(image.getSampleModel(), fromImage.getSampleModel());
}
@Test
public void testCreateFromRenderedImageIndexedShouldRetainPalette() {
IndexColorModel palette = new IndexColorModel(4, 16, new int[]{
0xFFFFFFFF, 0xFF999999, 0xFF666666, 0xFF333333,
0xFF000000, 0xFF00202E, 0xFF003F5C, 0xFF2C4875,
0xFF8A508F, 0xFFBC5090, 0xFFFF6361, 0xFFFF8531,
0xFFFFA600, 0xFFFFD380, 0xFF74A892, 0xFF008585
}, 0, false, -1, DataBuffer.TYPE_BYTE);
BufferedImage image = new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED, palette);
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(palette, fromImage.getColorModel());
assertSame(palette, fromImage.getColorModel()); // Note: This can be relaxed to asserting the LUTs are equal
assertEquals(image.getSampleModel(), fromImage.getSampleModel());
}
private static byte[] createByteLut(final int count) { private static byte[] createByteLut(final int count) {
byte[] lut = new byte[count]; byte[] lut = new byte[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
lut[i] = (byte) (i * 255 / count); lut[i] = (byte) count;
} }
return lut; return lut;
} }
@@ -794,8 +728,7 @@ public class ImageTypeSpecifiersTest {
int[] lut = new int[count]; int[] lut = new int[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
int val = (i * 255 / count); lut[i] = 0xff000000 | count << 16 | count << 8 | count;
lut[i] = 0xff000000 | val << 16 | val << 8 | val;
} }
return lut; return lut;
@@ -43,17 +43,15 @@ import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import java.awt.*; import java.awt.*;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.net.URL; import java.net.URL;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.*;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
/** /**
@@ -110,8 +108,8 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
return getTestData().get(index); return getTestData().get(index);
} }
protected URL getClassLoaderResource(final String name) { protected URL getClassLoaderResource(final String pName) {
return getClass().getResource(name); return getClass().getResource(pName);
} }
@Test @Test
+1 -18
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.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-hdr</artifactId> <artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name> <name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
@@ -32,21 +32,4 @@
<artifactId>imageio-metadata</artifactId> <artifactId>imageio-metadata</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
@@ -40,8 +40,11 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
@@ -59,7 +62,7 @@ public final class HDRImageReader extends ImageReaderBase {
private HDRHeader header; private HDRHeader header;
HDRImageReader(final ImageReaderSpi provider) { protected HDRImageReader(final ImageReaderSpi provider) {
super(provider); super(provider);
} }
@@ -196,7 +199,7 @@ public final class HDRImageReader extends ImageReaderBase {
int ySub = param != null ? param.getSourceYSubsampling() : 1; int ySub = param != null ? param.getSourceYSubsampling() : 1;
byte[] rowRGBE = new byte[width * 4]; byte[] rowRGBE = new byte[width * 4];
byte[] pixelRGBE = new byte[4]; byte[] pixelRGBE = new byte[width];
processImageStarted(imageIndex); processImageStarted(imageIndex);
@@ -231,7 +234,7 @@ public final class HDRImageReader extends ImageReaderBase {
processImageComplete(); processImageComplete();
return raster; return destination.getRaster();
} }
@Override @Override
@@ -241,7 +244,10 @@ public final class HDRImageReader extends ImageReaderBase {
@Override @Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException { public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return new HDRMetadata(getRawImageType(imageIndex), header); checkBounds(imageIndex);
readHeader();
return new HDRMetadata(header);
} }
public static void main(final String[] args) throws IOException { public static void main(final String[] args) throws IOException {
@@ -1,19 +1,83 @@
/*
* Copyright (c) 2015, 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.hdr; package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport; import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
final class HDRMetadata extends StandardImageMetadataSupport { final class HDRMetadata extends AbstractMetadata {
public HDRMetadata(ImageTypeSpecifier type, HDRHeader header) { private final HDRHeader header;
super(builder(type)
.withCompressionTypeName("RLE") HDRMetadata(final HDRHeader header) {
.withTextEntry("Software", header.getSoftware())); this.header = header;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
csType.setAttribute("name", "RGB");
// TODO: Support XYZ
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", "3");
chroma.appendChild(numChannels);
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
blackIsZero.setAttribute("value", "TRUE");
chroma.appendChild(blackIsZero);
return chroma;
}
// No compression
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "RLE");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
return node;
} }
// For HDR, the stored sample data is UnsignedIntegral and data is 4 channels (RGB+Exp),
// but decoded to Real (float) 3 chanel RGB
@Override @Override
protected IIOMetadataNode getStandardDataNode() { protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data"); IIOMetadataNode node = new IIOMetadataNode("Data");
@@ -28,4 +92,38 @@ final class HDRMetadata extends StandardImageMetadataSupport {
return node; return node;
} }
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
// TODO: Support other orientations
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "Normal");
dimension.appendChild(imageOrientation);
return dimension;
}
// No document node
@Override
protected IIOMetadataNode getStandardTextNode() {
if (header.getSoftware() != null) {
IIOMetadataNode text = new IIOMetadataNode("Text");
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
textEntry.setAttribute("keyword", "Software");
textEntry.setAttribute("value", header.getSoftware());
text.appendChild(textEntry);
return text;
}
return null;
}
// No tiling
// No transparency
} }
+1 -20
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.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-icns</artifactId> <artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name> <name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
@@ -26,23 +26,4 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi,
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageWriterSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
@@ -1,41 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.icns;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import javax.imageio.ImageTypeSpecifier;
final class ICNSImageMetadata extends StandardImageMetadataSupport {
ICNSImageMetadata(ImageTypeSpecifier type, String compressionName) {
super(builder(type).withCompressionTypeName(compressionName));
}
}
@@ -35,16 +35,11 @@ import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOException; import javax.imageio.*;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.image.*; import java.awt.image.*;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.File; import java.io.File;
@@ -66,9 +61,10 @@ import java.util.List;
* @see <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a> * @see <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a>
*/ */
public final class ICNSImageReader extends ImageReaderBase { public final class ICNSImageReader extends ImageReaderBase {
// TODO: Support ToC resource for faster parsing/faster determine number of icons?
// TODO: Subsampled reading for completeness, even if never used? // TODO: Subsampled reading for completeness, even if never used?
private final List<IconResource> icons = new ArrayList<>(); private List<IconResource> icons = new ArrayList<IconResource>();
private final List<IconResource> masks = new ArrayList<>(); private List<IconResource> masks = new ArrayList<IconResource>();
private IconResource lastResourceRead; private IconResource lastResourceRead;
private int length; private int length;
@@ -140,7 +136,7 @@ public final class ICNSImageReader extends ImageReaderBase {
ImageTypeSpecifier rawType = getRawImageType(imageIndex); ImageTypeSpecifier rawType = getRawImageType(imageIndex);
IconResource resource = readIconResource(imageIndex); IconResource resource = readIconResource(imageIndex);
List<ImageTypeSpecifier> specifiers = new ArrayList<>(); List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
switch (resource.depth()) { switch (resource.depth()) {
case 1: case 1:
@@ -234,9 +230,14 @@ public final class ICNSImageReader extends ImageReaderBase {
packedSize -= 4; packedSize -= 4;
} }
try (InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize)) { InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
try {
ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
} }
finally {
input.close();
}
} }
else { else {
data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE]; data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE];
@@ -490,7 +491,7 @@ public final class ICNSImageReader extends ImageReaderBase {
String format; String format;
if (Arrays.equals(ICNS.PNG_MAGIC, Arrays.copyOfRange(magic, 0, ICNS.PNG_MAGIC.length))) { if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
format = "PNG"; format = "PNG";
} }
else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) { else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) {
@@ -526,6 +527,7 @@ public final class ICNSImageReader extends ImageReaderBase {
IconResource resource = IconResource.read(imageInput); IconResource resource = IconResource.read(imageInput);
if (resource.isTOC()) { if (resource.isTOC()) {
// TODO: IconResource.readTOC()?
int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE; int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE;
long pos = resource.start + resource.length; long pos = resource.start + resource.length;
@@ -568,23 +570,6 @@ public final class ICNSImageReader extends ImageReaderBase {
} }
} }
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
IconResource resource = readIconResource(imageIndex);
String compressionName;
if (resource.isForeignFormat()) {
// Special handling of PNG/JPEG 2000 icons
imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE);
compressionName = getForeignFormat(imageInput);
}
else {
compressionName = resource.isCompressed() ? "RLE" : "None";
}
return new ICNSImageMetadata(getRawImageType(imageIndex), compressionName);
}
private static final class ICNSBitMaskColorModel extends IndexColorModel { private static final class ICNSBitMaskColorModel extends IndexColorModel {
static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel(); static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel();
@@ -593,6 +578,7 @@ public final class ICNSImageReader extends ImageReaderBase {
} }
} }
@SuppressWarnings({"UnusedAssignment"})
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
int argIndex = 0; int argIndex = 0;
@@ -34,13 +34,7 @@ import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream; import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import javax.imageio.IIOException; import javax.imageio.*;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi; import javax.imageio.spi.ImageWriterSpi;
@@ -110,7 +104,6 @@ public final class ICNSImageWriter extends ImageWriterBase {
sequenceIndex = 0; sequenceIndex = 0;
} }
@SuppressWarnings("RedundantThrows")
@Override @Override
public void endWriteSequence() throws IOException { public void endWriteSequence() throws IOException {
assertOutput(); assertOutput();
@@ -38,13 +38,8 @@ import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam; import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader; import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.io.BufferedReader; import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator; import java.util.Iterator;
/** /**
@@ -145,12 +140,17 @@ final class SipsJP2Reader {
} }
private static String checkErrorMessage(final Process process) throws IOException { private static String checkErrorMessage(final Process process) throws IOException {
try (InputStream stream = process.getErrorStream()) { InputStream stream = process.getErrorStream();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String message = reader.readLine(); String message = reader.readLine();
return message != null && message.startsWith("Error: ") ? message.substring(7) : null; return message != null && message.startsWith("Error: ") ? message.substring(7) : null;
} }
finally {
stream.close();
}
} }
private static String[] buildCommand(final File sipsCommand, final File tempFile) { private static String[] buildCommand(final File sipsCommand, final File tempFile) {
@@ -159,13 +159,19 @@ final class SipsJP2Reader {
}; };
} }
private static File dumpToFile(final ImageInputStream stream) throws IOException { private static File dumpToFile(final ImageInputStream stream) throws IOException {
File tempFile = File.createTempFile("imageio-icns-", ".png"); File tempFile = File.createTempFile("imageio-icns-", ".png");
tempFile.deleteOnExit(); tempFile.deleteOnExit();
try (FileOutputStream out = new FileOutputStream(tempFile)) { FileOutputStream out = new FileOutputStream(tempFile);
try {
FileUtil.copy(IIOUtil.createStreamAdapter(stream), out); FileUtil.copy(IIOUtil.createStreamAdapter(stream), out);
} }
finally {
out.close();
}
return tempFile; return tempFile;
} }
+1 -20
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.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-iff</artifactId> <artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name> <name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
@@ -29,23 +29,4 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi,
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageWriterSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
@@ -1,13 +1,13 @@
package com.twelvemonkeys.imageio.plugins.iff; package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import java.awt.image.*; import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.twelvemonkeys.imageio.plugins.iff.IFF.*;
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr; import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
import static com.twelvemonkeys.lang.Validate.isTrue;
/** /**
* Form. * Form.
@@ -27,7 +27,7 @@ abstract class Form {
abstract int width(); abstract int width();
abstract int height(); abstract int height();
abstract double aspect(); abstract float aspect();
abstract int bitplanes(); abstract int bitplanes();
abstract int compressionType(); abstract int compressionType();
@@ -118,7 +118,7 @@ abstract class Form {
} }
private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) { private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) {
super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s")); super(formType);
this.bitmapHeader = bitmapHeader; this.bitmapHeader = bitmapHeader;
this.viewMode = viewMode; this.viewMode = viewMode;
this.colorMap = colorMap; this.colorMap = colorMap;
@@ -127,19 +127,6 @@ abstract class Form {
this.body = body; this.body = body;
} }
private static boolean validFormType(int formType) {
switch (formType) {
case TYPE_ACBM:
case TYPE_ILBM:
case TYPE_PBM:
case TYPE_RGB8:
case TYPE_RGBN:
return true;
default:
return false;
}
}
@Override @Override
int width() { int width() {
return bitmapHeader.width; return bitmapHeader.width;
@@ -161,8 +148,8 @@ abstract class Form {
} }
@Override @Override
double aspect() { float aspect() {
return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (double) bitmapHeader.yAspect); return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (float) bitmapHeader.yAspect);
} }
@Override @Override
@@ -310,7 +297,7 @@ abstract class Form {
} }
private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) { private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) {
super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s")); super(formType);
this.deepGlobal = deepGlobal; this.deepGlobal = deepGlobal;
this.deepLocation = deepLocation; this.deepLocation = deepLocation;
this.deepPixel = deepPixel; this.deepPixel = deepPixel;
@@ -318,15 +305,6 @@ abstract class Form {
this.body = body; this.body = body;
} }
private static boolean validFormType(int formType) {
switch (formType) {
case TYPE_DEEP:
case TYPE_TVPP:
return true;
default:
return false;
}
}
@Override @Override
int width() { int width() {
@@ -359,8 +337,8 @@ abstract class Form {
} }
@Override @Override
double aspect() { float aspect() {
return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (double) deepGlobal.yAspect; return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (float) deepGlobal.yAspect;
} }
@Override @Override
@@ -1,83 +1,188 @@
/*
* Copyright (c) 2020, 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.iff; package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport; import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*; import java.awt.*;
import java.awt.image.IndexColorModel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.twelvemonkeys.imageio.plugins.iff.IFF.*; import static com.twelvemonkeys.imageio.plugins.iff.IFF.*;
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr; import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
import static java.util.Collections.emptyList;
final class IFFImageMetadata extends StandardImageMetadataSupport { final class IFFImageMetadata extends AbstractMetadata {
IFFImageMetadata(ImageTypeSpecifier type, Form header, IndexColorModel palette) { private final Form header;
this(builder(type), notNull(header, "header"), palette); private final IndexColorModel colorMap;
private final List<GenericChunk> meta;
IFFImageMetadata(Form header, IndexColorModel colorMap) {
this.header = notNull(header, "header");
isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s");
this.colorMap = colorMap;
this.meta = header.meta;
} }
private IFFImageMetadata(Builder builder, Form header, IndexColorModel palette) { private boolean validFormType(int formType) {
super(builder.withPalette(palette) switch (formType) {
.withCompressionTypeName(compressionName(header))
.withBitsPerSample(bitsPerSample(header))
.withPlanarConfiguration(planarConfiguration(header))
.withPixelAspectRatio(header.aspect() != 0 ? header.aspect() : null)
.withFormatVersion("1.0")
.withTextEntries(textEntries(header)));
}
private static String compressionName(Form header) {
switch (header.compressionType()) {
case BMHDChunk.COMPRESSION_NONE:
return "None";
case BMHDChunk.COMPRESSION_BYTE_RUN:
return "RLE";
case 4:
// Compression type 4 means different things for different FORM types, we support
// Impulse RGB8 RLE compression: 24 bit RGB + 1 bit mask + 7 bit run count
if (header.formType == TYPE_RGB8) {
return "RGB8";
}
default: default:
return "Unknown"; return false;
case TYPE_ACBM:
case TYPE_DEEP:
case TYPE_ILBM:
case TYPE_PBM:
case TYPE_RGB8:
case TYPE_RGBN:
case TYPE_TVPP:
return true;
} }
} }
private static int[] bitsPerSample(Form header) { @Override
int bitplanes = header.bitplanes(); protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
switch (header.bitplanes()) {
case 8:
if (colorMap == null) {
csType.setAttribute("name", "GRAY");
break;
}
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 24:
case 25:
case 32:
csType.setAttribute("name", "RGB");
break;
default:
csType.setAttribute("name", "Unknown");
}
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
chroma.appendChild(numChannels);
if (colorMap == null && header.bitplanes() == 8) {
numChannels.setAttribute("value", Integer.toString(1));
}
else if (header.bitplanes() == 25 || header.bitplanes() == 32) {
numChannels.setAttribute("value", Integer.toString(4));
}
else {
numChannels.setAttribute("value", Integer.toString(3));
}
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
chroma.appendChild(blackIsZero);
blackIsZero.setAttribute("value", "TRUE");
// NOTE: TGA files may contain a color map, even if true color...
// Not sure if this is a good idea to expose to the meta data,
// as it might be unexpected... Then again...
if (colorMap != null) {
IIOMetadataNode palette = new IIOMetadataNode("Palette");
chroma.appendChild(palette);
for (int i = 0; i < colorMap.getMapSize(); i++) {
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
palette.appendChild(paletteEntry);
paletteEntry.setAttribute("index", Integer.toString(i));
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
}
if (colorMap.getTransparentPixel() != -1) {
IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex");
chroma.appendChild(backgroundIndex);
backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
}
}
// TODO: TVPP TVPaint Project files have a MIXR chunk with a background color
// and also a BGP1 (background pen 1?) and BGP2 chunks
// if (extensions != null && extensions.getBackgroundColor() != 0) {
// Color background = new Color(extensions.getBackgroundColor(), true);
//
// IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
// chroma.appendChild(backgroundColor);
//
// backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
// backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
// backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
// }
return chroma;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) {
return null; // All defaults
}
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "RLE");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
return node;
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode data = new IIOMetadataNode("Data");
// PlanarConfiguration
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
switch (header.formType) {
case TYPE_DEEP:
case TYPE_TVPP:
case TYPE_RGB8:
case TYPE_PBM:
planarConfiguration.setAttribute("value", "PixelInterleaved");
break;
case TYPE_ILBM:
planarConfiguration.setAttribute("value", "PlaneInterleaved");
break;
default:
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType));
break;
}
data.appendChild(planarConfiguration);
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
sampleFormat.setAttribute("value", colorMap != null ? "Index" : "UnsignedIntegral");
data.appendChild(sampleFormat);
// BitsPerSample
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
String value = bitsPerSampleValue(header.bitplanes());
bitsPerSample.setAttribute("value", value);
data.appendChild(bitsPerSample);
// SignificantBitsPerSample not in format
// SampleMSB not in format
return data;
}
private String bitsPerSampleValue(int bitplanes) {
switch (bitplanes) { switch (bitplanes) {
case 1: case 1:
case 2: case 2:
@@ -87,47 +192,91 @@ final class IFFImageMetadata extends StandardImageMetadataSupport {
case 6: case 6:
case 7: case 7:
case 8: case 8:
return new int[] {bitplanes}; return Integer.toString(bitplanes);
case 24: case 24:
return new int[] {8, 8, 8}; return "8 8 8";
case 25: case 25:
if (header.formType != TYPE_RGB8) { if (header.formType != TYPE_RGB8) {
throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType))); throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType)));
} }
return new int[] {8, 8, 8, 1}; return "8 8 8 1";
case 32: case 32:
return new int[] {8, 8, 8, 8}; return "8 8 8 8";
default: default:
throw new IllegalArgumentException("Unknown bit count: " + bitplanes); throw new IllegalArgumentException("Unknown bit count: " + bitplanes);
} }
} }
private static PlanarConfiguration planarConfiguration(Form header) { @Override
switch (header.formType) { protected IIOMetadataNode getStandardDimensionNode() {
case TYPE_DEEP: if (header.aspect() == 0) {
case TYPE_TVPP:
case TYPE_RGB8:
case TYPE_PBM:
return PlanarConfiguration.PixelInterleaved;
case TYPE_ILBM:
return PlanarConfiguration.PlaneInterleaved;
default:
return null; return null;
} }
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
// PixelAspectRatio
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", String.valueOf(header.aspect()));
dimension.appendChild(pixelAspectRatio);
// TODO: HorizontalScreenSize?
// TODO: VerticalScreenSize?
return dimension;
} }
private static List<TextEntry> textEntries(Form header) { @Override
if (header.meta.isEmpty()) { protected IIOMetadataNode getStandardDocumentNode() {
return emptyList(); IIOMetadataNode document = new IIOMetadataNode("Document");
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
document.appendChild(formatVersion);
formatVersion.setAttribute("value", "1.0");
return document;
} }
List<TextEntry> text = new ArrayList<>(); @Override
for (GenericChunk chunk : header.meta) { protected IIOMetadataNode getStandardTextNode() {
text.add(new TextEntry(toChunkStr(chunk.chunkId), if (meta.isEmpty()) {
new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8:StandardCharsets.US_ASCII))); return null;
}
IIOMetadataNode text = new IIOMetadataNode("Text");
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
for (GenericChunk chunk : meta) {
IIOMetadataNode node = new IIOMetadataNode("TextEntry");
node.setAttribute("keyword", IFFUtil.toChunkStr(chunk.chunkId));
node.setAttribute("value", new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII));
text.appendChild(node);
} }
return text; return text;
} }
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) {
return null;
}
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
if (header.bitplanes() == 25 || header.bitplanes() == 32) {
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied");
transparency.appendChild(alpha);
}
if (colorMap != null && colorMap.getTransparency() == Transparency.BITMASK) {
IIOMetadataNode transparentIndex = new IIOMetadataNode("TransparentIndex");
transparentIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
transparency.appendChild(transparentIndex);
}
return transparency;
}
} }
@@ -38,19 +38,14 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
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 javax.imageio.IIOException; import javax.imageio.*;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.image.*; import java.awt.image.*;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@@ -355,7 +350,9 @@ public final class IFFImageReader extends ImageReaderBase {
@Override @Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException { public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return new IFFImageMetadata(getRawImageType(imageIndex), header, header.colorMap()); init(imageIndex);
return new IFFImageMetadata(header, header.colorMap());
} }
@Override @Override
@@ -1,37 +1,24 @@
package com.twelvemonkeys.imageio.plugins.iff; package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import org.junit.Test; import org.junit.Test;
import org.junit.function.ThrowingRunnable; import org.junit.function.ThrowingRunnable;
import org.w3c.dom.Node; import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.IIOException; import javax.imageio.IIOException;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*; import java.awt.image.IndexColorModel;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static java.awt.image.BufferedImage.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class IFFImageMetadataTest { public class IFFImageMetadataTest {
private static final ImageTypeSpecifier TYPE_8_BIT_GRAY = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_GRAY);
private static final ImageTypeSpecifier TYPE_8_BIT_PALETTE = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_INDEXED);
private static final ImageTypeSpecifier TYPE_24_BIT_RGB = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_3BYTE_BGR);
private static final ImageTypeSpecifier TYPE_32_BIT_ARGB = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
private static final ImageTypeSpecifier TYPE_32_BIT_ARGB_DEEP = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE);
@Test @Test
public void testStandardFeatures() throws IIOException { public void testStandardFeatures() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
final IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); final IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Standard metadata format // Standard metadata format
assertTrue(metadata.isStandardMetadataFormatSupported()); assertTrue(metadata.isStandardMetadataFormatSupported());
@@ -64,9 +51,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma); assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName()); assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength()); assertEquals(3, chroma.getLength());
@@ -91,9 +78,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma); assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName()); assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength()); assertEquals(3, chroma.getLength());
@@ -119,10 +106,9 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1)); .with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
byte[] bw = {0, (byte) 0xff}; byte[] bw = {0, (byte) 0xff};
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma); assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName()); assertEquals("Chroma", chroma.getNodeName());
assertEquals(5, chroma.getLength()); assertEquals(5, chroma.getLength());
@@ -133,7 +119,7 @@ public class IFFImageMetadataTest {
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling(); IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
assertEquals("NumChannels", numChannels.getNodeName()); assertEquals("NumChannels", numChannels.getNodeName());
assertEquals("4", numChannels.getAttribute("value")); assertEquals("3", numChannels.getAttribute("value"));
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling(); IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
assertEquals("BlackIsZero", blackIsZero.getNodeName()); assertEquals("BlackIsZero", blackIsZero.getNodeName());
@@ -167,9 +153,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode compression = getStandardNode(metadata, "Compression"); IIOMetadataNode compression = metadata.getStandardCompressionNode();
assertNotNull(compression); assertNotNull(compression);
assertEquals("Compression", compression.getNodeName()); assertEquals("Compression", compression.getNodeName());
assertEquals(2, compression.getLength()); assertEquals(2, compression.getLength());
@@ -190,9 +176,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
assertNull(getStandardNode(metadata, "Compression")); // No compression, all default... assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
} }
@Test @Test
@@ -200,9 +186,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -227,9 +213,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -254,9 +240,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -283,10 +269,9 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0)); IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -312,9 +297,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_PBM) Form header = Form.ofType(IFF.TYPE_PBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -339,9 +324,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_PBM) Form header = Form.ofType(IFF.TYPE_PBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -371,20 +356,10 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader); .with(bitmapHeader);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNull(dimension);
if (dimension != null) {
assertEquals("Dimension", dimension.getNodeName());
assertEquals(1, dimension.getLength());
IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
}
} }
@Test @Test
@@ -393,24 +368,20 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(new CAMGChunk(4)); .with(new CAMGChunk(4));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); IIOMetadataNode dimension = metadata.getStandardDimensionNode();
// No Dimension node is okay, or one with an aspect ratio of 1.0 // No Dimension node is okay, or one with an aspect ratio of 1.0
if (dimension != null) { if (dimension != null) {
assertEquals("Dimension", dimension.getNodeName()); assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength()); assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value")); assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); assertNull(pixelAspectRatio.getNextSibling()); // No more children
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
} }
} }
@@ -427,22 +398,18 @@ public class IFFImageMetadataTest {
.with(bitmapHeader) .with(bitmapHeader)
.with(viewPort); .with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension); assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName()); assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength()); assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("2.0", pixelAspectRatio.getAttribute("value")); assertEquals("2.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); assertNull(pixelAspectRatio.getNextSibling()); // No more children
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
} }
@Test @Test
@@ -458,22 +425,18 @@ public class IFFImageMetadataTest {
.with(bitmapHeader) .with(bitmapHeader)
.with(viewPort); .with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension); assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName()); assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength()); assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("0.5", pixelAspectRatio.getAttribute("value")); assertEquals("0.5", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); assertNull(pixelAspectRatio.getNextSibling()); // No more children
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
} }
@Test @Test
@@ -484,22 +447,18 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(viewPort); .with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension"); IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension); assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName()); assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength()); assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName()); assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value")); assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling(); assertNull(pixelAspectRatio.getNextSibling()); // No more children
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
} }
@Test @Test
@@ -507,33 +466,32 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode document = getStandardNode(metadata, "Document"); IIOMetadataNode document = metadata.getStandardDocumentNode();
assertNotNull(document); assertNotNull(document);
assertEquals("Document", document.getNodeName()); assertEquals("Document", document.getNodeName());
assertEquals(1, document.getLength()); assertEquals(1, document.getLength());
IIOMetadataNode formatVersion = (IIOMetadataNode) document.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild();
assertEquals("FormatVersion", formatVersion.getNodeName()); assertEquals("FormatVersion", pixelAspectRatio.getNodeName());
assertEquals("1.0", formatVersion.getAttribute("value")); assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
assertNull(formatVersion.getNextSibling()); // No more children assertNull(pixelAspectRatio.getNextSibling()); // No more children
} }
@Test @Test
public void testStandardText() throws IIOException { public void testStandardText() throws IIOException {
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_ANNO, IFF.CHUNK_UTF8}; int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
String[] texts = {"annotation", "dupe", "äñnótâtïøñ"}; String[] texts = {"annotation", "äñnótâtïøñ"};
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)) .with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII))) .with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII)))
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.US_ASCII))) .with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8)));
.with(new GenericChunk(chunks[2], texts[2].getBytes(StandardCharsets.UTF_8)));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode text = getStandardNode(metadata, "Text"); IIOMetadataNode text = metadata.getStandardTextNode();
assertNotNull(text); assertNotNull(text);
assertEquals("Text", text.getNodeName()); assertEquals("Text", text.getNodeName());
assertEquals(texts.length, text.getLength()); assertEquals(texts.length, text.getLength());
@@ -551,21 +509,10 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNull(transparency); // No transparency, just defaults
if (transparency != null) {
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName());
assertEquals("none", alpha.getAttribute("value"));
assertNull(alpha.getNextSibling()); // No more children
}
// Otherwise, no transparency, just defaults
} }
@Test @Test
@@ -573,18 +520,18 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM) Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency); assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName()); assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength()); assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName()); assertEquals("Alpha", pixelAspectRatio.getNodeName());
assertEquals("nonpremultiplied", alpha.getAttribute("value")); assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value"));
assertNull(alpha.getNextSibling()); // No more children assertNull(pixelAspectRatio.getNextSibling()); // No more children
} }
@Test @Test
@@ -593,33 +540,28 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1)); .with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
byte[] bw = {0, (byte) 0xff}; byte[] bw = {0, (byte) 0xff};
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex())); IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency); assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName()); assertEquals("Transparency", transparency.getNodeName());
assertEquals(2, transparency.getLength()); assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild(); IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName()); assertEquals("TransparentIndex", pixelAspectRatio.getNodeName());
assertEquals("nonpremultiplied", alpha.getAttribute("value")); assertEquals("1", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode transparentIndex = (IIOMetadataNode) alpha.getNextSibling(); assertNull(pixelAspectRatio.getNextSibling()); // No more children
assertEquals("TransparentIndex", transparentIndex.getNodeName());
assertEquals("1", transparentIndex.getAttribute("value"));
assertNull(transparentIndex.getNextSibling()); // No more children
} }
@Test @Test
public void testStandardRGB8() throws IIOException { public void testStandardRGB8() throws IIOException {
Form header = Form.ofType(IFF.TYPE_RGB8) Form header = Form.ofType(IFF.TYPE_RGB8)
.with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0)); .with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Chroma // Chroma
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma); assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName()); assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength()); assertEquals(3, chroma.getLength());
@@ -639,7 +581,7 @@ public class IFFImageMetadataTest {
assertNull(blackIsZero.getNextSibling()); // No more children assertNull(blackIsZero.getNextSibling()); // No more children
// Data // Data
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -659,7 +601,7 @@ public class IFFImageMetadataTest {
assertNull(bitsPerSample.getNextSibling()); // No more children assertNull(bitsPerSample.getNextSibling()); // No more children
// Transparency // Transparency
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency); assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName()); assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength()); assertEquals(1, transparency.getLength());
@@ -682,10 +624,10 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_DEEP) Form header = Form.ofType(IFF.TYPE_DEEP)
.with(new DGBLChunk(8)) .with(new DGBLChunk(8))
.with(dpel); .with(dpel);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB_DEEP, header, header.colorMap()); IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Chroma // Chroma
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma"); IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma); assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName()); assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength()); assertEquals(3, chroma.getLength());
@@ -707,7 +649,7 @@ public class IFFImageMetadataTest {
assertNull(blackIsZero.getNextSibling()); // No more children assertNull(blackIsZero.getNextSibling()); // No more children
// Data // Data
IIOMetadataNode data = getStandardNode(metadata, "Data"); IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data); assertNotNull(data);
assertEquals("Data", data.getNodeName()); assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength()); assertEquals(3, data.getLength());
@@ -727,7 +669,7 @@ public class IFFImageMetadataTest {
assertNull(bitsPerSample.getNextSibling()); // No more children assertNull(bitsPerSample.getNextSibling()); // No more children
// Transparency // Transparency
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency"); IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency); assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName()); assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength()); assertEquals(1, transparency.getLength());
@@ -738,13 +680,4 @@ public class IFFImageMetadataTest {
assertNull(alpha.getNextSibling()); // No more children assertNull(alpha.getNextSibling()); // No more children
} }
// TODO: Test RGB8 + ColorMap
private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) {
IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList nodes = asTree.getElementsByTagName(nodeName);
return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null;
}
} }
+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.10.2-SNAPSHOT</version> <version>3.8.4-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.10.2-SNAPSHOT</version> <version>3.8.4-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 -20
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.10.2-SNAPSHOT</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name> <name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -32,23 +32,4 @@
<artifactId>imageio-metadata</artifactId> <artifactId>imageio-metadata</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
<instructions>
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi,
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageWriterSpi
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project> </project>
@@ -145,8 +145,8 @@ final class HuffmanTable extends Segment {
} }
int c = temp >> 4; int c = temp >> 4;
if (c > 1) { if (c > 2) {
throw new IIOException("Unexpected JPEG Huffman Table class (> 1): " + c); throw new IIOException("Unexpected JPEG Huffman Table class (> 2): " + c);
} }
table.tc[t][c] = true; table.tc[t][c] = true;
@@ -888,21 +888,20 @@ public final class JPEGImageReader extends ImageReaderBase {
throw new IIOException("No SOF segment in stream"); throw new IIOException("No SOF segment in stream");
} }
private Application lastAppSegment(int marker, String identifier) throws IOException {
List<Application> appSegments = getAppSegments(marker, identifier);
return appSegments.isEmpty() ? null : appSegments.get(appSegments.size() - 1);
}
AdobeDCT getAdobeDCT() throws IOException { AdobeDCT getAdobeDCT() throws IOException {
return (AdobeDCT) lastAppSegment(JPEG.APP14, "Adobe"); List<Application> adobe = getAppSegments(JPEG.APP14, "Adobe");
return adobe.isEmpty() ? null : (AdobeDCT) adobe.get(0);
} }
JFIF getJFIF() throws IOException{ JFIF getJFIF() throws IOException{
return (JFIF) lastAppSegment(JPEG.APP0, "JFIF"); List<Application> jfif = getAppSegments(JPEG.APP0, "JFIF");
return jfif.isEmpty() ? null : (JFIF) jfif.get(0);
} }
JFXX getJFXX() throws IOException { JFXX getJFXX() throws IOException {
return (JFXX) lastAppSegment(JPEG.APP0, "JFXX"); List<Application> jfxx = getAppSegments(JPEG.APP0, "JFXX");
return jfxx.isEmpty() ? null : (JFXX) jfxx.get(0);
} }
private EXIF getExif() throws IOException { private EXIF getExif() throws IOException {
@@ -52,7 +52,7 @@ import static com.twelvemonkeys.imageio.util.IIOUtil.lookupProviderByName;
* @version $Id: JPEGImageReaderSpi.java,v 1.0 24.01.11 22.12 haraldk Exp$ * @version $Id: JPEGImageReaderSpi.java,v 1.0 24.01.11 22.12 haraldk Exp$
*/ */
public final class JPEGImageReaderSpi extends ImageReaderSpiBase { public final class JPEGImageReaderSpi extends ImageReaderSpiBase {
ImageReaderSpi delegateProvider; protected ImageReaderSpi delegateProvider;
/** /**
* Constructor for use by {@link javax.imageio.spi.IIORegistry} only. * Constructor for use by {@link javax.imageio.spi.IIORegistry} only.
@@ -32,21 +32,19 @@ package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.ImageWriterBase; import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG; import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import org.w3c.dom.NodeList;
import javax.imageio.IIOImage; import javax.imageio.IIOImage;
import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter; import javax.imageio.ImageWriter;
import javax.imageio.event.IIOWriteWarningListener; import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*; import java.awt.image.*;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -163,7 +161,7 @@ public final class JPEGImageWriter extends ImageWriterBase {
else { else {
// If the image metadata is our substitute, convert it back to native com.sun format // If the image metadata is our substitute, convert it back to native com.sun format
if (image.getMetadata() instanceof JPEGImage10Metadata) { if (image.getMetadata() instanceof JPEGImage10Metadata) {
ImageTypeSpecifier type = image.hasRaster() ? null : ImageTypeSpecifiers.createFromRenderedImage(image.getRenderedImage()); ImageTypeSpecifier type = image.hasRaster() ? null : ImageTypeSpecifier.createFromRenderedImage(image.getRenderedImage());
IIOMetadata nativeMetadata = delegate.getDefaultImageMetadata(type, param); IIOMetadata nativeMetadata = delegate.getDefaultImageMetadata(type, param);
JPEGImage10Metadata metadata = (JPEGImage10Metadata) image.getMetadata(); JPEGImage10Metadata metadata = (JPEGImage10Metadata) image.getMetadata();
@@ -187,66 +185,22 @@ public final class JPEGImageWriter extends ImageWriterBase {
boolean overrideDestination = param != null && param.getDestinationType() != null; boolean overrideDestination = param != null && param.getDestinationType() != null;
ImageTypeSpecifier destinationType = overrideDestination ImageTypeSpecifier destinationType = overrideDestination
? param.getDestinationType() ? param.getDestinationType()
: ImageTypeSpecifiers.createFromRenderedImage(renderedImage); : ImageTypeSpecifier.createFromRenderedImage(renderedImage);
IIOMetadata metadata = convertCMYKMetadata(image.getMetadata(), destinationType, param); ColorSpace cmykCS = destinationType.getColorModel().getColorSpace();
Raster raster = new InvertedRaster(getRaster(renderedImage)); IIOMetadata metadata = delegate.getDefaultImageMetadata(destinationType, param);
// TODO: For YCCK we need oposite conversion
// for (int i = 0; i < data.length; i += 4) {
// YCbCrConverter.convertYCbCr2RGB(data, data, i);
// }
if (overrideDestination) {
// Avoid javax.imageio.IIOException: Invalid argument to native writeImage
param.setDestinationType(null);
}
try {
delegate.write(streamMetadata, new IIOImage(raster, null, metadata), param);
}
finally {
if (overrideDestination) {
param.setDestinationType(destinationType);
}
}
}
private IIOMetadata convertCMYKMetadata(IIOMetadata original, ImageTypeSpecifier destinationType, ImageWriteParam param) throws IIOInvalidTreeException {
IIOMetadataNode jpegMeta = new IIOMetadataNode(JAVAX_IMAGEIO_JPEG_IMAGE_1_0); IIOMetadataNode jpegMeta = new IIOMetadataNode(JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
jpegMeta.appendChild(new IIOMetadataNode("JPEGVariety")); // Just leave as default, we can't write JFIF jpegMeta.appendChild(new IIOMetadataNode("JPEGVariety")); // Just leave as default
IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence"); IIOMetadataNode markerSequence = new IIOMetadataNode("markerSequence");
jpegMeta.appendChild(markerSequence); jpegMeta.appendChild(markerSequence);
IIOMetadataNode originalTree = original != null
? (IIOMetadataNode) original.getAsTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0)
: new IIOMetadataNode("emptyNode");
// Append original unknown nodes, if present, but filter out any ICC Profiles
NodeList unknowns = originalTree.getElementsByTagName("unknown");
for (int i = 0; i < unknowns.getLength(); i++) {
IIOMetadataNode unknown = (IIOMetadataNode) unknowns.item(i);
// TODO: If the cmykCS is not an ICC profile, maybe it makes sense to NOT filter here? that's a corner case...
if ("226".equals(unknown.getAttribute("MarkerTag"))) {
Object userObject = unknown.getUserObject();
if (userObject instanceof byte[] && ((byte[]) userObject).length >= "ICC_PROFILE".length()
&& "ICC_PROFILE".equals(new String((byte[]) userObject, 0, "ICC_PROFILE".length()))) {
continue;
}
}
markerSequence.appendChild(unknown);
}
IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe"); IIOMetadataNode app14Adobe = new IIOMetadataNode("app14Adobe");
app14Adobe.setAttribute("transform", "0"); // 0 for CMYK, 2 for YCCK app14Adobe.setAttribute("transform", "0"); // 0 for CMYK, 2 for YCCK
markerSequence.appendChild(app14Adobe); markerSequence.appendChild(app14Adobe);
ColorSpace cmykCS = destinationType.getColorModel().getColorSpace();
if (cmykCS instanceof ICC_ColorSpace) { if (cmykCS instanceof ICC_ColorSpace) {
ICC_Profile profile = ((ICC_ColorSpace) cmykCS).getProfile(); ICC_Profile profile = ((ICC_ColorSpace) cmykCS).getProfile();
byte[] profileData = profile.getData(); byte[] profileData = profile.getData();
@@ -279,16 +233,28 @@ public final class JPEGImageWriter extends ImageWriterBase {
} }
} }
// Append original comment nodes, if present
NodeList comments = originalTree.getElementsByTagName("COM");
for (int i = 0; i < comments.getLength(); i++) {
markerSequence.appendChild(comments.item(i));
}
IIOMetadata metadata = delegate.getDefaultImageMetadata(destinationType, param);
metadata.mergeTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0, jpegMeta); metadata.mergeTree(JAVAX_IMAGEIO_JPEG_IMAGE_1_0, jpegMeta);
return metadata; Raster raster = new InvertedRaster(getRaster(renderedImage));
// TODO: For YCCK we need oposite conversion
// for (int i = 0; i < data.length; i += 4) {
// YCbCrConverter.convertYCbCr2RGB(data, data, i);
// }
if (overrideDestination) {
// Avoid javax.imageio.IIOException: Invalid argument to native writeImage
param.setDestinationType(null);
}
try {
delegate.write(streamMetadata, new IIOImage(raster, null, metadata), param);
}
finally {
if (overrideDestination) {
param.setDestinationType(destinationType);
}
}
} }
// TODO: Candidate util method // TODO: Candidate util method
@@ -38,13 +38,7 @@ import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import org.junit.Test; import org.junit.Test;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import javax.imageio.IIOImage; import javax.imageio.*;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
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.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.metadata.IIOMetadataNode;
@@ -54,7 +48,8 @@ import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.*; import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*; import java.awt.image.*;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
@@ -216,7 +211,7 @@ public class JPEGImageWriterTest extends ImageWriterAbstractTest<JPEGImageWriter
// Test APP2/ICC_PROFILE segments form native metadata // Test APP2/ICC_PROFILE segments form native metadata
IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
NodeList unknown = nativeMeta.getElementsByTagName("unknown"); NodeList unknown = nativeMeta.getElementsByTagName("unknown");
assertEquals(14, unknown.getLength()); // We write longer segments than the original, so we get less segments assertEquals(11, unknown.getLength()); // We write longer segments than the original, so we get less segments
ByteArrayOutputStream iccSegments = new ByteArrayOutputStream(1024 * 1024); ByteArrayOutputStream iccSegments = new ByteArrayOutputStream(1024 * 1024);
@@ -243,6 +238,7 @@ public class JPEGImageWriterTest extends ImageWriterAbstractTest<JPEGImageWriter
ImageWriter writer = createWriter(); ImageWriter writer = createWriter();
ImageReader reader = ImageIO.getImageReader(writer); ImageReader reader = ImageIO.getImageReader(writer);
// TODO: Add flag to allow removing the ICC profile from image
ByteArrayOutputStream stream = transcode(reader, getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), writer, ColorSpace.TYPE_CMYK, false); ByteArrayOutputStream stream = transcode(reader, getClassLoaderResource("/jpeg/cmyk-sample-multiple-chunk-icc.jpg"), writer, ColorSpace.TYPE_CMYK, false);
reader.reset(); reader.reset();
@@ -263,7 +259,7 @@ public class JPEGImageWriterTest extends ImageWriterAbstractTest<JPEGImageWriter
// Test APP2/ICC_PROFILE segments form native metadata // Test APP2/ICC_PROFILE segments form native metadata
IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0); IIOMetadataNode nativeMeta = (IIOMetadataNode) metadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
NodeList unknown = nativeMeta.getElementsByTagName("unknown"); NodeList unknown = nativeMeta.getElementsByTagName("unknown");
assertEquals(3, unknown.getLength()); assertEquals(0, unknown.getLength());
} }
// TODO: YCCK // TODO: YCCK

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