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
272 changed files with 4548 additions and 10890 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"
+16 -55
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,68 +9,37 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ] os: [ ubuntu-latest, windows-latest, macos-latest ]
java: [ 11, 17, 21 ] java: [ 8, 11, 17 ]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
checks: write
steps: steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - uses: actions/checkout@v2
- uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.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@db71d41eb79864e25ab0337e395c352e84523afe # v4.3.1 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"
check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }} check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
test-jdk8: test_oracle:
name: Test OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-13 ]
java: [ 8 ]
runs-on: ${{ matrix.os }}
permissions:
checks: write
steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
java-package: jdk
cache: 'maven'
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@db71d41eb79864e25ab0337e395c352e84523afe # v4.3.1
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
test-oracle:
name: Test Oracle JDK 8 with KCMS=${{ matrix.kcms }} 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@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - 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@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.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
@@ -91,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@db71d41eb79864e25ab0337e395c352e84523afe # v4.3.1 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"
@@ -101,13 +62,13 @@ jobs:
release: release:
name: Deploy name: Deploy
needs: [ test, test-jdk8, test-oracle ] needs: [ test, test_oracle ]
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@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - uses: actions/checkout@v2
- name: Set up Maven Central - name: Set up Maven Central
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # v4.4.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'
@@ -119,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@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11
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@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11
# ℹ️ 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@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11
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@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
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@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
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@6db8d6351fd0be61f9ed8ebd12ccd35dcec51fea # v3.26.11
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/
+77 -77
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.10.1</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.10.1</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.10.1</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.10.1</version> <version>3.8.1</version>
<classifier>jakarta</classifier> <classifier>jakarta</classifier>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -312,18 +306,18 @@ 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.10.1.jar twelvemonkeys-common-lang-3.8.1.jar
twelvemonkeys-common-io-3.10.1.jar twelvemonkeys-common-io-3.8.1.jar
twelvemonkeys-common-image-3.10.1.jar twelvemonkeys-common-image-3.8.1.jar
twelvemonkeys-imageio-core-3.10.1.jar twelvemonkeys-imageio-core-3.8.1.jar
twelvemonkeys-imageio-metadata-3.10.1.jar twelvemonkeys-imageio-metadata-3.8.1.jar
twelvemonkeys-imageio-jpeg-3.10.1.jar twelvemonkeys-imageio-jpeg-3.8.1.jar
twelvemonkeys-imageio-tiff-3.10.1.jar twelvemonkeys-imageio-tiff-3.8.1.jar
#### Deploying the plugins in a web app #### Deploying the plugins in a web app
Because the `ImageIO` plugin registry (the `IIORegistry`) is "VM global", it does not work well with Because the `ImageIO` plugin registry (the `IIORegistry`) is "VM global", it doesn't by default work well with
servlet contexts as-is. This is especially evident if you load plugins from the `WEB-INF/lib` or `classes` folder. servlet contexts. This is especially evident if you load plugins from the `WEB-INF/lib` or `classes` folder.
Unless you add `ImageIO.scanForPlugins()` somewhere in your code, the plugins might never be available at all. Unless you add `ImageIO.scanForPlugins()` somewhere in your code, the plugins might never be available at all.
In addition, servlet contexts dynamically loads and unloads classes (using a new class loader per context). In addition, servlet contexts dynamically loads and unloads classes (using a new class loader per context).
@@ -359,16 +353,6 @@ or other ImageIO plugins as well.
Another safe option, is to place the JAR files in the application server's shared or common lib folder. Another safe option, is to place the JAR files in the application server's shared or common lib folder.
##### Jakarta Servlet Support
For those transitioning from the old `javax.servlet` to the new `jakarta.servlet` package, there is a separate
dependency available. It contains exactly the same servlet classes as mentioned above, but built against the new Jakarta EE
packages. The dependency has the same group name and identifier as before, but a `jakarta` *classifier* appended, to
distinguish it from the non-Jakarta package.
See the [Maven dependency example](#maven-dependency-example) for how to enable it with Maven.
Gradle or other build tools will have similar options.
#### Including the plugins in a "fat" JAR #### Including the plugins in a "fat" JAR
The recommended way to use the plugins, is just to include the JARs as-is in your project, through a Maven dependency or similar. The recommended way to use the plugins, is just to include the JARs as-is in your project, through a Maven dependency or similar.
@@ -394,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.10.1) ##### 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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.10.1/common-lang-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.10.1/common-io-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.10.1/common-image-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.10.1/imageio-core-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.10.1/imageio-metadata-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.10.1/imageio-bmp-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.10.1/imageio-hdr-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.10.1/imageio-icns-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.10.1/imageio-iff-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.10.1/imageio-jpeg-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.10.1/imageio-pcx-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.10.1/imageio-pict-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.10.1/imageio-pnm-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.10.1/imageio-psd-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.10.1/imageio-sgi-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.10.1/imageio-tga-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.10.1/imageio-thumbsdb-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.10.1/imageio-tiff-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.10.1/imageio-webp-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.10.1/imageio-xwd-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.10.1/imageio-batik-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.10.1/imageio-clippath-3.10.1.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.10.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.10.1/servlet-3.10.1.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
@@ -469,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?
@@ -489,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 -6
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.12.0</version> <version>3.8.4-SNAPSHOT</version>
</parent> </parent>
<groupId>com.twelvemonkeys.bom</groupId> <groupId>com.twelvemonkeys.bom</groupId>
@@ -63,11 +63,6 @@
<artifactId>imageio-hdr</artifactId> <artifactId>imageio-hdr</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-dds</artifactId>
<version>${project.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-icns</artifactId> <artifactId>imageio-icns</artifactId>
+9 -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.12.0</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>
@@ -28,14 +28,12 @@
<artifactId>common-io</artifactId> <artifactId>common-io</artifactId>
</dependency> </dependency>
<dependency>
<groupId>jmagick</groupId>
<artifactId>jmagick</artifactId>
<version>6.6.9</version>
<optional>true</optional>
<scope>provided</scope>
</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) {
createColorModel();
}
else {
createBuffered();
}
} }
finally {
// Clean up, in case any objects are copied/cloned, so we can free resources if (pColorModelOnly) {
freeResources(); createColorModel();
}
else {
createBuffered();
} }
} }
} }
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) {
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR"); case 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;
} }
@@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2024, Harald Kuhr * Copyright (c) 2008, 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
@@ -28,22 +28,28 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/ */
package com.twelvemonkeys.imageio.plugins.dds;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo; package com.twelvemonkeys.image;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest;
/** /**
* DDSProviderInfoTest. * Magick
* *
* @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: harald.kuhr$ * @author last modified by $Author: haku $
* @version $Id: DDSProviderInfoTest.java,v 1.0 02/06/16 harald.kuhr Exp$ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/Magick.java#1 $
*/ */
public class DDSProviderInfoTest extends ReaderWriterProviderInfoTest { final class Magick {
static final boolean DEBUG = useDebug();
@Override private static boolean useDebug() {
protected ReaderWriterProviderInfo createProviderInfo() { try {
return new DDSProviderInfo(); return "TRUE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.magick.debug"));
}
catch (Throwable t) {
// Most probably in case of a SecurityManager
return false;
}
} }
private Magick() {}
} }
@@ -0,0 +1,187 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.image;
import com.twelvemonkeys.lang.SystemUtil;
import magick.MagickImage;
import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
/**
* This class accelerates certain graphics operations, using
* JMagick and ImageMagick, if available.
* If those libraries are not installed, this class silently does nothing.
* <p>
* Set the system property {@code "com.twelvemonkeys.image.accel"} to
* {@code false}, to disable, even if JMagick is installed.
* Set the system property {@code "com.twelvemonkeys.image.magick.debug"} to
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickAccelerator.java#3 $
*/
final class MagickAccelerator {
private static final boolean DEBUG = Magick.DEBUG;
private static final boolean USE_MAGICK = useMagick();
private static final int RESAMPLE_OP = 0;
private static Class[] nativeOp = new Class[1];
static {
try {
nativeOp[RESAMPLE_OP] = Class.forName("com.twelvemonkeys.image.ResampleOp");
}
catch (ClassNotFoundException e) {
System.err.println("Could not find class: " + e);
}
}
private static boolean useMagick() {
try {
boolean available = SystemUtil.isClassAvailable("magick.MagickImage");
if (DEBUG && !available) {
System.err.print("ImageMagick bindings not available.");
}
boolean useMagick =
available && !"FALSE".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.image.accel"));
if (DEBUG) {
System.err.println(
useMagick
? "Will use ImageMagick bindings to accelerate image resampling operations."
: "Will not use ImageMagick to accelerate image resampling operations."
);
}
return useMagick;
}
catch (Throwable t) {
// Most probably in case of a SecurityManager
System.err.println("Could not enable ImageMagick bindings: " + t);
return false;
}
}
private static int getNativeOpIndex(Class pOpClass) {
for (int i = 0; i < nativeOp.length; i++) {
if (pOpClass == nativeOp[i]) {
return i;
}
}
return -1;
}
public static BufferedImage filter(BufferedImageOp pOperation, BufferedImage pInput, BufferedImage pOutput) {
if (!USE_MAGICK) {
return null;
}
BufferedImage result = null;
switch (getNativeOpIndex(pOperation.getClass())) {
case RESAMPLE_OP:
ResampleOp resample = (ResampleOp) pOperation;
result = resampleMagick(pInput, resample.width, resample.height, resample.filterType);
// NOTE: If output parameter is non-null, we have to return that
// image, instead of result
if (pOutput != null) {
//pOutput.setData(result.getRaster()); // Fast, but less compatible
// NOTE: For some reason, this is sometimes super-slow...?
ImageUtil.drawOnto(pOutput, result);
result = pOutput;
}
break;
default:
// Simply fall through, allowing acceleration to be added later
break;
}
return result;
}
private static BufferedImage resampleMagick(BufferedImage pSrc, int pWidth, int pHeight, int pFilterType) {
// Convert to Magick, scale and convert back
MagickImage image = null;
MagickImage scaled = null;
try {
image = MagickUtil.toMagick(pSrc);
long start = 0;
if (DEBUG) {
start = System.currentTimeMillis();
}
// NOTE: setFilter affects zoomImage, NOT scaleImage
image.setFilter(pFilterType);
scaled = image.zoomImage(pWidth, pHeight);
//scaled = image.scaleImage(pWidth, pHeight); // AREA_AVERAGING
if (DEBUG) {
long time = System.currentTimeMillis() - start;
System.out.println("Filtered: " + time + " ms");
}
return MagickUtil.toBuffered(scaled);
}
//catch (MagickException e) {
catch (Exception e) {
// NOTE: Stupid workaround: If MagickException is caught, a
// NoClassDefFoundError is thrown, when MagickException class is
// unavailable...
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
throw new ImageConversionException(e.getMessage(), e);
}
finally {
// NOTE: ImageMagick might be unstable after a while, if image data
// is not deallocated. The GC/finalize method handles this, but in
// special circumstances, it's not triggered often enough.
if (image != null) {
image.destroyImages();
}
if (scaled != null) {
scaled.destroyImages();
}
}
}
}
@@ -0,0 +1,621 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.image;
import magick.ImageType;
import magick.MagickException;
import magick.MagickImage;
import magick.PixelPacket;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
/**
* Utility for converting JMagick {@code MagickImage}s to standard Java
* {@code BufferedImage}s and back.
* <p>
* <em>NOTE: This class is considered an implementation detail and not part of
* the public API. This class is subject to change without further notice.
* You have been warned. :-)</em>
* </p>
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/image/MagickUtil.java#4 $
*/
public final class MagickUtil {
// IMPORTANT NOTE: Disaster happens if any of these constants are used outside this class
// because you then have a dependency on MagickException (this is due to Java class loading
// and initialization magic).
// Do not use outside this class. If the constants need to be shared, move to Magick or ImageUtil.
/** Color Model usesd for bilevel (B/W) */
private static final IndexColorModel CM_MONOCHROME = MonochromeColorModel.getInstance();
/** Color Model usesd for raw ABGR */
private static final ColorModel CM_COLOR_ALPHA =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8, 8},
true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
/** Color Model usesd for raw BGR */
private static final ColorModel CM_COLOR_OPAQUE =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[] {8, 8, 8},
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
/** Color Model usesd for raw RGB */
//private static final ColorModel CM_COLOR_RGB = new DirectColorModel(24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x0);
/** Color Model usesd for raw GRAY + ALPHA */
private static final ColorModel CM_GRAY_ALPHA =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
/** Color Model usesd for raw GRAY */
private static final ColorModel CM_GRAY_OPAQUE =
new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY),
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
/** Band offsets for raw ABGR */
private static final int[] BAND_OFF_TRANS = new int[] {3, 2, 1, 0};
/** Band offsets for raw BGR */
private static final int[] BAND_OFF_OPAQUE = new int[] {2, 1, 0};
/** The point at {@code 0, 0} */
private static final Point LOCATION_UPPER_LEFT = new Point(0, 0);
private static final boolean DEBUG = Magick.DEBUG;
// Only static members and methods
private MagickUtil() {}
/**
* Converts a {@code MagickImage} to a {@code BufferedImage}.
* <p>
* The conversion depends on {@code pImage}'s {@code ImageType}:
* </p>
* <dl>
* <dt>{@code ImageType.BilevelType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY}</dd>
*
* <dt>{@code ImageType.GrayscaleType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_GRAY}</dd>
* <dt>{@code ImageType.GrayscaleMatteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_USHORT_GRAY}</dd>
*
* <dt>{@code ImageType.PaletteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of &lt;= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
* <dt>{@code ImageType.PaletteMatteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of &lt;= 16 colors) or {@code TYPE_BYTE_INDEXED}</dd>
*
* <dt>{@code ImageType.TrueColorType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_3BYTE_BGR}</dd>
* <dt>{@code ImageType.TrueColorPaletteType}</dt>
* <dd>{@code BufferedImage} of type {@code TYPE_4BYTE_ABGR}</dd>
* </dl>
*
* @param pImage the original {@code MagickImage}
* @return a new {@code BufferedImage}
*
* @throws IllegalArgumentException if {@code pImage} is {@code null}
* or if the {@code ImageType} is not one mentioned above.
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
public static BufferedImage toBuffered(MagickImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
long start = 0L;
if (DEBUG) {
start = System.currentTimeMillis();
}
BufferedImage image = null;
try {
switch (pImage.getImageType()) {
case ImageType.BilevelType:
image = bilevelToBuffered(pImage);
break;
case ImageType.GrayscaleType:
image = grayToBuffered(pImage, false);
break;
case ImageType.GrayscaleMatteType:
image = grayToBuffered(pImage, true);
break;
case ImageType.PaletteType:
image = paletteToBuffered(pImage, false);
break;
case ImageType.PaletteMatteType:
image = paletteToBuffered(pImage, true);
break;
case ImageType.TrueColorType:
image = rgbToBuffered(pImage, false);
break;
case ImageType.TrueColorMatteType:
image = rgbToBuffered(pImage, true);
break;
case ImageType.ColorSeparationType:
image = cmykToBuffered(pImage, false);
break;
case ImageType.ColorSeparationMatteType:
image = cmykToBuffered(pImage, true);
break;
case ImageType.OptimizeType:
default:
throw new IllegalArgumentException("Unknown JMagick image type: " + pImage.getImageType());
}
}
finally {
if (DEBUG) {
long time = System.currentTimeMillis() - start;
System.out.println("Converted JMagick image type: " + pImage.getImageType() + " to BufferedImage: " + image);
System.out.println("Conversion to BufferedImage: " + time + " ms");
}
}
return image;
}
/**
* Converts a {@code BufferedImage} to a {@code MagickImage}.
* <p>
* The conversion depends on {@code pImage}'s {@code ColorModel}:
* </p>
* <dl>
* <dt>{@code IndexColorModel} with 1 bit b/w</dt>
* <dd>{@code MagickImage} of type {@code ImageType.BilevelType}</dd>
* <dt>{@code IndexColorModel} &gt; 1 bit,</dt>
* <dd>{@code MagickImage} of type {@code ImageType.PaletteType}
* or {@code MagickImage} of type {@code ImageType.PaletteMatteType}
* depending on <tt>ColorModel.getAlpha()</tt></dd>
*
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_GRAY}</dt>
* <dd>{@code MagickImage} of type {@code ImageType.GrayscaleType}
* or {@code MagickImage} of type {@code ImageType.GrayscaleMatteType}
* depending on <tt>ColorModel.getAlpha()</tt></dd>
*
* <dt>{@code ColorModel.getColorSpace().getType() == ColorSpace.TYPE_RGB}</dt>
* <dd>{@code MagickImage} of type {@code ImageType.TrueColorType}
* or {@code MagickImage} of type {@code ImageType.TrueColorPaletteType}</dd>
* </dl>
*
* @param pImage the original {@code BufferedImage}
* @return a new {@code MagickImage}
*
* @throws IllegalArgumentException if {@code pImage} is {@code null}
* or if the {@code ColorModel} is not one mentioned above.
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
long start = 0L;
if (DEBUG) {
start = System.currentTimeMillis();
}
try {
ColorModel cm = pImage.getColorModel();
if (cm instanceof IndexColorModel) {
// Handles both BilevelType, PaletteType and PaletteMatteType
return indexedToMagick(pImage, (IndexColorModel) cm, cm.hasAlpha());
}
switch (cm.getColorSpace().getType()) {
case ColorSpace.TYPE_GRAY:
// Handles GrayType and GrayMatteType
return grayToMagick(pImage, cm.hasAlpha());
case ColorSpace.TYPE_RGB:
// Handles TrueColorType and TrueColorMatteType
return rgbToMagic(pImage, cm.hasAlpha());
case ColorSpace.TYPE_CMY:
case ColorSpace.TYPE_CMYK:
case ColorSpace.TYPE_HLS:
case ColorSpace.TYPE_HSV:
// Other types not supported yet
default:
throw new IllegalArgumentException("Unknown buffered image type: " + pImage);
}
}
finally {
if (DEBUG) {
long time = System.currentTimeMillis() - start;
System.out.println("Conversion to MagickImage: " + time + " ms");
}
}
}
private static MagickImage rgbToMagic(BufferedImage pImage, boolean pAlpha) throws MagickException {
MagickImage image = new MagickImage();
BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_3BYTE_BGR);
// Need to get data of sub raster, not the full data array, this is
// just a convenient way
Raster raster;
if (buffered.getRaster().getParent() != null) {
raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight()));
}
else {
raster = buffered.getRaster();
}
image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "BGR",
((DataBufferByte) raster.getDataBuffer()).getData());
return image;
}
private static MagickImage grayToMagick(BufferedImage pImage, boolean pAlpha) throws MagickException {
MagickImage image = new MagickImage();
// TODO: Make a fix for TYPE_USHORT_GRAY
// The code below does not seem to work (JMagick issues?)...
/*
if (pImage.getType() == BufferedImage.TYPE_USHORT_GRAY) {
short[] data = ((DataBufferUShort) pImage.getRaster().getDataBuffer()).getData();
int[] intData = new int[data.length];
for (int i = 0; i < data.length; i++) {
intData[i] = (data[i] & 0xffff) * 0xffff;
}
image.constituteImage(pImage.getWidth(), pImage.getHeight(), "I", intData);
System.out.println("storageClass: " + image.getStorageClass());
System.out.println("depth: " + image.getDepth());
System.out.println("imageType: " + image.getImageType());
}
else {
*/
BufferedImage buffered = ImageUtil.toBuffered(pImage, pAlpha ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_BYTE_GRAY);
// Need to get data of sub raster, not the full data array, this is
// just a convenient way
Raster raster;
if (buffered.getRaster().getParent() != null) {
raster = buffered.getData(new Rectangle(buffered.getWidth(), buffered.getHeight()));
}
else {
raster = buffered.getRaster();
}
image.constituteImage(buffered.getWidth(), buffered.getHeight(), pAlpha ? "ABGR" : "I", ((DataBufferByte) raster.getDataBuffer()).getData());
//}
return image;
}
private static MagickImage indexedToMagick(BufferedImage pImage, IndexColorModel pColorModel, boolean pAlpha) throws MagickException {
MagickImage image = rgbToMagic(pImage, pAlpha);
int mapSize = pColorModel.getMapSize();
image.setNumberColors(mapSize);
return image;
}
/*
public static MagickImage toMagick(BufferedImage pImage) throws MagickException {
if (pImage == null) {
throw new IllegalArgumentException("image == null");
}
final int width = pImage.getWidth();
final int height = pImage.getHeight();
// int ARGB -> byte RGBA conversion
// NOTE: This is ImageMagick Q16 compatible raw RGBA format with 16 bits/sample...
// For a Q8 build, we could probably go with half the space...
// NOTE: This is close to insanity, as it wastes extreme ammounts of memory
final int[] argb = new int[width];
final byte[] raw16 = new byte[width * height * 8];
for (int y = 0; y < height; y++) {
// Fetch one line of ARGB data
pImage.getRGB(0, y, width, 1, argb, 0, width);
for (int x = 0; x < width; x++) {
int pixel = (x + (y * width)) * 8;
raw16[pixel ] = (byte) ((argb[x] >> 16) & 0xff); // R
raw16[pixel + 2] = (byte) ((argb[x] >> 8) & 0xff); // G
raw16[pixel + 4] = (byte) ((argb[x] ) & 0xff); // B
raw16[pixel + 6] = (byte) ((argb[x] >> 24) & 0xff); // A
}
}
// Create magick image
ImageInfo info = new ImageInfo();
info.setMagick("RGBA"); // Raw RGBA samples
info.setSize(width + "x" + height); // String?!?
MagickImage image = new MagickImage(info);
image.setImageAttribute("depth", "8");
// Set pixel data in 16 bit raw RGBA format
image.blobToImage(info, raw16);
return image;
}
*/
/**
* Converts a bi-level {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_BYTE_BINARY}.
*
* @param pImage the original {@code MagickImage}
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage bilevelToBuffered(MagickImage pImage) throws MagickException {
// As there is no way to get the binary representation of the image,
// convert to gray, and the create a binary image from it
BufferedImage temp = grayToBuffered(pImage, false);
BufferedImage image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, CM_MONOCHROME);
ImageUtil.drawOnto(image, temp);
return image;
}
/**
* Converts a gray {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_USHORT_GRAY} or {@code TYPE_BYTE_GRAY}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage grayToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
Dimension size = pImage.getDimension();
int length = size.width * size.height;
int bands = pAlpha ? 2 : 1;
byte[] pixels = new byte[length * bands];
// TODO: Make a fix for 16 bit TYPE_USHORT_GRAY?!
// Note: The ordering AI or I corresponds to BufferedImage
// TYPE_CUSTOM and TYPE_BYTE_GRAY respectively
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "AI" : "I", pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
int[] bandOffsets = pAlpha ? new int[] {1, 0} : new int[] {0};
WritableRaster raster =
Raster.createInterleavedRaster(buffer, size.width, size.height,
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
return new BufferedImage(pAlpha ? CM_GRAY_ALPHA : CM_GRAY_OPAQUE, raster, pAlpha, null);
}
/**
* Converts a palette-based {@code MagickImage} to a
* {@code BufferedImage}, of type {@code TYPE_BYTE_BINARY} (for images
* with a palette of &lt;= 16 colors) or {@code TYPE_BYTE_INDEXED}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage paletteToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
// Create indexcolormodel for the image
IndexColorModel cm;
try {
cm = createIndexColorModel(pImage.getColormap(), pAlpha);
}
catch (MagickException e) {
// NOTE: Some MagickImages incorrecly (?) reports to be paletteType,
// but does not have a colormap, this is a workaround.
return rgbToBuffered(pImage, pAlpha);
}
// As there is no way to get the indexes of an indexed image, convert to
// RGB, and the create an indexed image from it
BufferedImage temp = rgbToBuffered(pImage, pAlpha);
BufferedImage image;
if (cm.getMapSize() <= 16) {
image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_BINARY, cm);
}
else {
image = new BufferedImage(temp.getWidth(), temp.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, cm);
}
// Create transparent background for images containing alpha
if (pAlpha) {
Graphics2D g = image.createGraphics();
try {
g.setComposite(AlphaComposite.Clear);
g.fillRect(0, 0, temp.getWidth(), temp.getHeight());
}
finally {
g.dispose();
}
}
// NOTE: This is (surprisingly) much faster than using g2d.drawImage()..
// (Tests shows 20-30ms, vs. 600-700ms on the same image)
BufferedImageOp op = new CopyDither(cm);
op.filter(temp, image);
return image;
}
/**
* Creates an {@code IndexColorModel} from an array of
* {@code PixelPacket}s.
*
* @param pColormap the original colormap as a {@code PixelPacket} array
* @param pAlpha keep alpha channel
*
* @return a new {@code IndexColorModel}
*/
public static IndexColorModel createIndexColorModel(PixelPacket[] pColormap, boolean pAlpha) {
int[] colors = new int[pColormap.length];
// TODO: Verify if this is correct for alpha...?
int trans = pAlpha ? colors.length - 1 : -1;
//for (int i = 0; i < pColormap.length; i++) {
for (int i = pColormap.length - 1; i != 0; i--) {
PixelPacket color = pColormap[i];
if (pAlpha) {
colors[i] = (0xff - (color.getOpacity() & 0xff)) << 24 |
(color.getRed() & 0xff) << 16 |
(color.getGreen() & 0xff) << 8 |
(color.getBlue() & 0xff);
}
else {
colors[i] = (color.getRed() & 0xff) << 16 |
(color.getGreen() & 0xff) << 8 |
(color.getBlue() & 0xff);
}
}
return new InverseColorMapIndexColorModel(8, colors.length, colors, 0, pAlpha, trans, DataBuffer.TYPE_BYTE);
}
/**
* Converts an (A)RGB {@code MagickImage} to a {@code BufferedImage}, of
* type {@code TYPE_4BYTE_ABGR} or {@code TYPE_3BYTE_BGR}.
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage rgbToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
Dimension size = pImage.getDimension();
int length = size.width * size.height;
int bands = pAlpha ? 4 : 3;
byte[] pixels = new byte[length * bands];
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
// Note: The ordering ABGR or BGR corresponds to BufferedImage
// TYPE_4BYTE_ABGR and TYPE_3BYTE_BGR respectively
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ABGR" : "BGR", pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
int[] bandOffsets = pAlpha ? BAND_OFF_TRANS : BAND_OFF_OPAQUE;
WritableRaster raster =
Raster.createInterleavedRaster(buffer, size.width, size.height,
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
return new BufferedImage(pAlpha ? CM_COLOR_ALPHA : CM_COLOR_OPAQUE, raster, pAlpha, null);
}
/**
* Converts an {@code MagickImage} to a {@code BufferedImage} which holds an CMYK ICC profile
*
* @param pImage the original {@code MagickImage}
* @param pAlpha keep alpha channel
* @return a new {@code BufferedImage}
*
* @throws MagickException if an exception occurs during conversion
*
* @see BufferedImage
*/
private static BufferedImage cmykToBuffered(MagickImage pImage, boolean pAlpha) throws MagickException {
Dimension size = pImage.getDimension();
int length = size.width * size.height;
// Retreive the ICC profile
ICC_Profile profile = ICC_Profile.getInstance(pImage.getColorProfile().getInfo());
ColorSpace cs = new ICC_ColorSpace(profile);
int bands = cs.getNumComponents() + (pAlpha ? 1 : 0);
int[] bits = new int[bands];
for (int i = 0; i < bands; i++) {
bits[i] = 8;
}
ColorModel cm = pAlpha ?
new ComponentColorModel(cs, bits, true, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE) :
new ComponentColorModel(cs, bits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
byte[] pixels = new byte[length * bands];
// TODO: If we do multiple dispatches (one per line, typically), we could provide listener
// feedback. But it's currently a lot slower than fetching all the pixels in one go.
// TODO: handle more generic cases if profile is not CMYK
// TODO: Test "ACMYK"
pImage.dispatchImage(0, 0, size.width, size.height, pAlpha ? "ACMYK" : "CMYK", pixels);
// Init databuffer with array, to avoid allocation of empty array
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
// TODO: build array from bands variable, here it just works for CMYK
// The values has not been tested with an alpha picture actually...
int[] bandOffsets = pAlpha ? new int[] {0, 1, 2, 3, 4} : new int[] {0, 1, 2, 3};
WritableRaster raster =
Raster.createInterleavedRaster(buffer, size.width, size.height,
size.width * bands, bands, bandOffsets, LOCATION_UPPER_LEFT);
return new BufferedImage(cm, raster, pAlpha, null);
}
}
@@ -55,7 +55,9 @@
package com.twelvemonkeys.image; package com.twelvemonkeys.image;
import java.awt.*; import java.awt.*;
import java.awt.geom.*; import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.*; import java.awt.image.*;
/** /**
@@ -101,6 +103,15 @@ import java.awt.image.*;
* BufferedImage scaled = new ResampleOp(w, h).filter(temp, null); * BufferedImage scaled = new ResampleOp(w, h).filter(temp, null);
* </pre></blockquote> * </pre></blockquote>
* <p> * <p>
* For maximum performance, this class will use native code, through
* <a href="http://www.yeo.id.au/jmagick/">JMagick</a>, when available.
* Otherwise, the class will silently fall back to pure Java mode.
* Native code may be disabled globally, by setting the system property
* {@code com.twelvemonkeys.image.accel} to {@code false}.
* To allow debug of the native code, set the system property
* {@code com.twelvemonkeys.image.magick.debug} to {@code true}.
* </p>
* <p>
* This {@code BufferedImageOp} is based on C example code found in * This {@code BufferedImageOp} is based on C example code found in
* <a href="http://www.acm.org/tog/GraphicsGems/">Graphics Gems III</a>, * <a href="http://www.acm.org/tog/GraphicsGems/">Graphics Gems III</a>,
* Filtered Image Rescaling, by Dale Schumacher (with additional improvments by * Filtered Image Rescaling, by Dale Schumacher (with additional improvments by
@@ -128,6 +139,9 @@ import java.awt.image.*;
// TODO: Consider using AffineTransformOp for more operations!? // TODO: Consider using AffineTransformOp for more operations!?
public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ { public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// NOTE: These MUST correspond to ImageMagick filter types, for the
// MagickAccelerator to work consistently (see magick.FilterType).
/** /**
* Undefined interpolation, filter method will use default filter. * Undefined interpolation, filter method will use default filter.
*/ */
@@ -281,10 +295,11 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC); new Value(KEY_RESAMPLE_INTERPOLATION, "Blackman-Sinc", FILTER_BLACKMAN_SINC);
// Member variables // Member variables
private final int width; // Package access, to allow access from MagickAccelerator
private final int height; int width;
int height;
private final int filterType; int filterType;
/** /**
* RendereingHints.Key implementation, works only with Value values. * RendereingHints.Key implementation, works only with Value values.
@@ -532,6 +547,16 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// Fall through // Fall through
} }
// Try to use native ImageMagick code
BufferedImage result = MagickAccelerator.filter(this, input, output);
if (result != null) {
return result;
}
// Otherwise, continue in pure Java mode
// TODO: What if output != null and wrong size? Create new? Render on only a part? Document?
// If filter type != POINT or BOX and input has IndexColorModel, convert // If filter type != POINT or BOX and input has IndexColorModel, convert
// to true color, with alpha reflecting that of the original color model. // to true color, with alpha reflecting that of the original color model.
BufferedImage temp; BufferedImage temp;
@@ -546,7 +571,7 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
// Create or convert output to a suitable image // Create or convert output to a suitable image
// TODO: OPTIMIZE: Don't really need to convert all types to same as input // TODO: OPTIMIZE: Don't really need to convert all types to same as input
BufferedImage result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null); result = output != null && temp.getType() != BufferedImage.TYPE_CUSTOM ? /*output*/ ImageUtil.toBuffered(output, temp.getType()) : createCompatibleDestImage(temp, null);
resample(temp, result, filter); resample(temp, result, filter);
@@ -1255,12 +1280,12 @@ public class ResampleOp implements BufferedImageOp/* TODO: RasterOp */ {
/* /*
* image rescaling routine * image rescaling routine
*/ */
static class Contributor { class Contributor {
int pixel; int pixel;
double weight; double weight;
} }
static class ContributorList { class ContributorList {
int n;/* number of contributors (may be < p.length) */ int n;/* number of contributors (may be < p.length) */
Contributor[] p;/* pointer to list of contributions */ Contributor[] p;/* pointer to list of contributions */
} }
@@ -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;
} }
+6
View File
@@ -0,0 +1,6 @@
TODO:
Remove compile-time dependency on JMagick:
- Extract interface for MagickAccelerator
- Move implementation to separate module
- Instantiate impl via reflection
DONE:
+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.12.0</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;
} }
} }
@@ -77,7 +77,7 @@ public final class DecoderStream extends FilterInputStream {
super(stream); super(stream);
this.decoder = decoder; this.decoder = decoder;
buffer = ByteBuffer.allocate(bufferSize); // TODO: Allow decoder to specify minimum buffer size buffer = ByteBuffer.allocate(bufferSize);
buffer.flip(); buffer.flip();
} }
@@ -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.12.0</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.12.0</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.12.0</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.12.0</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.17.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.12.0</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 bitmap) throws IOException { private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
short[] pixels = new short[bitmap.getWidth() * bitmap.getHeight()]; short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts // TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
// Will create TYPE_USHORT_555 // Will create TYPE_USHORT_555
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F); DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
DataBuffer buffer = new 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.12.0</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.12.0</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);
} }
@Override public String getDescription(Locale pLocale) {
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)
} }
} }
@@ -37,14 +37,14 @@ import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
/** /**
* ImageOutputStream that writes through a delegate, but keeps local position and bit offset. * ImageInputStream that writes through a delegate, but keeps local position and bit offset.
* Note: Flushing or closing this stream will *not* have an effect on the delegate. * Note: Flushing or closing this stream will *not* have an effect on the delegate.
* *
* @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: harald.kuhr$ * @author last modified by $Author: harald.kuhr$
* @version $Id: SubImageOutputStream.java,v 1.0 30/03/15 harald.kuhr Exp$ * @version $Id: SubImageOutputStream.java,v 1.0 30/03/15 harald.kuhr Exp$
*/ */
public final class SubImageOutputStream extends ImageOutputStreamImpl { public class SubImageOutputStream extends ImageOutputStreamImpl {
private final ImageOutputStream stream; private final ImageOutputStream stream;
private final long startPos; private final long startPos;
@@ -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";
} }
} }
@@ -40,7 +40,7 @@ import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
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.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.io.InputStream; import java.io.InputStream;
@@ -232,10 +232,6 @@ public final class IIOUtil {
int samplesPerPixel, int bitsPerSample, int samplePeriod) { int samplesPerPixel, int bitsPerSample, int samplePeriod) {
// Period == 1 is a no-op... // Period == 1 is a no-op...
if (samplePeriod == 1) { if (samplePeriod == 1) {
if (srcRow != destRow) {
System.arraycopy(srcRow, srcPos, destRow, destPos, srcWidth);
}
return; return;
} }
@@ -279,10 +275,6 @@ public final class IIOUtil {
int samplesPerPixel, int bitsPerSample, int samplePeriod) { int samplesPerPixel, int bitsPerSample, int samplePeriod) {
// Period == 1 is a no-op... // Period == 1 is a no-op...
if (samplePeriod == 1) { if (samplePeriod == 1) {
if (srcRow != destRow) {
System.arraycopy(srcRow, srcPos, destRow, destPos, srcWidth);
}
return; return;
} }
@@ -291,7 +283,7 @@ public final class IIOUtil {
"bitsPerSample must be > 0 and <= 16 and a power of 2"); "bitsPerSample must be > 0 and <= 16 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
Validate.isTrue(samplesPerPixel * bitsPerSample <= 16 || samplesPerPixel * bitsPerSample % 16 == 0, Validate.isTrue(samplesPerPixel * bitsPerSample <= 16 || samplesPerPixel * bitsPerSample % 16 == 0,
"samplesPerPixel * bitsPerSample must be < 16 or a multiple of 16"); "samplesPerPixel * bitsPerSample must be < 16 or a multiple of 16 ");
int pixelStride = bitsPerSample * samplesPerPixel / 16; int pixelStride = bitsPerSample * samplesPerPixel / 16;
for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) {
@@ -305,10 +297,6 @@ public final class IIOUtil {
int samplesPerPixel, int bitsPerSample, int samplePeriod) { int samplesPerPixel, int bitsPerSample, int samplePeriod) {
// Period == 1 is a no-op... // Period == 1 is a no-op...
if (samplePeriod == 1) { if (samplePeriod == 1) {
if (srcRow != destRow) {
System.arraycopy(srcRow, srcPos, destRow, destPos, srcWidth);
}
return; return;
} }
@@ -317,7 +305,7 @@ public final class IIOUtil {
"bitsPerSample must be > 0 and <= 32 and a power of 2"); "bitsPerSample must be > 0 and <= 32 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0,
"samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32"); "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 ");
int pixelStride = bitsPerSample * samplesPerPixel / 32; int pixelStride = bitsPerSample * samplesPerPixel / 32;
for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) {
@@ -334,7 +322,7 @@ public final class IIOUtil {
"bitsPerSample must be > 0 and <= 32 and a power of 2"); "bitsPerSample must be > 0 and <= 32 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0"); Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0, Validate.isTrue(samplesPerPixel * bitsPerSample <= 32 || samplesPerPixel * bitsPerSample % 32 == 0,
"samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32"); "samplesPerPixel * bitsPerSample must be < 32 or a multiple of 32 ");
int pixelStride = bitsPerSample * samplesPerPixel / 32; int pixelStride = bitsPerSample * samplesPerPixel / 32;
for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) { for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) {
@@ -342,21 +330,4 @@ public final class IIOUtil {
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride); System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
} }
} }
public static void subsampleRow(double[] srcRow, int srcPos, int srcWidth,
double[] destRow, int destPos,
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 64 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
"bitsPerSample must be > 0 and <= 64 and a power of 2");
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
Validate.isTrue(samplesPerPixel * bitsPerSample <= 64 || samplesPerPixel * bitsPerSample % 64 == 0,
"samplesPerPixel * bitsPerSample must be < 64 or a multiple of 64");
int pixelStride = bitsPerSample * samplesPerPixel / 64;
for (int x = 0; x < srcWidth * pixelStride; x += samplePeriod * pixelStride) {
// System.arraycopy should be intrinsic, but consider using direct array access for pixelStride == 1
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
}
}
} }
@@ -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,
0xFF0000,
0x00FF00,
0x0000FF,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
0x0000FF,
0x00FF00,
0xFF0000,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16,
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15,
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
private ImageTypeSpecifiers() {} private ImageTypeSpecifiers() {}
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) { public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
switch (bufferedImageType) { switch (bufferedImageType) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types // ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the 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: case BufferedImage.TYPE_USHORT_565_RGB:
return TYPE_USHORT_565_RGB; return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
case BufferedImage.TYPE_USHORT_555_RGB: case BufferedImage.TYPE_USHORT_555_RGB:
return TYPE_USHORT_555_RGB; return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
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); notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
} }
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied); return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
} }
private static int calculateRequiredBits(int mask) {
// See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
int r = 1;
while ((mask >>>= 1) != 0) {
r++;
}
return r;
}
static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits,
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace, 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]);
}
}
@@ -139,69 +139,6 @@ public class IIOUtilTest {
assertArrayEquals(expected, output); assertArrayEquals(expected, output);
} }
@Test
public void subsampleRowPeriod1ByteSameArray() {
byte[] inputOutput = {-1, 0, (byte) 0xAA, 0, -1};
byte[] expected = {-1, 0, (byte) 0xAA, 0, -1};
IIOUtil.subsampleRow(inputOutput, 0, inputOutput.length, inputOutput, 0, 1, 8, 1);
assertArrayEquals(expected, inputOutput);
}
@Test
public void subsampleRowPeriod1ByteDifferentArray() {
byte[] input = {-1, 0, (byte) 0xAA, 0, -1};
byte[] output = new byte[input.length];
byte[] expected = {-1, 0, (byte) 0xAA, 0, -1};
IIOUtil.subsampleRow(input, 0, input.length, output, 0, 1, 8, 1);
assertArrayEquals(expected, output);
}
@Test
public void subsampleRowPeriod1ShortSameArray() {
short[] inputOutput = {-1, 0, (short) 0xAA77, 0, -1};
short[] expected = {-1, 0, (short) 0xAA77, 0, -1};
IIOUtil.subsampleRow(inputOutput, 0, inputOutput.length, inputOutput, 0, 4, 4, 1);
assertArrayEquals(expected, inputOutput);
}
@Test
public void subsampleRowPeriod1ShortDifferentArray() {
short[] input = {-1, 0, (short) 0xAA77, 0, -1};
short[] output = new short[input.length];
short[] expected = {-1, 0, (short) 0xAA77, 0, -1};
IIOUtil.subsampleRow(input, 0, input.length, output, 0, 1, 16, 1);
assertArrayEquals(expected, output);
}
@Test
public void subsampleRowPeriod1IntSameArray() {
int[] inputOutput = {-1, 0, 0xAA997755, 0, -1};
int[] expected = {-1, 0, 0xAA997755, 0, -1};
IIOUtil.subsampleRow(inputOutput, 0, inputOutput.length, inputOutput, 0, 1, 32, 1);
assertArrayEquals(expected, inputOutput);
}
@Test
public void subsampleRowPeriod1IntDifferentArray() {
int[] input = {-1, 0, 0xAA997755, 0, -1};
int[] output = new int[input.length];
int[] expected = {-1, 0, 0xAA997755, 0, -1};
IIOUtil.subsampleRow(input, 0, input.length, output, 0, 4, 8, 1);
assertArrayEquals(expected, output);
}
private int divCeil(int numerator, int denominator) { private int divCeil(int numerator, int denominator) {
return (numerator + denominator - 1) / denominator; return (numerator + denominator - 1) / denominator;
} }
@@ -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);
@@ -1391,8 +1385,6 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
public void testSetDestination() throws IOException { public void testSetDestination() throws IOException {
ImageReader reader = createReader(); ImageReader reader = createReader();
TestData data = getTestData().get(0); TestData data = getTestData().get(0);
Dimension size = data.getDimension(0);
reader.setInput(data.getInputStream()); reader.setInput(data.getInputStream());
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
@@ -1400,7 +1392,7 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
while (types.hasNext()) { while (types.hasNext()) {
ImageTypeSpecifier type = types.next(); ImageTypeSpecifier type = types.next();
BufferedImage destination = type.createBufferedImage(size.width, size.height); BufferedImage destination = type.createBufferedImage(50, 50);
param.setDestination(destination); param.setDestination(destination);
BufferedImage result = null; BufferedImage result = null;
@@ -1450,15 +1442,13 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
public void testSetDestinationIllegal() throws IOException { public void testSetDestinationIllegal() throws IOException {
final ImageReader reader = createReader(); final ImageReader reader = createReader();
TestData data = getTestData().get(0); TestData data = getTestData().get(0);
Dimension size = data.getDimension(0);
reader.setInput(data.getInputStream()); reader.setInput(data.getInputStream());
List<ImageTypeSpecifier> illegalTypes = createIllegalTypes(reader.getImageTypes(0)); List<ImageTypeSpecifier> illegalTypes = createIllegalTypes(reader.getImageTypes(0));
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
for (ImageTypeSpecifier illegalType : illegalTypes) { for (ImageTypeSpecifier illegalType : illegalTypes) {
BufferedImage destination = illegalType.createBufferedImage(size.width, size.height); BufferedImage destination = illegalType.createBufferedImage(50, 50);
param.setDestination(destination); param.setDestination(destination);
try { try {
@@ -1759,12 +1749,11 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
ImageReader reader = createReader(); ImageReader reader = createReader();
for (TestData testData : getTestDataForAffineTransformOpCompatibility()) { for (TestData testData : getTestDataForAffineTransformOpCompatibility()) {
Dimension size = testData.getDimension(0);
try (ImageInputStream input = testData.getInputStream()) { try (ImageInputStream input = testData.getInputStream()) {
reader.setInput(input); reader.setInput(input);
ImageReadParam param = reader.getDefaultReadParam(); ImageReadParam param = reader.getDefaultReadParam();
param.setSourceRegion(new Rectangle(size.width, size.height)); param.setSourceRegion(new Rectangle(min(reader.getWidth(0), 64), min(reader.getHeight(0), 64)));
BufferedImage originalImage = reader.read(0, param); BufferedImage originalImage = reader.read(0, param);
@@ -1839,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;
if (dimensions == null) {
// Copy dimensions from images
combinedDimensions = new ArrayList<>(images.size());
for (BufferedImage image : images) {
combinedDimensions.add(new Dimension(image.getWidth(), image.getHeight()));
}
}
else {
// Validate equal dimensions
if (images != null) {
if (images.size() != dimensions.size()) {
throw new IllegalArgumentException("Dimensions and images parameter's 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 = Collections.unmodifiableList(combinedDimensions); sizes = new ArrayList<>();
images = new ArrayList<>();
this.images = images != null List<Dimension> sizes = pSizes;
? Collections.unmodifiableList(new ArrayList<>(images)) if (sizes == null) {
: Collections.<BufferedImage>emptyList(); sizes = new ArrayList<>();
if (pImages != null) {
for (BufferedImage image : pImages) {
sizes.add(new Dimension(image.getWidth(), image.getHeight()));
}
}
else {
throw new IllegalArgumentException("Need either size or image");
}
}
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");
}
this.input = input; }
}
this.sizes.addAll(sizes);
if (pImages != null) {
images.addAll(pImages);
}
input = pInput;
} }
public Object getInput() { public Object getInput() {
@@ -1908,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
-52
View File
@@ -1,52 +0,0 @@
Copyright (c) 2024, 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.
Parts of this software is based on DDSReader by Kenji Sasaki:
The MIT License (MIT)
Copyright (c) 2015 Kenji Sasaki
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-52
View File
@@ -1,52 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.12.0</version>
</parent>
<artifactId>imageio-dds</artifactId>
<name>TwelveMonkeys :: ImageIO :: DDS plugin</name>
<description>
ImageIO plugin for Microsoft Direct DrawSurface (DDS).
</description>
<properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.dds</project.jpms.module.name>
</properties>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-metadata</artifactId>
</dependency>
</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>
@@ -1,51 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.dds;
@SuppressWarnings("unused")
interface DDS {
byte[] MAGIC = new byte[]{'D', 'D', 'S', ' '};
int HEADER_SIZE = 124;
// Header Flags
int FLAG_CAPS = 0x1; // Required in every .dds file.
int FLAG_HEIGHT = 0x2; // Required in every .dds file.
int FLAG_WIDTH = 0x4; // Required in every .dds file.
int FLAG_PITCH = 0x8; // Required when pitch is provided for an uncompressed texture.
int FLAG_PIXELFORMAT = 0x1000; // Required in every .dds file.
int FLAG_MIPMAPCOUNT = 0x20000; // Required in a mipmapped texture.
int FLAG_LINEARSIZE = 0x80000; // Required when pitch is provided for a compressed texture.
int FLAG_DEPTH = 0x800000; // Required in a depth texture.
// Pixel Format Flags
int PIXEL_FORMAT_FLAG_FOURCC = 0x04;
int PIXEL_FORMAT_FLAG_RGB = 0x40;
}
@@ -1,177 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.dds;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.Dimension;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Arrays;
final class DDSHeader {
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide
private int flags;
private int mipMapCount;
private Dimension[] dimensions;
private int pixelFormatFlags;
private int fourCC;
private int bitCount;
private int redMask;
private int greenMask;
private int blueMask;
private int alphaMask;
@SuppressWarnings("unused")
static DDSHeader read(final ImageInputStream imageInput) throws IOException {
DDSHeader header = new DDSHeader();
// Read MAGIC bytes [0,3]
byte[] magic = new byte[DDS.MAGIC.length];
imageInput.readFully(magic);
if (!Arrays.equals(DDS.MAGIC, magic)) {
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%08x', read 0x%08x", new BigInteger(DDS.MAGIC), new BigInteger(magic)));
}
// DDS_HEADER structure
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
int dwSize = imageInput.readInt(); // [4,7]
if (dwSize != DDS.HEADER_SIZE) {
throw new IIOException(String.format("Invalid DDS header size (expected %d): %d", DDS.HEADER_SIZE, dwSize));
}
// Verify flags
header.flags = imageInput.readInt(); // [8,11]
if (!header.getFlag(DDS.FLAG_CAPS
| DDS.FLAG_HEIGHT
| DDS.FLAG_WIDTH
| DDS.FLAG_PIXELFORMAT)) {
throw new IIOException("Required DDS Flag missing in header: " + Integer.toBinaryString(header.flags));
}
// Read Height & Width
int dwHeight = imageInput.readInt(); // [12,15]
int dwWidth = imageInput.readInt(); // [16,19]
int dwPitchOrLinearSize = imageInput.readInt(); // [20,23]
int dwDepth = imageInput.readInt(); // [24,27]
// 0 = (unused) or 1 = (1 level), but still one 'base' image
header.mipMapCount = Math.max(1, imageInput.readInt()); // [28,31]
// build dimensions list
header.addDimensions(dwWidth, dwHeight);
byte[] dwReserved1 = new byte[11 * 4]; // [32,75]
imageInput.readFully(dwReserved1);
// DDS_PIXELFORMAT structure
int px_dwSize = imageInput.readInt(); // [76,79]
header.pixelFormatFlags = imageInput.readInt(); // [80,83]
header.fourCC = imageInput.readInt(); // [84,87]
header.bitCount = imageInput.readInt(); // [88,91]
header.redMask = imageInput.readInt(); // [92,95]
header.greenMask = imageInput.readInt(); // [96,99]
header.blueMask = imageInput.readInt(); // [100,103]
header.alphaMask = imageInput.readInt(); // [104,107]
int dwCaps = imageInput.readInt(); // [108,111]
int dwCaps2 = imageInput.readInt(); // [112,115]
int dwCaps3 = imageInput.readInt(); // [116,119]
int dwCaps4 = imageInput.readInt(); // [120,123]
int dwReserved2 = imageInput.readInt(); // [124,127]
return header;
}
private void addDimensions(int width, int height) {
dimensions = new Dimension[mipMapCount];
int w = width;
int h = height;
for (int i = 0; i < mipMapCount; i++) {
dimensions[i] = new Dimension(w, h);
w /= 2;
h /= 2;
}
}
private boolean getFlag(int mask) {
return (flags & mask) != 0;
}
int getWidth(int imageIndex) {
int lim = dimensions[imageIndex].width;
return (lim <= 0) ? 1 : lim;
}
int getHeight(int imageIndex) {
int lim = dimensions[imageIndex].height;
return (lim <= 0) ? 1 : lim;
}
int getMipMapCount() {
return mipMapCount;
}
int getBitCount() {
return bitCount;
}
int getFourCC() {
return fourCC;
}
int getPixelFormatFlags() {
return pixelFormatFlags;
}
int getRedMask() {
return redMask;
}
int getGreenMask() {
return greenMask;
}
int getBlueMask() {
return blueMask;
}
int getAlphaMask() {
return alphaMask;
}
}
@@ -1,171 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.dds;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Collections;
import java.util.Iterator;
public final class DDSImageReader extends ImageReaderBase {
private DDSHeader header;
public DDSImageReader(final ImageReaderSpi provider) {
super(provider);
}
@Override
protected void resetMembers() {
header = null;
}
@Override
public int getWidth(final int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
return header.getWidth(imageIndex);
}
@Override
public int getHeight(int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
return header.getHeight(imageIndex);
}
@Override
public int getNumImages(final boolean allowSearch) throws IOException {
assertInput();
readHeader();
return header.getMipMapCount();
}
@Override
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
checkBounds(imageIndex);
readHeader();
// TODO: Implement for the specific formats...
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
}
@Override
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
return Collections.singletonList(getRawImageType(imageIndex)).iterator();
}
@Override
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
checkBounds(imageIndex);
readHeader();
processImageStarted(imageIndex);
DDSReader dds = new DDSReader(header);
int[] pixels = dds.read(imageInput, imageIndex);
int width = getWidth(imageIndex);
int height = getHeight(imageIndex);
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), width, height);
Rectangle srcRegion = new Rectangle();
Rectangle destRegion = new Rectangle();
computeRegions(param, width, height, destination, srcRegion, destRegion);
int srcXStep = param != null ? param.getSourceXSubsampling() : 1;
int srcYStep = param != null ? param.getSourceYSubsampling() : 1;
int srcMaxY = srcRegion.y + srcRegion.height;
for (int y = 0, srcY = srcRegion.y, destY = destRegion.y; srcY < srcMaxY; y++, srcY += srcYStep, destY++) {
int offset = width * srcY + srcRegion.x;
subsampleRow(pixels, offset, width, pixels, offset, 4, 8, srcXStep);
destination.setRGB(destRegion.x, destY, destRegion.width, 1, pixels, offset, width);
if (abortRequested()) {
processReadAborted();
break;
}
processImageProgress(100f * y / destRegion.height);
}
processImageComplete();
return destination;
}
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
ImageTypeSpecifier imageType = getRawImageType(imageIndex);
return new DDSMetadata(imageType, header);
}
private void readHeader() throws IOException {
if (header == null) {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
header = DDSHeader.read(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition());
}
imageInput.seek(imageInput.getFlushedPosition());
}
public static void main(final String[] args) throws IOException {
for (String arg : args) {
File file = new File(arg);
BufferedImage image = ImageIO.read(file);
showIt(image, file.getName());
}
}
}
@@ -1,76 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.dds;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Locale;
public final class DDSImageReaderSpi extends ImageReaderSpiBase {
public DDSImageReaderSpi() {
super(new DDSProviderInfo());
}
@Override
public boolean canDecodeInput(final Object source) throws IOException {
if (!(source instanceof ImageInputStream)) {
return false;
}
ImageInputStream stream = (ImageInputStream) source;
stream.mark();
try {
byte[] magic = new byte[DDS.MAGIC.length];
stream.readFully(magic);
return Arrays.equals(DDS.MAGIC, magic);
} finally {
stream.reset();
}
}
@Override
public ImageReader createReaderInstance(Object extension) {
return new DDSImageReader(this);
}
@Override
public String getDescription(Locale locale) {
return "Direct DrawSurface (DDS) Image Reader";
}
}
@@ -1,58 +0,0 @@
/*
* Copyright (c) 2024, 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.dds;
import javax.imageio.ImageTypeSpecifier;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
final class DDSMetadata extends StandardImageMetadataSupport {
DDSMetadata(ImageTypeSpecifier type, DDSHeader header) {
super(builder(type)
.withCompressionTypeName(compressionName(header))
.withFormatVersion("1.0")
);
}
private static String compressionName(DDSHeader header) {
// If the fourCC is valid, compression is one of the DXTn versions, otherwise None
int flags = header.getPixelFormatFlags();
if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
// DXTn
DDSType type = DDSType.valueOf(header.getFourCC());
return type.name();
}
return "None";
}
}
@@ -1,50 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.dds;
import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
final class DDSProviderInfo extends ReaderWriterProviderInfo {
DDSProviderInfo() {
super(
DDSProviderInfo.class,
new String[]{"DDS", "dds"},
new String[]{"dds"},
new String[]{"image/vnd-ms.dds"},
"com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi"},
null,
null,
false, null, null, null, null,
true, null, null, null, null
);
}
}
@@ -1,609 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.
*
* Based on DDSReader.java:
*
* The MIT License (MIT)
*
* Copyright (c) 2015 Kenji Sasaki
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* DDSReader.java
* <p>
* Copyright (c) 2015 Kenji Sasaki
* Released under the MIT license.
* <a href="https://github.com/npedotnet/DDSReader/blob/master/LICENSE">MIT License</a>
* <p>
* <a href="https://github.com/npedotnet/DDSReader/blob/master/README.md">English document</a>
* <p>
* <a href="http://3dtech.jp/wiki/index.php?DDSReader">Japanese document</a>
*/
final class DDSReader {
static final Order ARGB_ORDER = new Order(16, 8, 0, 24);
private final DDSHeader header;
DDSReader(DDSHeader header) {
this.header = header;
}
int[] read(ImageInputStream imageInput, int imageIndex) throws IOException {
// type
DDSType type = getType();
// offset buffer to index mipmap image
byte[] buffer = null;
for (int i = 0; i <= imageIndex; i++) {
int len = getLength(type, i);
buffer = new byte[len];
imageInput.readFully(buffer);
}
int width = header.getWidth(imageIndex);
int height = header.getHeight(imageIndex);
switch (type) {
case DXT1:
return decodeDXT1(width, height, buffer);
case DXT2:
return decodeDXT2(width, height, buffer);
case DXT3:
return decodeDXT3(width, height, buffer);
case DXT4:
return decodeDXT4(width, height, buffer);
case DXT5:
return decodeDXT5(width, height, buffer);
case A1R5G5B5:
return readA1R5G5B5(width, height, buffer);
case X1R5G5B5:
return readX1R5G5B5(width, height, buffer);
case A4R4G4B4:
return readA4R4G4B4(width, height, buffer);
case X4R4G4B4:
return readX4R4G4B4(width, height, buffer);
case R5G6B5:
return readR5G6B5(width, height, buffer);
case R8G8B8:
return readR8G8B8(width, height, buffer);
case A8B8G8R8:
return readA8B8G8R8(width, height, buffer);
case X8B8G8R8:
return readX8B8G8R8(width, height, buffer);
case A8R8G8B8:
return readA8R8G8B8(width, height, buffer);
case X8R8G8B8:
return readX8R8G8B8(width, height, buffer);
default:
throw new IIOException("Unsupported type: " + type);
}
}
private DDSType getType() throws IIOException {
int flags = header.getPixelFormatFlags();
if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
// DXT
int type = header.getFourCC();
return DDSType.valueOf(type);
} else if ((flags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
// RGB
int bitCount = header.getBitCount();
int redMask = header.getRedMask();
int greenMask = header.getGreenMask();
int blueMask = header.getBlueMask();
int alphaMask = ((flags & 0x01) != 0) ? header.getAlphaMask() : 0; // 0x01 alpha
if (bitCount == 16) {
if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
// A1R5G5B5
return DDSType.A1R5G5B5;
} else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
// X1R5G5B5
return DDSType.X1R5G5B5;
} else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
// A4R4G4B4
return DDSType.A4R4G4B4;
} else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
// X4R4G4B4
return DDSType.X4R4G4B4;
} else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
// R5G6B5
return DDSType.R5G6B5;
} else {
throw new IIOException("Unsupported 16bit RGB image.");
}
} else if (bitCount == 24) {
if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
// R8G8B8
return DDSType.R8G8B8;
} else {
throw new IIOException("Unsupported 24bit RGB image.");
}
} else if (bitCount == 32) {
if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
// A8B8G8R8
return DDSType.A8B8G8R8;
} else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
// X8B8G8R8
return DDSType.X8B8G8R8;
} else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
// A8R8G8B8
return DDSType.A8R8G8B8;
} else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
// X8R8G8B8
return DDSType.X8R8G8B8;
} else {
throw new IIOException("Unsupported 32bit RGB image.");
}
} else {
throw new IIOException("Unsupported bit count: " + bitCount);
}
} else {
throw new IIOException("Unsupported YUV or LUMINANCE image.");
}
}
private int getLength(DDSType type, int imageIndex) throws IIOException {
int width = header.getWidth(imageIndex);
int height = header.getHeight(imageIndex);
switch (type) {
case DXT1:
return 8 * ((width + 3) / 4) * ((height + 3) / 4);
case DXT2:
case DXT3:
case DXT4:
case DXT5:
return 16 * ((width + 3) / 4) * ((height + 3) / 4);
case A1R5G5B5:
case X1R5G5B5:
case A4R4G4B4:
case X4R4G4B4:
case R5G6B5:
case R8G8B8:
case A8B8G8R8:
case X8B8G8R8:
case A8R8G8B8:
case X8R8G8B8:
return (type.value() & 0xFF) * width * height;
default:
throw new IIOException("Unknown type: " + Integer.toHexString(type.value()));
}
}
private static int[] decodeDXT1(int width, int height, byte[] buffer) {
int[] pixels = new int[width * height];
int index = 0;
int w = (width + 3) / 4;
int h = (height + 3) / 4;
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
for (int k = 0; k < 4; k++) {
if (4 * i + k >= height) break;
int t0 = (buffer[index] & 0x03);
int t1 = (buffer[index] & 0x0C) >> 2;
int t2 = (buffer[index] & 0x30) >> 4;
int t3 = (buffer[index++] & 0xC0) >> 6;
pixels[4 * width * i + 4 * j + width * k ] = getDXTColor(c0, c1, 0xFF, t0);
if (4 * j + 1 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 1] = getDXTColor(c0, c1, 0xFF, t1);
if (4 * j + 2 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 2] = getDXTColor(c0, c1, 0xFF, t2);
if (4 * j + 3 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 3] = getDXTColor(c0, c1, 0xFF, t3);
}
}
}
return pixels;
}
private static int[] decodeDXT2(int width, int height, byte[] buffer) {
return decodeDXT3(width, height, buffer);
}
private static int[] decodeDXT3(int width, int height, byte[] buffer) {
int index = 0;
int w = (width + 3) / 4;
int h = (height + 3) / 4;
int[] pixels = new int[width * height];
int[] alphaTable = new int[16];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
// create alpha table(4bit to 8bit)
for (int k = 0; k < 4; k++) {
int a0 = (buffer[index++] & 0xFF);
int a1 = (buffer[index++] & 0xFF);
// 4bit alpha to 8bit alpha
alphaTable[4 * k ] = 17 * ((a0 & 0xF0) >> 4);
alphaTable[4 * k + 1] = 17 * (a0 & 0x0F);
alphaTable[4 * k + 2] = 17 * ((a1 & 0xF0) >> 4);
alphaTable[4 * k + 3] = 17 * (a1 & 0x0F);
}
int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
for (int k = 0; k < 4; k++) {
if (4 * i + k >= height) break;
int t0 = (buffer[index] & 0x03);
int t1 = (buffer[index] & 0x0C) >> 2;
int t2 = (buffer[index] & 0x30) >> 4;
int t3 = (buffer[index++] & 0xC0) >> 6;
pixels[4 * width * i + 4 * j + width * k ] = getDXTColor(c0, c1, alphaTable[4 * k ], t0);
if (4 * j + 1 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 1] = getDXTColor(c0, c1, alphaTable[4 * k + 1], t1);
if (4 * j + 2 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 2] = getDXTColor(c0, c1, alphaTable[4 * k + 2], t2);
if (4 * j + 3 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 3] = getDXTColor(c0, c1, alphaTable[4 * k + 3], t3);
}
}
}
return pixels;
}
private static int[] decodeDXT4(int width, int height, byte[] buffer) {
return decodeDXT5(width, height, buffer);
}
private static int[] decodeDXT5(int width, int height, byte[] buffer) {
int index = 0;
int w = (width + 3) / 4;
int h = (height + 3) / 4;
int[] pixels = new int[width * height];
int[] alphaTable = new int[16];
for (int i = 0; i < h; i++) {
for (int j = 0; j < w; j++) {
// create alpha table
int a0 = (buffer[index++] & 0xFF);
int a1 = (buffer[index++] & 0xFF);
int b0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8 | (buffer[index + 2] & 0xFF) << 16;
index += 3;
int b1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8 | (buffer[index + 2] & 0xFF) << 16;
index += 3;
alphaTable[0] = b0 & 0x07;
alphaTable[1] = (b0 >> 3) & 0x07;
alphaTable[2] = (b0 >> 6) & 0x07;
alphaTable[3] = (b0 >> 9) & 0x07;
alphaTable[4] = (b0 >> 12) & 0x07;
alphaTable[5] = (b0 >> 15) & 0x07;
alphaTable[6] = (b0 >> 18) & 0x07;
alphaTable[7] = (b0 >> 21) & 0x07;
alphaTable[8] = b1 & 0x07;
alphaTable[9] = (b1 >> 3) & 0x07;
alphaTable[10] = (b1 >> 6) & 0x07;
alphaTable[11] = (b1 >> 9) & 0x07;
alphaTable[12] = (b1 >> 12) & 0x07;
alphaTable[13] = (b1 >> 15) & 0x07;
alphaTable[14] = (b1 >> 18) & 0x07;
alphaTable[15] = (b1 >> 21) & 0x07;
int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
for (int k = 0; k < 4; k++) {
if (4 * i + k >= height) break;
int t0 = (buffer[index] & 0x03);
int t1 = (buffer[index] & 0x0C) >> 2;
int t2 = (buffer[index] & 0x30) >> 4;
int t3 = (buffer[index++] & 0xC0) >> 6;
pixels[4 * width * i + 4 * j + width * k ] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k ]), t0);
if (4 * j + 1 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 1] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 1]), t1);
if (4 * j + 2 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 2] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 2]), t2);
if (4 * j + 3 >= width) continue;
pixels[4 * width * i + 4 * j + width * k + 3] = getDXTColor(c0, c1, getDXT5Alpha(a0, a1, alphaTable[4 * k + 3]), t3);
}
}
}
return pixels;
}
private static int[] readA1R5G5B5(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int r = BIT5[(rgba & A1R5G5B5_MASKS[0]) >> 10];
int g = BIT5[(rgba & A1R5G5B5_MASKS[1]) >> 5];
int b = BIT5[(rgba & A1R5G5B5_MASKS[2])];
int a = 255 * ((rgba & A1R5G5B5_MASKS[3]) >> 15);
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readX1R5G5B5(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int r = BIT5[(rgba & X1R5G5B5_MASKS[0]) >> 10];
int g = BIT5[(rgba & X1R5G5B5_MASKS[1]) >> 5];
int b = BIT5[(rgba & X1R5G5B5_MASKS[2])];
int a = 255;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readA4R4G4B4(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int r = 17 * ((rgba & A4R4G4B4_MASKS[0]) >> 8);
int g = 17 * ((rgba & A4R4G4B4_MASKS[1]) >> 4);
int b = 17 * ((rgba & A4R4G4B4_MASKS[2]));
int a = 17 * ((rgba & A4R4G4B4_MASKS[3]) >> 12);
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readX4R4G4B4(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int r = 17 * ((rgba & A4R4G4B4_MASKS[0]) >> 8);
int g = 17 * ((rgba & A4R4G4B4_MASKS[1]) >> 4);
int b = 17 * ((rgba & A4R4G4B4_MASKS[2]));
int a = 255;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readR5G6B5(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8;
index += 2;
int r = BIT5[((rgba & R5G6B5_MASKS[0]) >> 11)];
int g = BIT6[((rgba & R5G6B5_MASKS[1]) >> 5)];
int b = BIT5[((rgba & R5G6B5_MASKS[2]))];
int a = 255;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readR8G8B8(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int b = buffer[index++] & 0xFF;
int g = buffer[index++] & 0xFF;
int r = buffer[index++] & 0xFF;
int a = 255;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readA8B8G8R8(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int r = buffer[index++] & 0xFF;
int g = buffer[index++] & 0xFF;
int b = buffer[index++] & 0xFF;
int a = buffer[index++] & 0xFF;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readX8B8G8R8(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int r = buffer[index++] & 0xFF;
int g = buffer[index++] & 0xFF;
int b = buffer[index++] & 0xFF;
int a = 255;
index++;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readA8R8G8B8(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int b = buffer[index++] & 0xFF;
int g = buffer[index++] & 0xFF;
int r = buffer[index++] & 0xFF;
int a = buffer[index++] & 0xFF;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int[] readX8R8G8B8(int width, int height, byte[] buffer) {
int index = 0;
int[] pixels = new int[width * height];
for (int i = 0; i < height * width; i++) {
int b = buffer[index++] & 0xFF;
int g = buffer[index++] & 0xFF;
int r = buffer[index++] & 0xFF;
int a = 255;
index++;
pixels[i] = (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
return pixels;
}
private static int getDXTColor(int c0, int c1, int a, int t) {
switch (t) {
case 0:
return getDXTColor1(c0, a);
case 1:
return getDXTColor1(c1, a);
case 2:
return (c0 > c1) ? getDXTColor2_1(c0, c1, a) : getDXTColor1_1(c0, c1, a);
case 3:
return (c0 > c1) ? getDXTColor2_1(c1, c0, a) : 0;
}
return 0;
}
private static int getDXTColor2_1(int c0, int c1, int a) {
// 2*c0/3 + c1/3
int r = (2 * BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 3;
int g = (2 * BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 3;
int b = (2 * BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 3;
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
private static int getDXTColor1_1(int c0, int c1, int a) {
// (c0+c1) / 2
int r = (BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 2;
int g = (BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 2;
int b = (BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 2;
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
private static int getDXTColor1(int c, int a) {
int r = BIT5[(c & 0xFC00) >> 11];
int g = BIT6[(c & 0x07E0) >> 5];
int b = BIT5[(c & 0x001F)];
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
private static int getDXT5Alpha(int a0, int a1, int t) {
if (a0 > a1) switch (t) {
case 0:
return a0;
case 1:
return a1;
case 2:
return (6 * a0 + a1) / 7;
case 3:
return (5 * a0 + 2 * a1) / 7;
case 4:
return (4 * a0 + 3 * a1) / 7;
case 5:
return (3 * a0 + 4 * a1) / 7;
case 6:
return (2 * a0 + 5 * a1) / 7;
case 7:
return (a0 + 6 * a1) / 7;
}
else switch (t) {
case 0:
return a0;
case 1:
return a1;
case 2:
return (4 * a0 + a1) / 5;
case 3:
return (3 * a0 + 2 * a1) / 5;
case 4:
return (2 * a0 + 3 * a1) / 5;
case 5:
return (a0 + 4 * a1) / 5;
case 6:
return 0;
case 7:
return 255;
}
return 0;
}
// RGBA Masks
private static final int[] A1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x8000};
private static final int[] X1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x0000};
private static final int[] A4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0xF000};
private static final int[] X4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0x0000};
private static final int[] R5G6B5_MASKS = {0xF800, 0x07E0, 0x001F, 0x0000};
private static final int[] R8G8B8_MASKS = {0xFF0000, 0x00FF00, 0x0000FF, 0x000000};
private static final int[] A8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000};
private static final int[] X8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000};
private static final int[] A8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
private static final int[] X8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000};
// BIT4 = 17 * index;
private static final int[] BIT5 = {0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255};
private static final int[] BIT6 = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255};
private static final class Order {
Order(int redShift, int greenShift, int blueShift, int alphaShift) {
this.redShift = redShift;
this.greenShift = greenShift;
this.blueShift = blueShift;
this.alphaShift = alphaShift;
}
public int redShift;
public int greenShift;
public int blueShift;
public int alphaShift;
}
}
@@ -1,70 +0,0 @@
/*
* Copyright (c) 2024, Paul Allen, 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.dds;
enum DDSType {
DXT1(0x31545844),
DXT2(0x32545844),
DXT3(0x33545844),
DXT4(0x34545844),
DXT5(0x35545844),
A1R5G5B5((1 << 16) | 2),
X1R5G5B5((2 << 16) | 2),
A4R4G4B4((3 << 16) | 2),
X4R4G4B4((4 << 16) | 2),
R5G6B5((5 << 16) | 2),
R8G8B8((1 << 16) | 3),
A8B8G8R8((1 << 16) | 4),
X8B8G8R8((2 << 16) | 4),
A8R8G8B8((3 << 16) | 4),
X8R8G8B8((4 << 16) | 4);
private final int value;
DDSType(int value) {
this.value = value;
}
public int value() {
return value;
}
public static DDSType valueOf(int value) {
for (DDSType type : DDSType.values()) {
if (value == type.value()) {
return type;
}
}
throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", value));
}
}
@@ -1 +0,0 @@
com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi
@@ -1,107 +0,0 @@
/*
* Copyright (c) 2024, 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.dds;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.Dimension;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class DDSImageReaderTest extends ImageReaderAbstractTest<DDSImageReader> {
@Override
protected ImageReaderSpi createProvider() {
return new DDSImageReaderSpi();
}
@Override
protected List<TestData> getTestData() {
Dimension dim256 = new Dimension(256, 256);
Dimension dim128 = new Dimension(128, 128);
Dimension dim64 = new Dimension(64, 64);
Dimension dim32 = new Dimension(32, 32);
Dimension dim16 = new Dimension(16, 16);
Dimension dim8 = new Dimension(8, 8);
Dimension dim4 = new Dimension(4, 4);
Dimension dim2 = new Dimension(2, 2);
Dimension dim1 = new Dimension(1, 1);
return Arrays.asList(
new TestData(getClassLoaderResource("/dds/dds_A1R5G5B5.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_A1R5G5B5_mipmap.dds"), dim256, dim128, dim64, dim32, dim16, dim8, dim4, dim2, dim1),
new TestData(getClassLoaderResource("/dds/dds_A4R4G4B4.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_A4R4G4B4_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_A8B8G8R8.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_A8B8G8R8_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_A8R8G8B8.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_A8R8G8B8_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_DXT1.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_DXT1_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_DXT2.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_DXT2_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_DXT3.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_DXT3_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_DXT4.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_DXT4_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_DXT5.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_DXT5_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_R5G6B5.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_R5G6B5_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_R8G8B8.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_R8G8B8_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_X1R5G5B5.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_X1R5G5B5_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_X4R4G4B4.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_X4R4G4B4_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_X8B8G8R8.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_X8B8G8R8_mipmap.dds"), dim256, dim128, dim64),
new TestData(getClassLoaderResource("/dds/dds_X8R8G8B8.dds"), dim256),
new TestData(getClassLoaderResource("/dds/dds_X8R8G8B8_mipmap.dds"), dim256, dim128, dim64)
);
}
@Override
protected List<String> getFormatNames() {
return Arrays.asList("DDS", "dds");
}
@Override
protected List<String> getSuffixes() {
return Collections.singletonList("dds");
}
@Override
protected List<String> getMIMETypes() {
return Collections.singletonList("image/vnd-ms.dds");
}
}

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