mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-21 00:00:01 -04:00
Compare commits
351 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 666e0ef94a | |||
| b58c4ba5dd | |||
| 6328862e4f | |||
| a95235b422 | |||
| 9f4b09fc7d | |||
| 03455f0132 | |||
| ac5779d8d6 | |||
| fef8ff3aab | |||
| a9e4b2e262 | |||
| 7147e2dfb1 | |||
| 1680fadf83 | |||
| d1df8c13ed | |||
| 031937fe99 | |||
| 24c473ae45 | |||
| 57941cb638 | |||
| d325b6deec | |||
| 3f67430b2d | |||
| 314071dde8 | |||
| 83ad25c2dd | |||
| d7dae90e2e | |||
| ad437c2470 | |||
| dabc26bdb5 | |||
| b441298a9a | |||
| 345ca0ac13 | |||
| 9720a931c5 | |||
| 4d3f691e6a | |||
| 2e0e575183 | |||
| 2c7c47b158 | |||
| 57680f1bec | |||
| 8dc83c4e9e | |||
| 929bea6b6b | |||
| 699662d054 | |||
| 1bebee1851 | |||
| b0e8dd86bb | |||
| 5bdb0b5502 | |||
| 85d47528a3 | |||
| a7ebd66149 | |||
| f0a032a7b9 | |||
| 690cb064e7 | |||
| e65f471a8f | |||
| f2cc9faaf8 | |||
| 8bd3f4f34a | |||
| be348543d8 | |||
| 80229b8c3c | |||
| a5ee53569d | |||
| c22ada03cd | |||
| b61341ce3b | |||
| e73b0a7bcf | |||
| ea9d1c0dc5 | |||
| e0563ee7dd | |||
| 44bcd202e8 | |||
| 5b57b51ff1 | |||
| 0b5f8a071e | |||
| d3dc578936 | |||
| 2ae937b870 | |||
| 2a7763299a | |||
| a57a1d35f9 | |||
| e618805786 | |||
| e3cb923d37 | |||
| 4513b0c166 | |||
| b55d4e8c1b | |||
| 399c75f59e | |||
| a7f7d73e2d | |||
| 41a83191b7 | |||
| 49e805eeb7 | |||
| f426637fdd | |||
| 4081ff1545 | |||
| ae4fbcc726 | |||
| 66208bfa03 | |||
| e72255805e | |||
| 780ccd7916 | |||
| 957e917281 | |||
| 9daf8337ee | |||
| 89c104d59f | |||
| 8608ee76c5 | |||
| aab2d36e92 | |||
| 608b37232d | |||
| e3ebf8e0fa | |||
| 9af10625a2 | |||
| ccc4e7b411 | |||
| 439323c5e5 | |||
| 332e191d0d | |||
| 58d1a89028 | |||
| 72fe799cc8 | |||
| 18af213e27 | |||
| 9d267f352f | |||
| f8f6f9f1e4 | |||
| 2841d26785 | |||
| 98ae5967ca | |||
| da9d87a561 | |||
| d472191926 | |||
| a8472170c4 | |||
| b2f7cada21 | |||
| 2a4c152c3d | |||
| 660932b7e7 | |||
| d0a17ff3b3 | |||
| 4259903bdd | |||
| 3018d2a342 | |||
| 8f94318f28 | |||
| 60b7151eb6 | |||
| 8ad21307e8 | |||
| 8c854f1e20 | |||
| 1445ff2533 | |||
| e8f1e80d4e | |||
| 18e29e9ed5 | |||
| ec4003b1c1 | |||
| 164b8db988 | |||
| 21feace385 | |||
| ba1f754611 | |||
| 822b5da631 | |||
| c785f6932f | |||
| 43e2a27c7f | |||
| 9d50acd2fe | |||
| ba73a308d6 | |||
| 20cd259abd | |||
| 033a1423ff | |||
| 7c8c520006 | |||
| 9162458e36 | |||
| d38af3cb16 | |||
| c57e8f80fa | |||
| 4750359ada | |||
| cbe8038619 | |||
| 2d8125e69c | |||
| 57a664c093 | |||
| 7f82377fd7 | |||
| 72b9f19a51 | |||
| 0083b8e77e | |||
| 79982cd493 | |||
| f0db338f3b | |||
| 9db4e0b3ed | |||
| cd4cbdcb82 | |||
| 15dc4b3852 | |||
| a3534ecd59 | |||
| 7bb5fee23b | |||
| 6cb7424bd0 | |||
| 9aa04d311e | |||
| 967e71dc92 | |||
| 628523ddc8 | |||
| 783c28ae0e | |||
| d35d67651f | |||
| 816d48efef | |||
| 25ee21d090 | |||
| 536ea7ba88 | |||
| d2687383f5 | |||
| 3ce35e059c | |||
| 1d5359dd35 | |||
| 2699b75b79 | |||
| 4e614dfc7e | |||
| 3a2efd9491 | |||
| c5dc2e4e53 | |||
| 41460bd32a | |||
| 13b37b3839 | |||
| 6dd74070f4 | |||
| 81b358b377 | |||
| 9715f4e74c | |||
| 38256c8be0 | |||
| bf3c1fad17 | |||
| b8488ae39b | |||
| d3bea0ae38 | |||
| 9435410b1e | |||
| f74e8c8ba1 | |||
| 0ae2c2f01d | |||
| 77c81a06bc | |||
| 2bbcd88798 | |||
| 829fbe7547 | |||
| 078425eed9 | |||
| 8ddcbbd2b2 | |||
| 507cca5fd7 | |||
| a4caac0c82 | |||
| 54c07b849c | |||
| c531d4f5d3 | |||
| aa2e8e5d7e | |||
| 76a35331b0 | |||
| 6b3f1c6ee3 | |||
| c731e10e8f | |||
| 4a8c3530f7 | |||
| e8996daa12 | |||
| 9196e60c74 | |||
| eabb8fd02b | |||
| 1794e336de | |||
| ac7612b3df | |||
| 606fd53823 | |||
| 614a07e040 | |||
| 34e8d88007 | |||
| 9b727df901 | |||
| f1f98bb4a4 | |||
| b34b26e08c | |||
| 993e07ee34 | |||
| a377712bdb | |||
| e5dc6aa878 | |||
| 46b1c1cf96 | |||
| d9300b1c90 | |||
| 0a2efb9eac | |||
| 3eabc591d8 | |||
| 5cefce2dbf | |||
| 4c645c0220 | |||
| 703848ca45 | |||
| 0ebd18fcb6 | |||
| 29f7547a99 | |||
| 25cd351eee | |||
| 77c98c917e | |||
| 4bbe946f46 | |||
| 78832ed923 | |||
| 164cc11592 | |||
| 8f5c1b409f | |||
| 26981513d8 | |||
| a3a30d54d4 | |||
| 102e9cff51 | |||
| da800be8c8 | |||
| 70493bd323 | |||
| 304d050bc3 | |||
| 0443172666 | |||
| cee2663f06 | |||
| 8f44cfc43c | |||
| 8a240aac68 | |||
| 61424f33b6 | |||
| c7b9b1fadd | |||
| ab08ec1e0d | |||
| cbe78dc67f | |||
| c9e11f171f | |||
| bc2c0c2301 | |||
| 6581e2e2a1 | |||
| c2873b1f27 | |||
| 35f2f0be9f | |||
| b5856fd110 | |||
| 1919d77a45 | |||
| 37afe24aac | |||
| 081f2efea2 | |||
| 627bb1bf1f | |||
| a77b62b6ba | |||
| 2500d8cc15 | |||
| c01336fb8a | |||
| 6f9b9bee01 | |||
| b9b1a35408 | |||
| 7ed5663633 | |||
| 6458fcdcbd | |||
| 9375bfda9a | |||
| 0160fb70f8 | |||
| 29dca0f124 | |||
| c21f14efe1 | |||
| 81ba43e1e8 | |||
| a1fcfc3958 | |||
| c60116a611 | |||
| 15e6ddc1fd | |||
| 49f4e5401e | |||
| e333c7d1b2 | |||
| cda34b704b | |||
| 7c4487be04 | |||
| 5a4525aaa1 | |||
| b766420e3e | |||
| c858454c5a | |||
| 67b48ce1e3 | |||
| 6608f61353 | |||
| 326b98d5e5 | |||
| fafa58b718 | |||
| 0ed0246762 | |||
| b3004a1227 | |||
| 7ab627a754 | |||
| 008e57a7ce | |||
| 28270b4d5b | |||
| 7382151db8 | |||
| b856ce07af | |||
| 190fe87ee9 | |||
| d1872ce94f | |||
| a5c52a99b4 | |||
| 4170b393fa | |||
| 53f9ba91e0 | |||
| be2d7d5f10 | |||
| 00aec2c90e | |||
| 2b04f7205c | |||
| 7d401d0194 | |||
| 48691139a3 | |||
| bcb87c09d2 | |||
| 84a8ceeb93 | |||
| 0cb99feedf | |||
| 91493c5145 | |||
| 6ddb799a95 | |||
| 8a187f6657 | |||
| b7d865f2cf | |||
| d50fb1a51e | |||
| 8992406f50 | |||
| 44eebff62f | |||
| 8c85c4ca96 | |||
| fa5c77bff0 | |||
| d87b80deea | |||
| ae138c3b4e | |||
| ab13fdd09d | |||
| aab5b062bd | |||
| 00d6acd1bf | |||
| 0f8a7ea482 | |||
| 9fe87fe10d | |||
| a33dbaf897 | |||
| 9e2f369459 | |||
| d34b0b7fcf | |||
| 5effcb1344 | |||
| b67d687761 | |||
| d0881c8b5c | |||
| 976928f48c | |||
| e1c2f2ee73 | |||
| 92632fa2a3 | |||
| 5a563e315f | |||
| c06d47d123 | |||
| fea6beb364 | |||
| 4b951c06cc | |||
| a3e6e52c95 | |||
| 5347015cbd | |||
| 4d190892df | |||
| 60eab81709 | |||
| b400b6b157 | |||
| 499b3ef120 | |||
| 92bc9c73f6 | |||
| 2a77558cac | |||
| 816cad60a8 | |||
| 7167f81c69 | |||
| f5cfa0e619 | |||
| 73ad024833 | |||
| 379449b621 | |||
| e17faad6fb | |||
| 1271a3d55e | |||
| 1cd594d113 | |||
| b76f74e79a | |||
| 78817a489b | |||
| b8f2a80ca6 | |||
| ac8a36db1c | |||
| 7e0d8922da | |||
| 9a6b8c9bfe | |||
| eced5b8efd | |||
| 74611e4e52 | |||
| b8614eca4d | |||
| efd24456ac | |||
| 191b2371c8 | |||
| 33419ef291 | |||
| 123f0bb7fc | |||
| 99b5f28a49 | |||
| b30fb4f8c3 | |||
| dc0bdcbd5b | |||
| 0cf29c167d | |||
| 98e4b76206 | |||
| aa4b5db054 | |||
| 433311c10d | |||
| f50178bc78 | |||
| e016e970e5 | |||
| 4223d13898 | |||
| 444aeabf21 | |||
| 05507a59d6 | |||
| c4c89a0a25 | |||
| b0ad6b2a4b | |||
| 25c703f4b2 | |||
| 529c59f93f | |||
| 584b1d9b21 | |||
| 312ce364cc |
@@ -7,14 +7,14 @@ assignees: ''
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Is your feature request related to a problem? Please describe.**
|
**Is your feature request related to a use case or a problem you are working on? Please describe.**
|
||||||
A clear and concise description of what the problem or use case is.
|
A clear and concise description of what the problem or use case is. Understanding the rationale is key, to be able to implemeent the right solution.
|
||||||
|
|
||||||
**Describe the solution you'd like**
|
**Describe the solution you'd like**
|
||||||
A clear and concise description of what you want to happen.
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
**Describe alternatives you've considered**
|
**Describe alternatives you've considered**
|
||||||
A clear and concise description of any alternative solutions or features you've considered.
|
A clear and concise description of any alternative solutions or features you've already considered, and why they won't work.
|
||||||
|
|
||||||
**Additional context**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
|
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
# Maven/Java library updates
|
||||||
|
- package-ecosystem: "maven"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
# GitHub actions updates
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/.github/workflows"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '**'
|
||||||
|
- '!dependabot/**'
|
||||||
|
pull_request:
|
||||||
|
branches: [ 'master' ]
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||||
|
java: [ 8, 11, 17, 21 ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: ${{ matrix.java }}
|
||||||
|
java-package: jdk
|
||||||
|
cache: 'maven'
|
||||||
|
- name: Run Tests
|
||||||
|
run: mvn --batch-mode --no-transfer-progress test
|
||||||
|
- name: Publish Test Report
|
||||||
|
uses: mikepenz/action-junit-report@0831a82caad2465c31c6dd929978f640cb42556c # v4.0.3
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||||
|
check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
|
||||||
|
|
||||||
|
test_oracle:
|
||||||
|
name: Test Oracle JDK 8 with KCMS=${{ matrix.kcms }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
kcms: [ true, false ]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- run: |
|
||||||
|
download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292"
|
||||||
|
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
|
||||||
|
- uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||||
|
with:
|
||||||
|
distribution: 'jdkfile'
|
||||||
|
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||||
|
java-version: '8'
|
||||||
|
cache: 'maven'
|
||||||
|
- name: Set MAVEN_OPTS
|
||||||
|
if: ${{ matrix.kcms }}
|
||||||
|
run: |
|
||||||
|
echo "MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider" >> $GITHUB_ENV
|
||||||
|
- name: Display Java version
|
||||||
|
run: java -version
|
||||||
|
- name: Run Tests
|
||||||
|
run: mvn --batch-mode --no-transfer-progress test
|
||||||
|
- name: Publish Test Report
|
||||||
|
uses: mikepenz/action-junit-report@0831a82caad2465c31c6dd929978f640cb42556c # v4.0.3
|
||||||
|
if: ${{ !cancelled() }}
|
||||||
|
with:
|
||||||
|
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||||
|
check_name: Unit Test Results for Oracle JDK 8 with KCMS=${{ matrix.kcms }}
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Deploy
|
||||||
|
needs: [ test, test_oracle ]
|
||||||
|
if: github.ref == 'refs/heads/master' # only perform on latest master
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
- name: Set up Maven Central
|
||||||
|
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||||
|
with: # running setup-java again overwrites the settings.xml
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '8'
|
||||||
|
java-package: jdk
|
||||||
|
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
|
||||||
|
server-username: MAVEN_CENTRAL_USERNAME # env variable for username in deploy (1)
|
||||||
|
server-password: MAVEN_CENTRAL_PASSWORD # env variable for token in deploy (2)
|
||||||
|
gpg-private-key: ${{ secrets.GPG_KEY }} # Value of the GPG private key to import
|
||||||
|
gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE # env variable for GPG private key passphrase (3)
|
||||||
|
- name: Get Project Version
|
||||||
|
run: |
|
||||||
|
echo "PROJECT_VERSION=$(mvn --batch-mode help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
|
||||||
|
- name: Publish to Maven Central
|
||||||
|
if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
|
||||||
|
run: mvn --batch-mode --no-transfer-progress deploy -P release -DskipTests
|
||||||
|
env:
|
||||||
|
MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} # must be the same env variable name as (1)
|
||||||
|
MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} # must be the same env variable name as (2)
|
||||||
|
MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # must be the same env variable name as (3)
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
pull_request:
|
||||||
|
# The branches below must be a subset of the branches above
|
||||||
|
branches: [ "master" ]
|
||||||
|
schedule:
|
||||||
|
- cron: '26 13 * * 6'
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze
|
||||||
|
# Runner size impacts CodeQL analysis time. To learn more, please see:
|
||||||
|
# - https://gh.io/recommended-hardware-resources-for-running-codeql
|
||||||
|
# - https://gh.io/supported-runners-and-hardware-resources
|
||||||
|
# - https://gh.io/using-larger-runners
|
||||||
|
# Consider using larger runners for possible analysis time improvements.
|
||||||
|
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
|
||||||
|
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'java' ]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
|
||||||
|
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||||
|
# queries: security-extended,security-and-quality
|
||||||
|
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||||
|
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# echo "Run, Build Application using script"
|
||||||
|
# ./location_of_script_within_repo/buildscript.sh
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
with:
|
||||||
|
category: "/language:${{matrix.language}}"
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||||
|
# by a third-party and are governed by separate terms of service, privacy
|
||||||
|
# policy, and support documentation.
|
||||||
|
|
||||||
|
name: Scorecard supply-chain security
|
||||||
|
on:
|
||||||
|
# For Branch-Protection check. Only the default branch is supported. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||||
|
branch_protection_rule:
|
||||||
|
# To guarantee Maintained check is occasionally updated. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||||
|
schedule:
|
||||||
|
- cron: '38 8 * * 2'
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
permissions: read-all # Declare default permissions as read only.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Scorecard analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
security-events: write # to upload the results to code-scanning dashboard.
|
||||||
|
id-token: write # to publish results and get a badge
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: "Run analysis"
|
||||||
|
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||||
|
with:
|
||||||
|
results_file: results.sarif
|
||||||
|
results_format: sarif
|
||||||
|
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||||
|
# you want to enable the Branch-Protection check on the repository
|
||||||
|
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-fine-grained-pat-optional.
|
||||||
|
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||||
|
|
||||||
|
# Publish Results:
|
||||||
|
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||||
|
# - Allows the repository to include the Scorecard badge.
|
||||||
|
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||||
|
publish_results: true
|
||||||
|
|
||||||
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
|
# format to the repository Actions tab.
|
||||||
|
- name: "Upload artifact"
|
||||||
|
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||||
|
with:
|
||||||
|
name: SARIF file
|
||||||
|
path: results.sarif
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
|
- name: "Upload to code-scanning"
|
||||||
|
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
||||||
@@ -15,3 +15,4 @@ private
|
|||||||
profiles.xml
|
profiles.xml
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/.metadata/
|
||||||
|
|||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
dist: trusty
|
|
||||||
language: java
|
|
||||||
jdk:
|
|
||||||
- oraclejdk8 # LTS until Mar 2022
|
|
||||||
- oraclejdk11 # LTS until Sep 2023
|
|
||||||
- oraclejdk15 # out of support
|
|
||||||
- oraclejdk16 # out of support
|
|
||||||
- oraclejdk17 # LTS until Sep 2026
|
|
||||||
jobs:
|
|
||||||
include:
|
|
||||||
# Extra job, testing legacy CMM option
|
|
||||||
- jdk: oraclejdk8
|
|
||||||
env: MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $HOME/.m2
|
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
[](https://travis-ci.com/github/haraldk/TwelveMonkeys)
|
[](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml)
|
||||||
[](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
|
[](https://github.com/haraldk/TwelveMonkeys/actions/workflows/codeql.yml)
|
||||||
|
[](https://securityscorecards.dev/viewer/?uri=github.com/haraldk/TwelveMonkeys)
|
||||||
|
[](https://www.bestpractices.dev/projects/7900)
|
||||||
|
|
||||||
|
[](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio)
|
||||||
|
[](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/)
|
||||||
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
||||||
[](https://paypal.me/haraldk76/100)
|
[](https://paypal.me/haraldk76/100)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## 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.
|
||||||
@@ -16,40 +23,40 @@ As there is lots of legacy data out there, we see the need for open implementati
|
|||||||
|
|
||||||
## File formats supported
|
## File formats supported
|
||||||
|
|
||||||
| Plugin | Format | Description | Read | Write | Metadata | Notes |
|
| Plugin | Format | Description | R | W | Metadata | Notes |
|
||||||
| ------ | -------- | ----------- |:----:|:-----:| -------- | ----- |
|
| ------ | -------- |---------------------------------------------------------|:---:|:---:| -------- | ----- |
|
||||||
| Batik | **SVG** | Scalable Vector Graphics | âś” | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/)
|
| Batik | **SVG** | Scalable Vector Graphics | âś” | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) |
|
||||||
| | WMF | MS Windows Metafile | âś” | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/)
|
| | WMF | MS Windows Metafile | âś” | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) |
|
||||||
| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/bmp_metadata.html) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/bmp_metadata.html), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | CUR | MS Windows Cursor Format | âś” | - | - |
|
| | CUR | MS Windows Cursor Format | âś” | - | - |
|
||||||
| | ICO | MS Windows Icon Format | âś” | âś” | - |
|
| | ICO | MS Windows Icon Format | âś” | âś” | - |
|
||||||
| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | âś” | âś” | - |
|
| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | âś” | âś” | - |
|
||||||
| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | JPEG Lossless | | âś” | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | JPEG Lossless | | âś” | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | DCX | Multi-page PCX fax document | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | DCX | Multi-page PCX fax document | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | PNTG | Apple MacPaint Picture Format | âś” | | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | PNTG | Apple MacPaint Picture Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | PBM | NetPBM Portable Bit Map | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | PBM | NetPBM Portable Bit Map | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | PGM | NetPBM Portable Grey Map | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | PGM | NetPBM Portable Grey Map | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | PPM | NetPBM Portable Pix Map | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | PPM | NetPBM Portable Pix Map | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | PFM | Portable Float Map | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | PFM | Portable Float Map | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | âś” | - | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | âś” | (âś”) | Native, [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | PSB | Adobe Photoshop Large Document | âś” | - | Native & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | PSB | Adobe Photoshop Large Document | âś” | - | Native, [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | âś” | âś” | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | âś” | - | - | OLE2 Compound Document based format only
|
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | âś” | - | - | OLE2 Compound Document based format only |
|
||||||
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| | BigTIFF | | âś” | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata) & [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| | BigTIFF | | âś” | âś” | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
| XWD | XWD | X11 Window Dump Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
| XWD | XWD | X11 Window Dump Format | âś” | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||||
|
|
||||||
|
|
||||||
**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 version 1.14 or later.*
|
and make sure you use an updated and secure version.*
|
||||||
|
|
||||||
Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the
|
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).
|
||||||
@@ -271,12 +278,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.7.0</version>
|
<version>3.9.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio-tiff</artifactId>
|
<artifactId>imageio-tiff</artifactId>
|
||||||
<version>3.7.0</version>
|
<version>3.9.4</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
@@ -286,7 +293,17 @@ 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.7.0</version>
|
<version>3.9.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Or Jakarta version, for Servlet API 5.0
|
||||||
|
-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||||
|
<artifactId>servlet</artifactId>
|
||||||
|
<version>3.9.4</version>
|
||||||
|
<classifier>jakarta</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
```
|
```
|
||||||
@@ -295,13 +312,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
|
|||||||
|
|
||||||
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
|
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
|
||||||
|
|
||||||
twelvemonkeys-common-lang-3.7.0.jar
|
twelvemonkeys-common-lang-3.9.4.jar
|
||||||
twelvemonkeys-common-io-3.7.0.jar
|
twelvemonkeys-common-io-3.9.4.jar
|
||||||
twelvemonkeys-common-image-3.7.0.jar
|
twelvemonkeys-common-image-3.9.4.jar
|
||||||
twelvemonkeys-imageio-core-3.7.0.jar
|
twelvemonkeys-imageio-core-3.9.4.jar
|
||||||
twelvemonkeys-imageio-metadata-3.7.0.jar
|
twelvemonkeys-imageio-metadata-3.9.4.jar
|
||||||
twelvemonkeys-imageio-jpeg-3.7.0.jar
|
twelvemonkeys-imageio-jpeg-3.9.4.jar
|
||||||
twelvemonkeys-imageio-tiff-3.7.0.jar
|
twelvemonkeys-imageio-tiff-3.9.4.jar
|
||||||
|
|
||||||
#### Deploying the plugins in a web app
|
#### Deploying the plugins in a web app
|
||||||
|
|
||||||
@@ -367,81 +384,50 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
|
|||||||
|
|
||||||
### Links to prebuilt binaries
|
### Links to prebuilt binaries
|
||||||
|
|
||||||
##### Latest version (3.7.0)
|
##### Latest version (3.9.4)
|
||||||
|
|
||||||
Requires Java 7 or later.
|
The latest version that will run on Java 7 is 3.9.4. Later versions will require Java 8 or later.
|
||||||
|
|
||||||
Common dependencies
|
Common dependencies
|
||||||
* [common-lang-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.7.0/common-lang-3.7.0.jar)
|
* [common-lang-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.9.4/common-lang-3.9.4.jar)
|
||||||
* [common-io-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.7.0/common-io-3.7.0.jar)
|
* [common-io-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.9.4/common-io-3.9.4.jar)
|
||||||
* [common-image-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.7.0/common-image-3.7.0.jar)
|
* [common-image-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.9.4/common-image-3.9.4.jar)
|
||||||
|
|
||||||
ImageIO dependencies
|
ImageIO dependencies
|
||||||
* [imageio-core-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.7.0/imageio-core-3.7.0.jar)
|
* [imageio-core-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.9.4/imageio-core-3.9.4.jar)
|
||||||
* [imageio-metadata-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.7.0/imageio-metadata-3.7.0.jar)
|
* [imageio-metadata-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.9.4/imageio-metadata-3.9.4.jar)
|
||||||
|
|
||||||
ImageIO plugins
|
ImageIO plugins
|
||||||
* [imageio-bmp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.7.0/imageio-bmp-3.7.0.jar)
|
* [imageio-bmp-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.9.4/imageio-bmp-3.9.4.jar)
|
||||||
* [imageio-hdr-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.7.0/imageio-hdr-3.7.0.jar)
|
* [imageio-hdr-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.9.4/imageio-hdr-3.9.4.jar)
|
||||||
* [imageio-icns-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.7.0/imageio-icns-3.7.0.jar)
|
* [imageio-icns-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.9.4/imageio-icns-3.9.4.jar)
|
||||||
* [imageio-iff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.7.0/imageio-iff-3.7.0.jar)
|
* [imageio-iff-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.9.4/imageio-iff-3.9.4.jar)
|
||||||
* [imageio-jpeg-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.7.0/imageio-jpeg-3.7.0.jar)
|
* [imageio-jpeg-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.9.4/imageio-jpeg-3.9.4.jar)
|
||||||
* [imageio-pcx-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.7.0/imageio-pcx-3.7.0.jar)
|
* [imageio-pcx-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.9.4/imageio-pcx-3.9.4.jar)
|
||||||
* [imageio-pict-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.7.0/imageio-pict-3.7.0.jar)
|
* [imageio-pict-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.9.4/imageio-pict-3.9.4.jar)
|
||||||
* [imageio-pnm-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.7.0/imageio-pnm-3.7.0.jar)
|
* [imageio-pnm-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.9.4/imageio-pnm-3.9.4.jar)
|
||||||
* [imageio-psd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.7.0/imageio-psd-3.7.0.jar)
|
* [imageio-psd-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.9.4/imageio-psd-3.9.4.jar)
|
||||||
* [imageio-sgi-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.7.0/imageio-sgi-3.7.0.jar)
|
* [imageio-sgi-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.9.4/imageio-sgi-3.9.4.jar)
|
||||||
* [imageio-tga-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.7.0/imageio-tga-3.7.0.jar)
|
* [imageio-tga-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.9.4/imageio-tga-3.9.4.jar)
|
||||||
* [imageio-thumbsdb-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.7.0/imageio-thumbsdb-3.7.0.jar)
|
* [imageio-thumbsdb-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.9.4/imageio-thumbsdb-3.9.4.jar)
|
||||||
* [imageio-tiff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.7.0/imageio-tiff-3.7.0.jar)
|
* [imageio-tiff-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.9.4/imageio-tiff-3.9.4.jar)
|
||||||
* [imageio-webp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.7.0/imageio-webp-3.7.0.jar)
|
* [imageio-webp-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.9.4/imageio-webp-3.9.4.jar)
|
||||||
* [imageio-xwd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.7.0/imageio-xwd-3.7.0.jar)
|
* [imageio-xwd-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.9.4/imageio-xwd-3.9.4.jar)
|
||||||
|
|
||||||
ImageIO plugins requiring 3rd party libs
|
ImageIO plugins requiring 3rd party libs
|
||||||
* [imageio-batik-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.7.0/imageio-batik-3.7.0.jar)
|
* [imageio-batik-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.9.4/imageio-batik-3.9.4.jar)
|
||||||
|
|
||||||
Photoshop Path support for ImageIO
|
Photoshop Path support for ImageIO
|
||||||
* [imageio-clippath-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.7.0/imageio-clippath-3.7.0.jar)
|
* [imageio-clippath-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.9.4/imageio-clippath-3.9.4.jar)
|
||||||
|
|
||||||
Servlet support
|
Servlet support
|
||||||
* [servlet-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.7.0/servlet-3.7.0.jar)
|
* [servlet-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.9.4/servlet-3.9.4.jar)
|
||||||
|
|
||||||
##### Old version (3.0.x)
|
|
||||||
|
|
||||||
Use this version for projects that requires Java 6 or need the JMagick support. *Does not support Java 8 or later*.
|
|
||||||
|
|
||||||
Common dependencies
|
|
||||||
* [common-lang-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.0.2/common-lang-3.0.2.jar)
|
|
||||||
* [common-io-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.0.2/common-io-3.0.2.jar)
|
|
||||||
* [common-image-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.0.2/common-image-3.0.2.jar)
|
|
||||||
|
|
||||||
ImageIO dependencies
|
|
||||||
* [imageio-core-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.0.2/imageio-core-3.0.2.jar)
|
|
||||||
* [imageio-metadata-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.0.2/imageio-metadata-3.0.2.jar)
|
|
||||||
|
|
||||||
ImageIO plugins
|
|
||||||
* [imageio-jpeg-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.0.2/imageio-jpeg-3.0.2.jar)
|
|
||||||
* [imageio-tiff-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.0.2/imageio-tiff-3.0.2.jar)
|
|
||||||
* [imageio-psd-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.0.2/imageio-psd-3.0.2.jar)
|
|
||||||
* [imageio-pict-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.0.2/imageio-pict-3.0.2.jar)
|
|
||||||
* [imageio-iff-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.0.2/imageio-iff-3.0.2.jar)
|
|
||||||
* [imageio-icns-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.0.2/imageio-icns-3.0.2.jar)
|
|
||||||
* [imageio-ico-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-ico/3.0.2/imageio-ico-3.0.2.jar)
|
|
||||||
* [imageio-thumbsdb-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.0.2/imageio-thumbsdb-3.0.2.jar)
|
|
||||||
|
|
||||||
ImageIO plugins requiring 3rd party libs
|
|
||||||
* [imageio-batik-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.0.2/imageio-batik-3.0.2.jar)
|
|
||||||
* [imageio-jmagick-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jmagick/3.0.2/imageio-jmagick-3.0.2.jar)
|
|
||||||
|
|
||||||
Servlet support
|
|
||||||
* [servlet-3.0.2.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar)
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project is provided under the OSI approved [BSD license](https://opensource.org/licenses/BSD-3-Clause):
|
This project is provided under the OSI approved [BSD license](https://opensource.org/licenses/BSD-3-Clause):
|
||||||
|
|
||||||
Copyright (c) 2008-2020, Harald Kuhr
|
Copyright (c) 2008-2022, Harald Kuhr
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
@@ -473,8 +459,9 @@ This project is provided under the OSI approved [BSD license](https://opensource
|
|||||||
|
|
||||||
q: How do I use it?
|
q: How do I use it?
|
||||||
|
|
||||||
a: The easiest way is to build your own project using Maven, and just add dependencies to the specific plug-ins you need.
|
a: The easiest way is to build your own project using Maven, Gradle or other build tool with dependency management,
|
||||||
If you don't use Maven, make sure you have all the necessary JARs in classpath. See the Install section above.
|
and just add dependencies to the specific plug-ins you need.
|
||||||
|
If you don't use such a build tool, make sure you have all the necessary JARs in classpath. See the Install section above.
|
||||||
|
|
||||||
|
|
||||||
q: What changes do I have to make to my code in order to use the plug-ins?
|
q: What changes do I have to make to my code in order to use the plug-ins?
|
||||||
@@ -492,27 +479,41 @@ q: How does it work?
|
|||||||
|
|
||||||
a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO. ImageIO uses a service lookup mechanism, to discover plug-ins at runtime.
|
a: The TwelveMonkeys ImageIO project contains plug-ins for ImageIO. ImageIO uses a service lookup mechanism, to discover plug-ins at runtime.
|
||||||
|
|
||||||
All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath.
|
All you have to do, is to make sure you have the TwelveMonkeys ImageIO JARs in your classpath.
|
||||||
|
|
||||||
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html).
|
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html).
|
||||||
|
|
||||||
The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and
|
The fine print: The TwelveMonkeys service providers for JPEG, BMP and TIFF, overrides the onRegistration method, and
|
||||||
utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before
|
utilizes the pairwise partial ordering mechanism of the `IIOServiceRegistry` to make sure it is installed before
|
||||||
the Sun/Oracle provided `JPEGImageReader` and `BMPImageReader`, and the Apple provided `TIFFImageReader` on OS X,
|
the Sun/Oracle provided `JPEGImageReader`, `BMPImageReader` `TIFFImageReader`, and the Apple provided `TIFFImageReader` on OS X,
|
||||||
respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most
|
respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most
|
||||||
cases you'll end up using the TwelveMonkeys plug-ins instead.
|
cases you'll end up using the TwelveMonkeys plug-ins instead.
|
||||||
|
|
||||||
|
|
||||||
q: Why is there no support for common formats like GIF or PNG?
|
q: Why is there no support for common formats like GIF or PNG?
|
||||||
|
|
||||||
a: The short answer is simply that the built-in support in ImageIO for these formats are good enough as-is.
|
a: The short answer is simply that the built-in support in ImageIO for these formats are considered good enough as-is.
|
||||||
If you are looking for better PNG write performance on Java 7 and 8, see [JDK9 PNG Writer Backport](https://github.com/gredler/jdk9-png-writer-backport).
|
If you are looking for better PNG write performance on Java 7 and 8, see [JDK9 PNG Writer Backport](https://github.com/gredler/jdk9-png-writer-backport).
|
||||||
|
|
||||||
|
|
||||||
|
q: When is the next release? What is the current release schedule?
|
||||||
|
|
||||||
|
a: The goal is to make monthly releases, containing bug fixes and minor new features.
|
||||||
|
And quarterly releases with more "major" features.
|
||||||
|
|
||||||
|
|
||||||
|
q: I love this project! How can I help?
|
||||||
|
|
||||||
|
a: Have a look at the open issues, and see if there are any issues you can help fix, or provide sample file or create test cases for.
|
||||||
|
It is also possible for you or your organization to become a sponsor, through GitHub Sponsors.
|
||||||
|
Providing funding will allow us to spend more time on fixing bugs and implementing new features.
|
||||||
|
|
||||||
|
|
||||||
q: What about JAI? Several of the formats are already supported by JAI.
|
q: What about JAI? Several of the formats are already supported by JAI.
|
||||||
|
|
||||||
a: While JAI (and jai-imageio in particular) have support for some of the same formats, JAI has some major issues.
|
a: While JAI (and jai-imageio in particular) have support for some of the same formats, JAI has some major issues.
|
||||||
The most obvious being:
|
The most obvious being:
|
||||||
- It's not actively developed. No issues has been fixed for years.
|
- It's not actively developed. No issue has been fixed for years.
|
||||||
- To get full format support, you need native libs.
|
- To get full format support, you need native libs.
|
||||||
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
|
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
|
||||||
Some environments may also prevent deployment of native libs, which brings us back to square one.
|
Some environments may also prevent deployment of native libs, which brings us back to square one.
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
To report a security issue, please disclose it at [security advisory](https://github.com/haraldk/TwelveMonkeys/security/advisories/new).
|
||||||
|
|
||||||
|
Vulnerabilities will be disclosed in a best effort base.
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<groupId>com.twelvemonkeys.bom</groupId>
|
<groupId>com.twelvemonkeys.bom</groupId>
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-image</artifactId>
|
<artifactId>common-image</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TwelveMonkeys :: Common :: Image</name>
|
<name>TwelveMonkeys :: Common :: Image</name>
|
||||||
<description>
|
<description>
|
||||||
The TwelveMonkeys Common Image support
|
TwelveMonkeys Common image support classes.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -31,9 +31,18 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jmagick</groupId>
|
<groupId>jmagick</groupId>
|
||||||
<artifactId>jmagick</artifactId>
|
<artifactId>jmagick</artifactId>
|
||||||
<version>6.2.4</version>
|
<version>6.6.9</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+96
-86
@@ -79,7 +79,7 @@ public final class BufferedImageFactory {
|
|||||||
private int scanSize;
|
private int scanSize;
|
||||||
|
|
||||||
private ColorModel sourceColorModel;
|
private ColorModel sourceColorModel;
|
||||||
private Hashtable sourceProperties; // ImageConsumer API dictates Hashtable
|
private Hashtable<?, ?> sourceProperties; // ImageConsumer API dictates Hashtable
|
||||||
|
|
||||||
private Object sourcePixels;
|
private Object sourcePixels;
|
||||||
|
|
||||||
@@ -91,21 +91,21 @@ public final class BufferedImageFactory {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code BufferedImageFactory}.
|
* Creates a {@code BufferedImageFactory}.
|
||||||
* @param pSource the source image
|
* @param source the source image
|
||||||
* @throws IllegalArgumentException if {@code pSource == null}
|
* @throws IllegalArgumentException if {@code source == null}
|
||||||
*/
|
*/
|
||||||
public BufferedImageFactory(final Image pSource) {
|
public BufferedImageFactory(final Image source) {
|
||||||
this(pSource != null ? pSource.getSource() : null);
|
this(source != null ? source.getSource() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code BufferedImageFactory}.
|
* Creates a {@code BufferedImageFactory}.
|
||||||
* @param pSource the source image producer
|
* @param source the source image producer
|
||||||
* @throws IllegalArgumentException if {@code pSource == null}
|
* @throws IllegalArgumentException if {@code source == null}
|
||||||
*/
|
*/
|
||||||
public BufferedImageFactory(final ImageProducer pSource) {
|
public BufferedImageFactory(final ImageProducer source) {
|
||||||
Validate.notNull(pSource, "source");
|
Validate.notNull(source, "source");
|
||||||
producer = pSource;
|
producer = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -155,44 +155,44 @@ public final class BufferedImageFactory {
|
|||||||
/**
|
/**
|
||||||
* Sets the source region (AOI) for the new image.
|
* Sets the source region (AOI) for the new image.
|
||||||
*
|
*
|
||||||
* @param pRegion the source region
|
* @param region the source region
|
||||||
*/
|
*/
|
||||||
public void setSourceRegion(final Rectangle pRegion) {
|
public void setSourceRegion(final Rectangle region) {
|
||||||
// Re-fetch everything, if region changed
|
// Re-fetch everything, if region changed
|
||||||
if (x != pRegion.x || y != pRegion.y || width != pRegion.width || height != pRegion.height) {
|
if (x != region.x || y != region.y || width != region.width || height != region.height) {
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
x = pRegion.x;
|
x = region.x;
|
||||||
y = pRegion.y;
|
y = region.y;
|
||||||
width = pRegion.width;
|
width = region.width;
|
||||||
height = pRegion.height;
|
height = region.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the source subsampling for the new image.
|
* Sets the source subsampling for the new image.
|
||||||
*
|
*
|
||||||
* @param pXSub horizontal subsampling factor
|
* @param xSubsampling horizontal subsampling factor
|
||||||
* @param pYSub vertical subsampling factor
|
* @param ySubsampling vertical subsampling factor
|
||||||
*/
|
*/
|
||||||
public void setSourceSubsampling(int pXSub, int pYSub) {
|
public void setSourceSubsampling(int xSubsampling, int ySubsampling) {
|
||||||
// Re-fetch everything, if subsampling changed
|
// Re-fetch everything, if subsampling changed
|
||||||
if (xSub != pXSub || ySub != pYSub) {
|
if (xSub != xSubsampling || ySub != ySubsampling) {
|
||||||
dispose();
|
dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pXSub > 1) {
|
if (xSubsampling > 1) {
|
||||||
xSub = pXSub;
|
xSub = xSubsampling;
|
||||||
}
|
}
|
||||||
if (pYSub > 1) {
|
if (ySubsampling > 1) {
|
||||||
ySub = pYSub;
|
ySub = ySubsampling;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
|
private synchronized void doFetch(final boolean colorModelOnly) throws ImageConversionException {
|
||||||
if (!fetching && (!pColorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
if (!fetching && (!colorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
||||||
// NOTE: Subsampling is only applied if extracting full image
|
// NOTE: Subsampling is only applied if extracting full image
|
||||||
if (!pColorModelOnly && (xSub > 1 || ySub > 1)) {
|
if (!colorModelOnly && (xSub > 1 || ySub > 1)) {
|
||||||
// If only sampling a region, the region must be scaled too
|
// If only sampling a region, the region must be scaled too
|
||||||
if (width > 0 && height > 0) {
|
if (width > 0 && height > 0) {
|
||||||
width = (width + xSub - 1) / xSub;
|
width = (width + xSub - 1) / xSub;
|
||||||
@@ -207,38 +207,41 @@ public final class BufferedImageFactory {
|
|||||||
|
|
||||||
// Start fetching
|
// Start fetching
|
||||||
fetching = true;
|
fetching = true;
|
||||||
readColorModelOnly = pColorModelOnly;
|
readColorModelOnly = colorModelOnly;
|
||||||
|
|
||||||
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
|
producer.startProduction(consumer); // Note: If single-thread (synchronous), this call will block
|
||||||
|
|
||||||
// Wait until the producer wakes us up, by calling imageComplete
|
// Wait until the producer wakes us up, by calling imageComplete
|
||||||
while (fetching) {
|
while (fetching) {
|
||||||
try {
|
try {
|
||||||
wait(200l);
|
wait(200L);
|
||||||
}
|
}
|
||||||
catch (InterruptedException e) {
|
catch (InterruptedException e) {
|
||||||
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
|
throw new ImageConversionException("Image conversion aborted: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (consumerException != null) {
|
try {
|
||||||
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
if (consumerException != null) {
|
||||||
}
|
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
||||||
|
}
|
||||||
|
|
||||||
if (pColorModelOnly) {
|
if (colorModelOnly) {
|
||||||
createColorModel();
|
createColorModel();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
createBuffered();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
finally {
|
||||||
createBuffered();
|
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||||
|
freeResources();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createColorModel() {
|
private void createColorModel() {
|
||||||
colorModel = sourceColorModel;
|
colorModel = sourceColorModel;
|
||||||
|
|
||||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
|
||||||
freeResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createBuffered() {
|
private void createBuffered() {
|
||||||
@@ -253,8 +256,9 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
if (buffered == null) {
|
||||||
freeResources();
|
throw new ImageConversionException("Could not create BufferedImage");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void freeResources() {
|
private void freeResources() {
|
||||||
@@ -280,27 +284,27 @@ public final class BufferedImageFactory {
|
|||||||
/**
|
/**
|
||||||
* Adds a progress listener to this factory.
|
* Adds a progress listener to this factory.
|
||||||
*
|
*
|
||||||
* @param pListener the progress listener
|
* @param listener the progress listener
|
||||||
*/
|
*/
|
||||||
public void addProgressListener(ProgressListener pListener) {
|
public void addProgressListener(ProgressListener listener) {
|
||||||
if (pListener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listeners == null) {
|
if (listeners == null) {
|
||||||
listeners = new CopyOnWriteArrayList<ProgressListener>();
|
listeners = new CopyOnWriteArrayList<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners.add(pListener);
|
listeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a progress listener from this factory.
|
* Removes a progress listener from this factory.
|
||||||
*
|
*
|
||||||
* @param pListener the progress listener
|
* @param listener the progress listener
|
||||||
*/
|
*/
|
||||||
public void removeProgressListener(ProgressListener pListener) {
|
public void removeProgressListener(ProgressListener listener) {
|
||||||
if (pListener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +312,7 @@ public final class BufferedImageFactory {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
listeners.remove(pListener);
|
listeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -324,21 +328,22 @@ public final class BufferedImageFactory {
|
|||||||
* Converts an array of {@code int} pixels to an array of {@code short}
|
* Converts an array of {@code int} pixels to an array of {@code short}
|
||||||
* pixels. The conversion is done, by masking out the
|
* pixels. The conversion is done, by masking out the
|
||||||
* <em>higher 16 bits</em> of the {@code int}.
|
* <em>higher 16 bits</em> of the {@code int}.
|
||||||
*
|
* <p>
|
||||||
* For any given {@code int}, the {@code short} value is computed as
|
* For any given {@code int}, the {@code short} value is computed as
|
||||||
* follows:
|
* follows:
|
||||||
* <blockquote>{@code
|
* <blockquote>{@code
|
||||||
* short value = (short) (intValue & 0x0000ffff);
|
* short value = (short) (intValue & 0x0000ffff);
|
||||||
* }</blockquote>
|
* }</blockquote>
|
||||||
|
* </p>
|
||||||
*
|
*
|
||||||
* @param pPixels the pixel data to convert
|
* @param inputPixels the pixel data to convert
|
||||||
* @return an array of {@code short}s, same lenght as {@code pPixels}
|
* @return an array of {@code short}s, same length as {@code inputPixels}
|
||||||
*/
|
*/
|
||||||
private static short[] toShortPixels(int[] pPixels) {
|
private static short[] toShortPixels(int[] inputPixels) {
|
||||||
short[] pixels = new short[pPixels.length];
|
short[] pixels = new short[inputPixels.length];
|
||||||
|
|
||||||
for (int i = 0; i < pixels.length; i++) {
|
for (int i = 0; i < pixels.length; i++) {
|
||||||
pixels[i] = (short) (pPixels[i] & 0xffff);
|
pixels[i] = (short) (inputPixels[i] & 0xffff);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pixels;
|
return pixels;
|
||||||
@@ -351,17 +356,17 @@ public final class BufferedImageFactory {
|
|||||||
* @see BufferedImageFactory#addProgressListener
|
* @see BufferedImageFactory#addProgressListener
|
||||||
* @see BufferedImageFactory#removeProgressListener
|
* @see BufferedImageFactory#removeProgressListener
|
||||||
*/
|
*/
|
||||||
public static interface ProgressListener extends EventListener {
|
public interface ProgressListener extends EventListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reports progress to this listener.
|
* Reports progress to this listener.
|
||||||
* Invoked by the {@code BufferedImageFactory} to report progress in
|
* Invoked by the {@code BufferedImageFactory} to report progress in
|
||||||
* the image decoding.
|
* the image decoding.
|
||||||
*
|
*
|
||||||
* @param pFactory the factory reporting the progress
|
* @param factory the factory reporting the progress
|
||||||
* @param pPercentage the percentage of progress
|
* @param percentage the percentage of progress
|
||||||
*/
|
*/
|
||||||
void progress(BufferedImageFactory pFactory, float pPercentage);
|
void progress(BufferedImageFactory factory, float percentage);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Consumer implements ImageConsumer {
|
private class Consumer implements ImageConsumer {
|
||||||
@@ -446,18 +451,18 @@ public final class BufferedImageFactory {
|
|||||||
processProgress(pY + pHeight);
|
processProgress(pY + pHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, short[] pPixels, int pOffset, int pScanSize) {
|
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, short[] pixels, int offset, int scanSize) {
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setColorModelOnce(final ColorModel pModel) {
|
private void setColorModelOnce(final ColorModel colorModel) {
|
||||||
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
|
// NOTE: There seems to be a "bug" in AreaAveragingScaleFilter, as it
|
||||||
// first passes the original color model through in setColorModel, then
|
// first passes the original color model through in setColorModel, then
|
||||||
// later replaces it with the default RGB in the first setPixels call
|
// later replaces it with the default RGB in the first setPixels call
|
||||||
// (this is probably allowed according to the spec, but it's a waste of time and space).
|
// (this is probably allowed according to the spec, but it's a waste of time and space).
|
||||||
if (sourceColorModel != pModel) {
|
if (sourceColorModel != colorModel) {
|
||||||
if (/*sourceColorModel == null ||*/ sourcePixels == null) {
|
if (sourcePixels == null) {
|
||||||
sourceColorModel = pModel;
|
sourceColorModel = colorModel;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
|
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
|
||||||
@@ -470,17 +475,16 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void imageComplete(int pStatus) {
|
@Override
|
||||||
|
public void imageComplete(int status) {
|
||||||
fetching = false;
|
fetching = false;
|
||||||
|
|
||||||
if (producer != null) {
|
if (producer != null) {
|
||||||
producer.removeConsumer(this);
|
producer.removeConsumer(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (pStatus) {
|
if (status == ImageConsumer.IMAGEERROR) {
|
||||||
case ImageConsumer.IMAGEERROR:
|
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
||||||
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (BufferedImageFactory.this) {
|
synchronized (BufferedImageFactory.this) {
|
||||||
@@ -488,16 +492,18 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setColorModel(ColorModel pModel) {
|
@Override
|
||||||
setColorModelOnce(pModel);
|
public void setColorModel(ColorModel colorModel) {
|
||||||
|
setColorModelOnce(colorModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDimensions(int pWidth, int pHeight) {
|
@Override
|
||||||
|
public void setDimensions(int w, int h) {
|
||||||
if (width < 0) {
|
if (width < 0) {
|
||||||
width = pWidth - x;
|
width = w - x;
|
||||||
}
|
}
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = pHeight - y;
|
height = h - y;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hmm.. Special case, but is it a good idea?
|
// Hmm.. Special case, but is it a good idea?
|
||||||
@@ -506,27 +512,31 @@ public final class BufferedImageFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setHints(int pHintflags) {
|
@Override
|
||||||
|
public void setHints(int hintFlags) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWidth, int pHeight, ColorModel pModel, byte[] pPixels, int pOffset, int pScanSize) {
|
@Override
|
||||||
setPixelsImpl(pX, pY, pWidth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, byte[] pixels, int offset, int scanSize) {
|
||||||
|
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPixels(int pX, int pY, int pWeigth, int pHeight, ColorModel pModel, int[] pPixels, int pOffset, int pScanSize) {
|
@Override
|
||||||
if (pModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
public void setPixels(int x, int y, int width, int height, ColorModel colorModel, int[] pixels, int offset, int scanSize) {
|
||||||
|
if (colorModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||||
// NOTE: Workaround for limitation in ImageConsumer API
|
// NOTE: Workaround for limitation in ImageConsumer API
|
||||||
// Convert int[] to short[], to be compatible with the ColorModel
|
// Convert int[] to short[], to be compatible with the ColorModel
|
||||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
|
setPixelsImpl(x, y, width, height, colorModel, toShortPixels(pixels), offset, scanSize);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProperties(Hashtable pProperties) {
|
@Override
|
||||||
sourceProperties = pProperties;
|
public void setProperties(Hashtable properties) {
|
||||||
|
sourceProperties = properties;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-14
@@ -34,14 +34,13 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.ImageProducer;
|
|
||||||
import java.awt.image.IndexColorModel;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BufferedImageFactoryTestCase
|
* BufferedImageFactoryTestCase
|
||||||
@@ -260,9 +259,9 @@ public class BufferedImageFactoryTest {
|
|||||||
|
|
||||||
// Listener should abort ASAP
|
// Listener should abort ASAP
|
||||||
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
|
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
public void progress(BufferedImageFactory factory, float percentage) {
|
||||||
if (pPercentage > 5) {
|
if (percentage > 5) {
|
||||||
pFactory.abort();
|
factory.abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -343,7 +342,7 @@ public class BufferedImageFactoryTest {
|
|||||||
VerifyingListener listener = new VerifyingListener(factory);
|
VerifyingListener listener = new VerifyingListener(factory);
|
||||||
factory.addProgressListener(listener);
|
factory.addProgressListener(listener);
|
||||||
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
|
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
public void progress(BufferedImageFactory factory, float percentage) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
factory.getBufferedImage();
|
factory.getBufferedImage();
|
||||||
@@ -380,11 +379,11 @@ public class BufferedImageFactoryTest {
|
|||||||
this.factory = factory;
|
this.factory = factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
public void progress(BufferedImageFactory factory, float percentage) {
|
||||||
assertEquals(factory, pFactory);
|
assertEquals(this.factory, factory);
|
||||||
assertTrue(pPercentage >= progress && pPercentage <= 100f);
|
assertTrue(percentage >= progress && percentage <= 100f);
|
||||||
|
|
||||||
progress = pPercentage;
|
progress = percentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-io</artifactId>
|
<artifactId>common-io</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TwelveMonkeys :: Common :: IO</name>
|
<name>TwelveMonkeys :: Common :: IO</name>
|
||||||
<description>
|
<description>
|
||||||
The TwelveMonkeys Common IO support
|
TwelveMonkeys Common I/O support classes.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
@@ -31,4 +31,12 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public class CompoundReader extends Reader {
|
|||||||
|
|
||||||
private int currentReader;
|
private int currentReader;
|
||||||
private int markedReader;
|
private int markedReader;
|
||||||
private int mark;
|
private long mark;
|
||||||
private int mNext;
|
private long next;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new compound reader.
|
* Create a new compound reader.
|
||||||
@@ -76,7 +76,7 @@ public class CompoundReader extends Reader {
|
|||||||
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
finalLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
||||||
// reference can't change, only it's elements
|
// reference can't change, only it's elements
|
||||||
|
|
||||||
readers = new ArrayList<Reader>();
|
readers = new ArrayList<>();
|
||||||
|
|
||||||
boolean markSupported = true;
|
boolean markSupported = true;
|
||||||
while (pReaders.hasNext()) {
|
while (pReaders.hasNext()) {
|
||||||
@@ -101,7 +101,7 @@ public class CompoundReader extends Reader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||||
mNext = 0;
|
next = 0;
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ public class CompoundReader extends Reader {
|
|||||||
|
|
||||||
synchronized (finalLock) {
|
synchronized (finalLock) {
|
||||||
ensureOpen();
|
ensureOpen();
|
||||||
mark = mNext;
|
mark = next;
|
||||||
markedReader = currentReader;
|
markedReader = currentReader;
|
||||||
|
|
||||||
current.mark(pReadLimit);
|
current.mark(pReadLimit);
|
||||||
@@ -158,7 +158,7 @@ public class CompoundReader extends Reader {
|
|||||||
}
|
}
|
||||||
current.reset();
|
current.reset();
|
||||||
|
|
||||||
mNext = mark;
|
next = mark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,13 +177,13 @@ public class CompoundReader extends Reader {
|
|||||||
return read(); // In case of 0-length readers
|
return read(); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext++;
|
next++;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
public int read(char[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||||
synchronized (finalLock) {
|
synchronized (finalLock) {
|
||||||
int read = current.read(pBuffer, pOffset, pLength);
|
int read = current.read(pBuffer, pOffset, pLength);
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ public class CompoundReader extends Reader {
|
|||||||
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext += read;
|
next += read;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ public class CompoundReader extends Reader {
|
|||||||
return skip(pChars); // In case of 0-length readers
|
return skip(pChars); // In case of 0-length readers
|
||||||
}
|
}
|
||||||
|
|
||||||
mNext += skipped;
|
next += skipped;
|
||||||
|
|
||||||
return skipped;
|
return skipped;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import java.io.ByteArrayInputStream;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
|
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
|
||||||
@@ -42,11 +43,8 @@ import java.io.OutputStream;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: FastByteArrayOutputStream.java#2 $
|
* @version $Id: FastByteArrayOutputStream.java#2 $
|
||||||
*/
|
*/
|
||||||
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
||||||
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||||
/** Max grow size (unless if writing more than this amount of bytes) */
|
|
||||||
protected int maxGrowSize = 1024 * 1024; // 1 MB
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
||||||
* size.
|
* size.
|
||||||
@@ -97,10 +95,8 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
|
|
||||||
private void growIfNeeded(int pNewCount) {
|
private void growIfNeeded(int pNewCount) {
|
||||||
if (pNewCount > buf.length) {
|
if (pNewCount > buf.length) {
|
||||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
|
int newSize = Math.max(buf.length << 1, pNewCount);
|
||||||
byte[] newBuf = new byte[newSize];
|
buf = Arrays.copyOf(buf, newSize);
|
||||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
|
||||||
buf = newBuf;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,10 +109,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
|||||||
// Non-synchronized version of toByteArray
|
// Non-synchronized version of toByteArray
|
||||||
@Override
|
@Override
|
||||||
public byte[] toByteArray() {
|
public byte[] toByteArray() {
|
||||||
byte[] newBuf = new byte[count];
|
return Arrays.copyOf(buf, count);
|
||||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
|
||||||
|
|
||||||
return newBuf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ public class StringArrayReader extends StringReader {
|
|||||||
protected final Object finalLock;
|
protected final Object finalLock;
|
||||||
private int currentSting;
|
private int currentSting;
|
||||||
private int markedString;
|
private int markedString;
|
||||||
private int mark;
|
private long mark;
|
||||||
private int next;
|
private long next;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new string array reader.
|
* Create a new string array reader.
|
||||||
@@ -151,7 +151,7 @@ public class StringArrayReader extends StringReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
public int read(char[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||||
synchronized (finalLock) {
|
synchronized (finalLock) {
|
||||||
int read = current.read(pBuffer, pOffset, pLength);
|
int read = current.read(pBuffer, pOffset, pLength);
|
||||||
|
|
||||||
|
|||||||
@@ -41,21 +41,20 @@ import java.io.InputStream;
|
|||||||
* underlying stream.
|
* underlying stream.
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $
|
|
||||||
*/
|
*/
|
||||||
public final class SubStream extends FilterInputStream {
|
public final class SubStream extends FilterInputStream {
|
||||||
private long bytesLeft;
|
private long bytesLeft;
|
||||||
private int markLimit;
|
private int markLimit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code SubStream} of the given {@code pStream}.
|
* Creates a {@code SubStream} of the given {@code stream}.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream
|
* @param stream the underlying input stream
|
||||||
* @param pLength maximum number of bytes to read drom this stream
|
* @param length maximum number of bytes to read from this stream
|
||||||
*/
|
*/
|
||||||
public SubStream(final InputStream pStream, final long pLength) {
|
public SubStream(final InputStream stream, final long length) {
|
||||||
super(Validate.notNull(pStream, "stream"));
|
super(Validate.notNull(stream, "stream"));
|
||||||
bytesLeft = pLength;
|
bytesLeft = Validate.isTrue(length >= 0, length, "length < 0: %s");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,22 +63,23 @@ public final class SubStream extends FilterInputStream {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
// NOTE: Do not close the underlying stream
|
// NOTE: Do not close the underlying stream, but consume it
|
||||||
while (bytesLeft > 0) {
|
while (bytesLeft > 0) {
|
||||||
//noinspection ResultOfMethodCallIgnored
|
if (skip(bytesLeft) <= 0 && read() < 0) {
|
||||||
skip(bytesLeft);
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() throws IOException {
|
public int available() throws IOException {
|
||||||
return (int) Math.min(super.available(), bytesLeft);
|
return (int) findMaxLen(super.available());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void mark(int pReadLimit) {
|
public void mark(int readLimit) {
|
||||||
super.mark(pReadLimit);// This either succeeds or does nothing...
|
super.mark(readLimit);// This either succeeds or does nothing...
|
||||||
markLimit = pReadLimit;
|
markLimit = readLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -93,44 +93,42 @@ public final class SubStream extends FilterInputStream {
|
|||||||
if (bytesLeft-- <= 0) {
|
if (bytesLeft-- <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.read();
|
return super.read();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int read(byte[] pBytes) throws IOException {
|
public int read(byte[] bytes) throws IOException {
|
||||||
return read(pBytes, 0, pBytes.length);
|
return read(bytes, 0, bytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] bytes, final int off, final int len) throws IOException {
|
||||||
if (bytesLeft <= 0) {
|
if (bytesLeft <= 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
int read = super.read(bytes, off, (int) findMaxLen(len));
|
||||||
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
||||||
|
|
||||||
return read;
|
return read;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long skip(long length) throws IOException {
|
||||||
|
long skipped = super.skip(findMaxLen(length)); // Skips 0 or more, never -1
|
||||||
|
bytesLeft -= skipped;
|
||||||
|
|
||||||
|
return skipped;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the maximum number of bytes we can read or skip, from this stream.
|
* Finds the maximum number of bytes we can read or skip, from this stream.
|
||||||
*
|
*
|
||||||
* @param pLength the requested length
|
* @param length the requested length
|
||||||
* @return the maximum number of bytes to read
|
* @return the maximum number of bytes to read
|
||||||
*/
|
*/
|
||||||
private long findMaxLen(long pLength) {
|
private long findMaxLen(long length) {
|
||||||
if (bytesLeft < pLength) {
|
return bytesLeft < length ? Math.max(bytesLeft, 0) : length;
|
||||||
return (int) Math.max(bytesLeft, 0);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return pLength;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long skip(long pLength) throws IOException {
|
|
||||||
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
|
|
||||||
bytesLeft -= skipped;
|
|
||||||
return skipped;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,39 +45,39 @@ import java.nio.ByteBuffer;
|
|||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
||||||
*/
|
*/
|
||||||
public final class DecoderStream extends FilterInputStream {
|
public final class DecoderStream extends FilterInputStream {
|
||||||
protected final ByteBuffer buffer;
|
private final ByteBuffer buffer;
|
||||||
protected final Decoder decoder;
|
private final Decoder decoder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new decoder stream and chains it to the
|
* Creates a new decoder stream and chains it to the
|
||||||
* input stream specified by the {@code pStream} argument.
|
* input stream specified by the {@code stream} argument.
|
||||||
* The stream will use a default decode buffer size.
|
* The stream will use a default decode buffer size.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream.
|
* @param stream the underlying input stream.
|
||||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
* @param decoder the decoder that will be used to decode the underlying stream
|
||||||
*
|
*
|
||||||
* @see java.io.FilterInputStream#in
|
* @see java.io.FilterInputStream#in
|
||||||
*/
|
*/
|
||||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
public DecoderStream(final InputStream stream, final Decoder decoder) {
|
||||||
// TODO: Let the decoder decide preferred buffer size
|
// TODO: Let the decoder decide preferred buffer size
|
||||||
this(pStream, pDecoder, 1024);
|
this(stream, decoder, 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new decoder stream and chains it to the
|
* Creates a new decoder stream and chains it to the
|
||||||
* input stream specified by the {@code pStream} argument.
|
* input stream specified by the {@code stream} argument.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying input stream.
|
* @param stream the underlying input stream.
|
||||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
* @param decoder the decoder that will be used to decode the underlying stream
|
||||||
* @param pBufferSize the size of the decode buffer
|
* @param bufferSize the size of the decode buffer
|
||||||
*
|
*
|
||||||
* @see java.io.FilterInputStream#in
|
* @see java.io.FilterInputStream#in
|
||||||
*/
|
*/
|
||||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) {
|
||||||
super(pStream);
|
super(stream);
|
||||||
|
|
||||||
decoder = pDecoder;
|
this.decoder = decoder;
|
||||||
buffer = ByteBuffer.allocate(pBufferSize);
|
buffer = ByteBuffer.allocate(bufferSize);
|
||||||
buffer.flip();
|
buffer.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
return buffer.get() & 0xff;
|
return buffer.get() & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
if (pBytes == null) {
|
if (bytes == null) {
|
||||||
throw new NullPointerException();
|
throw new NullPointerException();
|
||||||
}
|
}
|
||||||
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
else if ((offset < 0) || (offset > bytes.length) || (length < 0) ||
|
||||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
((offset + length) > bytes.length) || ((offset + length) < 0)) {
|
||||||
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength);
|
throw new IndexOutOfBoundsException("bytes.length=" + bytes.length + " offset=" + offset + " length=" + length);
|
||||||
}
|
}
|
||||||
else if (pLength == 0) {
|
else if (length == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,11 +114,11 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read until we have read pLength bytes, or have reached EOF
|
// Read until we have read length bytes, or have reached EOF
|
||||||
int count = 0;
|
int count = 0;
|
||||||
int off = pOffset;
|
int off = offset;
|
||||||
|
|
||||||
while (pLength > count) {
|
while (length > count) {
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
if (fill() < 0) {
|
if (fill() < 0) {
|
||||||
break;
|
break;
|
||||||
@@ -126,8 +126,8 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Copy as many bytes as possible
|
// Copy as many bytes as possible
|
||||||
int dstLen = Math.min(pLength - count, buffer.remaining());
|
int dstLen = Math.min(length - count, buffer.remaining());
|
||||||
buffer.get(pBytes, off, dstLen);
|
buffer.get(bytes, off, dstLen);
|
||||||
|
|
||||||
// Update offset (rest)
|
// Update offset (rest)
|
||||||
off += dstLen;
|
off += dstLen;
|
||||||
@@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long skip(final long pLength) throws IOException {
|
public long skip(final long length) throws IOException {
|
||||||
// End of file?
|
// End of file?
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
if (fill() < 0) {
|
if (fill() < 0) {
|
||||||
@@ -147,10 +147,10 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
// Skip until we have skipped length bytes, or have reached EOF
|
||||||
long total = 0;
|
long total = 0;
|
||||||
|
|
||||||
while (total < pLength) {
|
while (total < length) {
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
if (fill() < 0) {
|
if (fill() < 0) {
|
||||||
break;
|
break;
|
||||||
@@ -158,7 +158,7 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
|
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
|
||||||
int skipped = (int) Math.min(pLength - total, buffer.remaining());
|
int skipped = (int) Math.min(length - total, buffer.remaining());
|
||||||
buffer.position(buffer.position() + skipped);
|
buffer.position(buffer.position() + skipped);
|
||||||
total += skipped;
|
total += skipped;
|
||||||
}
|
}
|
||||||
@@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream {
|
|||||||
*
|
*
|
||||||
* @throws IOException if an I/O error occurs
|
* @throws IOException if an I/O error occurs
|
||||||
*/
|
*/
|
||||||
protected int fill() throws IOException {
|
private int fill() throws IOException {
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
int read = decoder.decode(in, buffer);
|
int read = decoder.decode(in, buffer);
|
||||||
|
|
||||||
|
|||||||
@@ -45,41 +45,39 @@ import java.nio.ByteBuffer;
|
|||||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
||||||
*/
|
*/
|
||||||
public final class EncoderStream extends FilterOutputStream {
|
public final class EncoderStream extends FilterOutputStream {
|
||||||
// TODO: This class need a test case ASAP!!!
|
|
||||||
|
|
||||||
protected final Encoder encoder;
|
private final Encoder encoder;
|
||||||
private final boolean flushOnWrite;
|
private final boolean flushOnWrite;
|
||||||
|
|
||||||
protected final ByteBuffer buffer;
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an output stream filter built on top of the specified
|
* Creates an output stream filter built on top of the specified
|
||||||
* underlying output stream.
|
* underlying output stream.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying output stream
|
* @param stream the underlying output stream
|
||||||
* @param pEncoder the encoder to use
|
* @param encoder the encoder to use
|
||||||
*/
|
*/
|
||||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
|
public EncoderStream(final OutputStream stream, final Encoder encoder) {
|
||||||
this(pStream, pEncoder, false);
|
this(stream, encoder, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an output stream filter built on top of the specified
|
* Creates an output stream filter built on top of the specified
|
||||||
* underlying output stream.
|
* underlying output stream.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying output stream
|
* @param stream the underlying output stream
|
||||||
* @param pEncoder the encoder to use
|
* @param encoder the encoder to use
|
||||||
* @param pFlushOnWrite if {@code true}, calls to the byte-array
|
* @param flushOnWrite if {@code true}, calls to the byte-array
|
||||||
* {@code write} methods will automatically flush the buffer.
|
* {@code write} methods will automatically flush the buffer.
|
||||||
*/
|
*/
|
||||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) {
|
||||||
super(pStream);
|
super(stream);
|
||||||
|
|
||||||
encoder = pEncoder;
|
this.encoder = encoder;
|
||||||
flushOnWrite = pFlushOnWrite;
|
this.flushOnWrite = flushOnWrite;
|
||||||
|
|
||||||
buffer = ByteBuffer.allocate(1024);
|
buffer = ByteBuffer.allocate(1024);
|
||||||
buffer.flip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
@@ -104,33 +102,33 @@ public final class EncoderStream extends FilterOutputStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void write(final byte[] pBytes) throws IOException {
|
public void write(final byte[] bytes) throws IOException {
|
||||||
write(pBytes, 0, pBytes.length);
|
write(bytes, 0, bytes.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Verify that this works for the general case (it probably won't)...
|
// TODO: Verify that this works for the general case (it probably won't)...
|
||||||
// TODO: We might need a way to explicitly flush the encoder, or specify
|
// TODO: We might need a way to explicitly flush the encoder, or specify
|
||||||
// that the encoder can't buffer. In that case, the encoder should probably
|
// that the encoder can't buffer. In that case, the encoder should probably
|
||||||
// tell the EncoderStream how large buffer it prefers...
|
// tell the EncoderStream how large buffer it prefers...
|
||||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public void write(final byte[] values, final int offset, final int length) throws IOException {
|
||||||
if (!flushOnWrite && pLength < buffer.remaining()) {
|
if (!flushOnWrite && length < buffer.remaining()) {
|
||||||
// Buffer data
|
// Buffer data
|
||||||
buffer.put(pBytes, pOffset, pLength);
|
buffer.put(values, offset, length);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Encode data already in the buffer
|
// Encode data already in the buffer
|
||||||
encodeBuffer();
|
encodeBuffer();
|
||||||
|
|
||||||
// Encode rest without buffering
|
// Encode rest without buffering
|
||||||
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
|
encoder.encode(out, ByteBuffer.wrap(values, offset, length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(final int pByte) throws IOException {
|
public void write(final int value) throws IOException {
|
||||||
if (!buffer.hasRemaining()) {
|
if (!buffer.hasRemaining()) {
|
||||||
encodeBuffer(); // Resets bufferPos to 0
|
encodeBuffer(); // Resets bufferPos to 0
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.put((byte) pByte);
|
buffer.put((byte) value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
package com.twelvemonkeys.io;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SubStreamTest.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: SubStreamTest.java,v 1.0 07/11/2023 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class SubStreamTest {
|
||||||
|
|
||||||
|
private final Random rng = new Random(2918475687L);
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateNullStream() {
|
||||||
|
new SubStream(null, 42);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testCreateNegativeLength() {
|
||||||
|
new SubStream(new ByteArrayInputStream(new byte[1]), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAll() throws IOException {
|
||||||
|
byte[] buf = new byte[128];
|
||||||
|
rng.nextBytes(buf);
|
||||||
|
|
||||||
|
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
|
||||||
|
for (byte b : buf) {
|
||||||
|
assertEquals(b, (byte) stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadAllArray() throws IOException {
|
||||||
|
byte[] buf = new byte[128];
|
||||||
|
rng.nextBytes(buf);
|
||||||
|
|
||||||
|
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
|
||||||
|
byte[] temp = new byte[buf.length / 4];
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
assertEquals(temp.length, stream.read(temp)); // Depends on ByteArrayInputStream specifics...
|
||||||
|
assertArrayEquals(Arrays.copyOfRange(buf, i * temp.length, (i + 1) * temp.length), temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSkipAll() throws IOException {
|
||||||
|
byte[] buf = new byte[128];
|
||||||
|
|
||||||
|
try (InputStream stream = new SubStream(new ByteArrayInputStream(buf), buf.length)) {
|
||||||
|
assertEquals(128, stream.skip(buf.length)); // Depends on ByteArrayInputStream specifics...
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
|
@Test
|
||||||
|
public void testCloseConsumesAll() throws IOException {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[128]);
|
||||||
|
|
||||||
|
try (InputStream ignore = new SubStream(stream, 128)) {
|
||||||
|
// Nothing here...
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, stream.available());
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
|
@Test
|
||||||
|
public void testCloseConsumesAllLongStream() throws IOException {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[256]);
|
||||||
|
|
||||||
|
try (InputStream ignore = new SubStream(stream, 128)) {
|
||||||
|
// Nothing here...
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(128, stream.available());
|
||||||
|
assertEquals(0, stream.read());
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EmptyTryBlock")
|
||||||
|
@Test(timeout = 500L)
|
||||||
|
public void testCloseConsumesAllShortStream() throws IOException {
|
||||||
|
ByteArrayInputStream stream = new ByteArrayInputStream(new byte[13]);
|
||||||
|
|
||||||
|
try (InputStream ignore = new SubStream(stream, 42)) {
|
||||||
|
// Nothing here...
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(0, stream.available());
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class DecoderStreamTest {
|
||||||
|
|
||||||
|
private final Random rng = new Random(5467809876546L);
|
||||||
|
|
||||||
|
private byte[] createData(final int length) {
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
rng.nextBytes(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeSingleBytes() throws IOException {
|
||||||
|
byte[] data = createData(1327);
|
||||||
|
|
||||||
|
InputStream source = new ByteArrayInputStream(data);
|
||||||
|
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||||
|
for (byte datum : data) {
|
||||||
|
int read = stream.read();
|
||||||
|
assertNotEquals(-1, read);
|
||||||
|
assertEquals(datum, (byte) read);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeArray() throws IOException {
|
||||||
|
int length = 793;
|
||||||
|
byte[] data = createData(length * 10);
|
||||||
|
|
||||||
|
InputStream source = new ByteArrayInputStream(data);
|
||||||
|
byte[] result = new byte[477];
|
||||||
|
|
||||||
|
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||||
|
int dataOffset = 0;
|
||||||
|
while (dataOffset < data.length) {
|
||||||
|
int count = stream.read(result);
|
||||||
|
|
||||||
|
assertFalse(count <= 0);
|
||||||
|
assertArrayEquals(Arrays.copyOfRange(data, dataOffset, dataOffset + count), Arrays.copyOfRange(result, 0, count));
|
||||||
|
|
||||||
|
dataOffset += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDecodeArrayOffset() throws IOException {
|
||||||
|
int length = 793;
|
||||||
|
byte[] data = createData(length * 10);
|
||||||
|
|
||||||
|
InputStream source = new ByteArrayInputStream(data);
|
||||||
|
byte[] result = new byte[477];
|
||||||
|
|
||||||
|
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||||
|
int dataOffset = 0;
|
||||||
|
while (dataOffset < data.length) {
|
||||||
|
int resultOffset = dataOffset % result.length;
|
||||||
|
int count = stream.read(result, resultOffset, result.length - resultOffset);
|
||||||
|
|
||||||
|
assertFalse(count <= 0);
|
||||||
|
assertArrayEquals(Arrays.copyOfRange(data, dataOffset + resultOffset, dataOffset + count), Arrays.copyOfRange(result, resultOffset, count));
|
||||||
|
|
||||||
|
dataOffset += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class NullDecoder implements Decoder {
|
||||||
|
@Override
|
||||||
|
public int decode(InputStream stream, ByteBuffer buffer) throws IOException {
|
||||||
|
int read = stream.read(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||||
|
|
||||||
|
if (read > 0) {
|
||||||
|
// Set position, should be equivalent to using buffer.put(stream.read()) until EOF or buffer full
|
||||||
|
buffer.position(read);
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.io.enc;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
|
||||||
|
public class EncoderStreamTest {
|
||||||
|
|
||||||
|
private final Random rng = new Random(5467809876546L);
|
||||||
|
|
||||||
|
private byte[] createData(final int length) {
|
||||||
|
byte[] data = new byte[length];
|
||||||
|
rng.nextBytes(data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeSingleBytes() throws IOException {
|
||||||
|
byte[] data = createData(1327);
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||||
|
for (byte datum : data) {
|
||||||
|
stream.write(datum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assertArrayEquals(data, result.toByteArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeArray() throws IOException {
|
||||||
|
byte[] data = createData(1793);
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
stream.write(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] encoded = result.toByteArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertArrayEquals(data, Arrays.copyOfRange(encoded, i * data.length, (i + 1) * data.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEncodeArrayOffset() throws IOException {
|
||||||
|
byte[] data = createData(87);
|
||||||
|
|
||||||
|
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||||
|
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
stream.write(data, 13, 59);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] original = Arrays.copyOfRange(data, 13, 13 + 59);
|
||||||
|
byte[] encoded = result.toByteArray();
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
assertArrayEquals(original, Arrays.copyOfRange(encoded, i * original.length, (i + 1) * original.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class NullEncoder implements Encoder {
|
||||||
|
@Override
|
||||||
|
public void encode(OutputStream stream, ByteBuffer buffer) throws IOException {
|
||||||
|
stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,17 +4,25 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>common-lang</artifactId>
|
<artifactId>common-lang</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<name>TwelveMonkeys :: Common :: Language support</name>
|
<name>TwelveMonkeys :: Common :: Language support</name>
|
||||||
<description>
|
<description>
|
||||||
The TwelveMonkeys Common Language support
|
TwelveMonkeys Common language support classes.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<properties>
|
<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());
|
throw new IllegalArgumentException(pe.getMessage() + " at pos " + pe.getErrorOffset());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -593,8 +593,8 @@ public class StringUtilTest {
|
|||||||
cal.clear();
|
cal.clear();
|
||||||
cal.set(Calendar.HOUR, 1);
|
cal.set(Calendar.HOUR, 1);
|
||||||
cal.set(Calendar.MINUTE, 2);
|
cal.set(Calendar.MINUTE, 2);
|
||||||
date = StringUtil.toDate("1:02 am",
|
format = new SimpleDateFormat("HH:mm");
|
||||||
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US));
|
date = StringUtil.toDate("1:02", format);
|
||||||
assertNotNull(date);
|
assertNotNull(date);
|
||||||
assertEquals(cal.getTime(), date);
|
assertEquals(cal.getTime(), date);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.twelvemonkeys.common</groupId>
|
<groupId>com.twelvemonkeys.common</groupId>
|
||||||
<artifactId>common</artifactId>
|
<artifactId>common</artifactId>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.13.1</version>
|
<version>4.13.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|||||||
+12
-3
@@ -4,13 +4,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys</groupId>
|
<groupId>com.twelvemonkeys</groupId>
|
||||||
<artifactId>twelvemonkeys</artifactId>
|
<artifactId>twelvemonkeys</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||||
<artifactId>contrib</artifactId>
|
<artifactId>contrib</artifactId>
|
||||||
<name>TwelveMonkeys :: Contrib</name>
|
<name>TwelveMonkeys :: Contrib</name>
|
||||||
<description>
|
<description>
|
||||||
Contributions to TwelveMonkeys which are not matching into the ImageIO plug-ins.
|
Contributions to TwelveMonkeys and code that doesn't fit anywhere else.
|
||||||
</description>
|
</description>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@@ -65,8 +65,17 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
<version>4.13.1</version>
|
<version>4.13.2</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@@ -30,9 +30,14 @@ import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation;
|
|||||||
public class EXIFUtilities {
|
public class EXIFUtilities {
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input a {@code URL}
|
* @param input a {@code URL}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
||||||
@@ -43,9 +48,14 @@ public class EXIFUtilities {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input an {@code InputStream}
|
* @param input an {@code InputStream}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
||||||
@@ -56,9 +66,14 @@ public class EXIFUtilities {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input a {@code File}
|
* @param input a {@code File}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final File input) throws IOException {
|
public static IIOImage readWithOrientation(final File input) throws IOException {
|
||||||
@@ -69,9 +84,14 @@ public class EXIFUtilities {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||||
|
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||||
|
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||||
|
*
|
||||||
|
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||||
*
|
*
|
||||||
* @param input an {@code ImageInputStream}
|
* @param input an {@code ImageInputStream}
|
||||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||||
|
* {@code null}.
|
||||||
* @throws IOException if an error occurs during reading.
|
* @throws IOException if an error occurs during reading.
|
||||||
*/
|
*/
|
||||||
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-batik</artifactId>
|
<artifactId>imageio-batik</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||||
@@ -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.14</batik.version>
|
<batik.version>1.17</batik.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -27,12 +27,24 @@
|
|||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<configuration>
|
<configuration>
|
||||||
<systemPropertyVariables>
|
<systemPropertyVariables>
|
||||||
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
<com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||||
true
|
true
|
||||||
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
</com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||||
</systemPropertyVariables>
|
</systemPropertyVariables>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Provide-Capability>
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi
|
||||||
|
</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
@@ -48,6 +60,13 @@
|
|||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.15.0</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.xmlgraphics</groupId>
|
<groupId>org.apache.xmlgraphics</groupId>
|
||||||
<artifactId>batik-rasterizer-ext</artifactId>
|
<artifactId>batik-rasterizer-ext</artifactId>
|
||||||
|
|||||||
+140
-132
@@ -30,21 +30,11 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.svg;
|
package com.twelvemonkeys.imageio.plugins.svg;
|
||||||
|
|
||||||
import java.awt.*;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import java.awt.geom.AffineTransform;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import java.awt.geom.Rectangle2D;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import java.awt.image.BufferedImage;
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import java.io.IOException;
|
import com.twelvemonkeys.lang.StringUtil;
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.ImageReadParam;
|
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
|
||||||
|
|
||||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||||
@@ -68,10 +58,19 @@ import org.w3c.dom.DOMImplementation;
|
|||||||
import org.w3c.dom.Document;
|
import org.w3c.dom.Document;
|
||||||
import org.w3c.dom.svg.SVGSVGElement;
|
import org.w3c.dom.svg.SVGSVGElement;
|
||||||
|
|
||||||
import com.twelvemonkeys.image.ImageUtil;
|
import javax.imageio.IIOException;
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import javax.imageio.ImageReadParam;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Image reader for SVG document fragments.
|
* Image reader for SVG document fragments.
|
||||||
@@ -79,12 +78,13 @@ import com.twelvemonkeys.lang.StringUtil;
|
|||||||
* @author Harald Kuhr
|
* @author Harald Kuhr
|
||||||
* @author Inpspired by code from the Batik Team
|
* @author Inpspired by code from the Batik Team
|
||||||
* @version $Id: $
|
* @version $Id: $
|
||||||
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
|
* @see <a href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</a>
|
||||||
*/
|
*/
|
||||||
public class SVGImageReader extends ImageReaderBase {
|
public class SVGImageReader extends ImageReaderBase {
|
||||||
|
|
||||||
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
|
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
|
||||||
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources"));
|
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowExternalResources",
|
||||||
|
System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources")));
|
||||||
|
|
||||||
private Rasterizer rasterizer;
|
private Rasterizer rasterizer;
|
||||||
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
|
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
|
||||||
@@ -92,10 +92,10 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
/**
|
/**
|
||||||
* Creates an {@code SVGImageReader}.
|
* Creates an {@code SVGImageReader}.
|
||||||
*
|
*
|
||||||
* @param pProvider the provider
|
* @param provider the provider
|
||||||
*/
|
*/
|
||||||
public SVGImageReader(final ImageReaderSpi pProvider) {
|
public SVGImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
@@ -109,20 +109,20 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setInput(Object pInput, boolean seekForwardOnly, boolean ignoreMetadata) {
|
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
|
||||||
super.setInput(pInput, seekForwardOnly, ignoreMetadata);
|
super.setInput(input, seekForwardOnly, ignoreMetadata);
|
||||||
|
|
||||||
if (imageInput != null) {
|
if (imageInput != null) {
|
||||||
TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
TranscoderInput transcoderInput = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||||
rasterizer.setInput(input);
|
rasterizer.setInput(transcoderInput);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
public BufferedImage read(int imageIndex, ImageReadParam param) throws IOException {
|
||||||
checkBounds(pIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
if (pParam instanceof SVGReadParam) {
|
if (param instanceof SVGReadParam) {
|
||||||
SVGReadParam svgParam = (SVGReadParam) pParam;
|
SVGReadParam svgParam = (SVGReadParam) param;
|
||||||
|
|
||||||
// set the external-resource-resolution preference
|
// set the external-resource-resolution preference
|
||||||
allowExternalResources = svgParam.isAllowExternalResources();
|
allowExternalResources = svgParam.isAllowExternalResources();
|
||||||
@@ -140,39 +140,33 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dimension size = null;
|
Dimension size = null;
|
||||||
if (pParam != null) {
|
if (param != null) {
|
||||||
size = pParam.getSourceRenderSize();
|
size = param.getSourceRenderSize();
|
||||||
}
|
}
|
||||||
if (size == null) {
|
if (size == null) {
|
||||||
size = new Dimension(getWidth(pIndex), getHeight(pIndex));
|
size = new Dimension(getWidth(imageIndex), getHeight(imageIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height);
|
BufferedImage destination = getDestination(param, getImageTypes(imageIndex), size.width, size.height);
|
||||||
|
|
||||||
// Read in the image, using the Batik Transcoder
|
// Read in the image, using the Batik Transcoder
|
||||||
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
|
BufferedImage image = rasterizer.getImage();
|
||||||
|
|
||||||
|
Graphics2D g = destination.createGraphics();
|
||||||
try {
|
try {
|
||||||
processImageStarted(pIndex);
|
g.setComposite(AlphaComposite.Src);
|
||||||
|
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||||
BufferedImage image = rasterizer.getImage();
|
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
||||||
|
|
||||||
Graphics2D g = destination.createGraphics();
|
|
||||||
try {
|
|
||||||
g.setComposite(AlphaComposite.Src);
|
|
||||||
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
|
||||||
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
g.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
processImageComplete();
|
|
||||||
|
|
||||||
return destination;
|
|
||||||
}
|
}
|
||||||
catch (TranscoderException e) {
|
finally {
|
||||||
Throwable cause = unwrapException(e);
|
g.dispose();
|
||||||
throw new IIOException(cause.getMessage(), cause);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processImageComplete();
|
||||||
|
|
||||||
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Throwable unwrapException(TranscoderException ex) {
|
private static Throwable unwrapException(TranscoderException ex) {
|
||||||
@@ -180,18 +174,18 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return ex.getException() != null ? ex.getException() : ex;
|
return ex.getException() != null ? ex.getException() : ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TranscodingHints paramsToHints(SVGReadParam pParam) throws IOException {
|
private TranscodingHints paramsToHints(SVGReadParam param) throws IOException {
|
||||||
TranscodingHints hints = new TranscodingHints();
|
TranscodingHints hints = new TranscodingHints();
|
||||||
// Note: We must allow generic ImageReadParams, so converting to
|
// Note: We must allow generic ImageReadParams, so converting to
|
||||||
// TanscodingHints should be done outside the SVGReadParam class.
|
// TanscodingHints should be done outside the SVGReadParam class.
|
||||||
|
|
||||||
// Set dimensions
|
// Set dimensions
|
||||||
Dimension size = pParam.getSourceRenderSize();
|
Dimension size = param.getSourceRenderSize();
|
||||||
Dimension origSize = new Dimension(getWidth(0), getHeight(0));
|
Rectangle viewBox = rasterizer.getViewBox();
|
||||||
if (size == null) {
|
if (size == null) {
|
||||||
// SVG is not a pixel based format, but we'll scale it, according to
|
// SVG is not a pixel based format, but we'll scale it, according to
|
||||||
// the subsampling for compatibility
|
// the subsampling for compatibility
|
||||||
size = getSourceRenderSizeFromSubsamping(pParam, origSize);
|
size = getSourceRenderSizeFromSubsamping(param, viewBox.getSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (size != null) {
|
if (size != null) {
|
||||||
@@ -200,7 +194,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set area of interest
|
// Set area of interest
|
||||||
Rectangle region = pParam.getSourceRegion();
|
Rectangle region = param.getSourceRegion();
|
||||||
if (region != null) {
|
if (region != null) {
|
||||||
hints.put(ImageTranscoder.KEY_AOI, region);
|
hints.put(ImageTranscoder.KEY_AOI, region);
|
||||||
|
|
||||||
@@ -211,8 +205,8 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Need to resize here...
|
// Need to resize here...
|
||||||
double xScale = size.getWidth() / origSize.getWidth();
|
double xScale = size.getWidth() / viewBox.getWidth();
|
||||||
double yScale = size.getHeight() / origSize.getHeight();
|
double yScale = size.getHeight() / viewBox.getHeight();
|
||||||
|
|
||||||
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
|
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
|
||||||
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
|
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
|
||||||
@@ -220,11 +214,11 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
else if (size != null) {
|
else if (size != null) {
|
||||||
// Allow non-uniform scaling
|
// Allow non-uniform scaling
|
||||||
hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize));
|
hints.put(ImageTranscoder.KEY_AOI, viewBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background color
|
// Background color
|
||||||
Paint bg = pParam.getBackgroundColor();
|
Paint bg = param.getBackgroundColor();
|
||||||
if (bg != null) {
|
if (bg != null) {
|
||||||
hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg);
|
hints.put(ImageTranscoder.KEY_BACKGROUND_COLOR, bg);
|
||||||
}
|
}
|
||||||
@@ -232,10 +226,10 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return hints;
|
return hints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) {
|
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam param, Dimension origSize) {
|
||||||
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
|
if (param.getSourceXSubsampling() > 1 || param.getSourceYSubsampling() > 1) {
|
||||||
return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()),
|
return new Dimension((int) (origSize.width / (float) param.getSourceXSubsampling()),
|
||||||
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
|
(int) (origSize.height / (float) param.getSourceYSubsampling()));
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -244,28 +238,19 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return new SVGReadParam();
|
return new SVGReadParam();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth(int pIndex) throws IOException {
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
checkBounds(pIndex);
|
checkBounds(imageIndex);
|
||||||
try {
|
|
||||||
return rasterizer.getDefaultWidth();
|
return rasterizer.getDefaultWidth();
|
||||||
}
|
|
||||||
catch (TranscoderException e) {
|
|
||||||
throw new IIOException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(int pIndex) throws IOException {
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
checkBounds(pIndex);
|
checkBounds(imageIndex);
|
||||||
try {
|
return rasterizer.getDefaultHeight();
|
||||||
return rasterizer.getDefaultHeight();
|
|
||||||
}
|
|
||||||
catch (TranscoderException e) {
|
|
||||||
throw new IIOException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
|
||||||
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
return Collections.singleton(ImageTypeSpecifiers.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -275,12 +260,11 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
* and needs major refactoring!
|
* and needs major refactoring!
|
||||||
* </p>
|
* </p>
|
||||||
*/
|
*/
|
||||||
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
|
private class Rasterizer extends SVGAbstractTranscoder {
|
||||||
|
|
||||||
private BufferedImage image;
|
private BufferedImage image;
|
||||||
private TranscoderInput transcoderInput;
|
private TranscoderInput transcoderInput;
|
||||||
private float defaultWidth;
|
private final Rectangle2D viewBox = new Rectangle2D.Float();
|
||||||
private float defaultHeight;
|
private final Dimension defaultSize = new Dimension();
|
||||||
private boolean initialized = false;
|
private boolean initialized = false;
|
||||||
private SVGOMDocument document;
|
private SVGOMDocument document;
|
||||||
private String uri;
|
private String uri;
|
||||||
@@ -341,54 +325,66 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
// ----
|
// ----
|
||||||
SVGSVGElement rootElement = svgDoc.getRootElement();
|
SVGSVGElement rootElement = svgDoc.getRootElement();
|
||||||
|
|
||||||
// get the 'width' and 'height' attributes of the SVG document
|
// Get the viewBox
|
||||||
UnitProcessor.Context uctx
|
String viewBoxStr = rootElement.getAttributeNS(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
|
||||||
= UnitProcessor.createContext(ctx, rootElement);
|
if (viewBoxStr.length() != 0) {
|
||||||
|
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
|
||||||
|
viewBox.setFrame(rect[0], rect[1], rect[2], rect[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the 'width' and 'height' attributes of the SVG document
|
||||||
|
double width = 0;
|
||||||
|
double height = 0;
|
||||||
|
UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, rootElement);
|
||||||
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
|
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
|
||||||
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
|
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
|
||||||
if (!StringUtil.isEmpty(widthStr)) {
|
if (!StringUtil.isEmpty(widthStr)) {
|
||||||
defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
width = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
||||||
}
|
}
|
||||||
if(!StringUtil.isEmpty(heightStr)){
|
if (!StringUtil.isEmpty(heightStr)) {
|
||||||
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
height = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean hasWidth = defaultWidth > 0.0;
|
boolean hasWidth = width > 0.0;
|
||||||
boolean hasHeight = defaultHeight > 0.0;
|
boolean hasHeight = height > 0.0;
|
||||||
|
|
||||||
if (!hasWidth || !hasHeight) {
|
if (!hasWidth || !hasHeight) {
|
||||||
String viewBoxStr = rootElement.getAttributeNS
|
if (!viewBox.isEmpty()) {
|
||||||
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
|
// If one dimension is given, calculate other by aspect ratio in viewBox
|
||||||
if (viewBoxStr.length() != 0) {
|
|
||||||
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
|
|
||||||
// if one dimension is given, calculate other by aspect ratio in viewBox
|
|
||||||
// or use viewBox if no dimension is given
|
|
||||||
if (hasWidth) {
|
if (hasWidth) {
|
||||||
defaultHeight = defaultWidth * rect[3] / rect[2];
|
height = width * viewBox.getHeight() / viewBox.getWidth();
|
||||||
}
|
}
|
||||||
else if (hasHeight) {
|
else if (hasHeight) {
|
||||||
defaultWidth = defaultHeight * rect[2] / rect[3];
|
width = height * viewBox.getWidth() / viewBox.getHeight();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
defaultWidth = rect[2];
|
// ...or use viewBox if no dimension is given
|
||||||
defaultHeight = rect[3];
|
width = viewBox.getWidth();
|
||||||
|
height = viewBox.getHeight();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// No viewBox, just assume square size
|
||||||
if (hasHeight) {
|
if (hasHeight) {
|
||||||
defaultWidth = defaultHeight;
|
width = height;
|
||||||
}
|
}
|
||||||
else if (hasWidth) {
|
else if (hasWidth) {
|
||||||
defaultHeight = defaultWidth;
|
height = width;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// fallback to batik default sizes
|
// ...or finally fall back to Batik default sizes
|
||||||
defaultWidth = 400;
|
width = 400;
|
||||||
defaultHeight = 400;
|
height = 400;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We now have a size, in the rare case we don't have a viewBox; set it to this size
|
||||||
|
defaultSize.setSize(width, height);
|
||||||
|
if (viewBox.isEmpty()) {
|
||||||
|
viewBox.setRect(0, 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
// Hack to work around exception above
|
// Hack to work around exception above
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
gvtRoot = root;
|
gvtRoot = root;
|
||||||
@@ -401,7 +397,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
ctx = null;
|
ctx = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readImage() throws TranscoderException {
|
private BufferedImage readImage() throws IOException {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
@@ -426,7 +422,8 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (gvtRoot == null) {
|
if (gvtRoot == null) {
|
||||||
throw exception;
|
Throwable cause = unwrapException(exception);
|
||||||
|
throw new IIOException(cause.getMessage(), cause);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx = context;
|
ctx = context;
|
||||||
@@ -444,7 +441,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
setImageSize(defaultWidth, defaultHeight);
|
setImageSize(defaultSize.width, defaultSize.height);
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
@@ -458,18 +455,17 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (BridgeException ex) {
|
catch (BridgeException ex) {
|
||||||
throw new TranscoderException(ex);
|
throw new IIOException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) {
|
if (Px.isIdentity() && (width != defaultSize.width || height != defaultSize.height)) {
|
||||||
// The document has no viewBox, we need to resize it by hand.
|
// The document has no viewBox, we need to resize it by hand.
|
||||||
// we want to keep the document size ratio
|
// we want to keep the document size ratio
|
||||||
float xscale, yscale;
|
float xscale, yscale;
|
||||||
xscale = width / defaultWidth;
|
xscale = width / defaultSize.width;
|
||||||
yscale = height / defaultHeight;
|
yscale = height / defaultSize.height;
|
||||||
float scale = Math.min(xscale, yscale);
|
float scale = Math.min(xscale, yscale);
|
||||||
Px = AffineTransform.getScaleInstance(scale, scale);
|
Px = AffineTransform.getScaleInstance(scale, scale);
|
||||||
}
|
}
|
||||||
@@ -519,7 +515,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (BridgeException ex) {
|
catch (BridgeException ex) {
|
||||||
throw new TranscoderException(ex);
|
throw new IIOException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.root = gvtRoot;
|
this.root = gvtRoot;
|
||||||
@@ -588,7 +584,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
throw new TranscoderException(ex.getMessage(), ex);
|
throw new IIOException(ex.getMessage(), ex);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
@@ -597,7 +593,7 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void init() throws TranscoderException {
|
private synchronized void init() throws IIOException {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
if (transcoderInput == null) {
|
if (transcoderInput == null) {
|
||||||
throw new IllegalStateException("input == null");
|
throw new IllegalStateException("input == null");
|
||||||
@@ -605,11 +601,18 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
|
||||||
super.transcode(transcoderInput, null);
|
try {
|
||||||
|
super.addTranscodingHint(SVGAbstractTranscoder.KEY_ALLOW_EXTERNAL_RESOURCES, allowExternalResources);
|
||||||
|
super.transcode(transcoderInput, null);
|
||||||
|
}
|
||||||
|
catch (TranscoderException e) {
|
||||||
|
Throwable cause = unwrapException(e);
|
||||||
|
throw new IIOException(cause.getMessage(), cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage getImage() throws TranscoderException {
|
private BufferedImage getImage() throws IOException {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
image = readImage();
|
image = readImage();
|
||||||
}
|
}
|
||||||
@@ -617,18 +620,23 @@ public class SVGImageReader extends ImageReaderBase {
|
|||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDefaultWidth() throws TranscoderException {
|
int getDefaultWidth() throws IOException {
|
||||||
init();
|
init();
|
||||||
return (int) Math.ceil(defaultWidth);
|
return defaultSize.width;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getDefaultHeight() throws TranscoderException {
|
int getDefaultHeight() throws IOException {
|
||||||
init();
|
init();
|
||||||
return (int) Math.ceil(defaultHeight);
|
return defaultSize.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setInput(final TranscoderInput pInput) {
|
Rectangle getViewBox() throws IOException {
|
||||||
transcoderInput = pInput;
|
init();
|
||||||
|
return viewBox.getBounds();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInput(final TranscoderInput input) {
|
||||||
|
transcoderInput = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+14
-14
@@ -60,22 +60,22 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new SVGProviderInfo());
|
super(new SVGProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
|
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("StatementWithEmptyBody")
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
private static boolean canDecode(final ImageInputStream input) throws IOException {
|
||||||
// NOTE: This test is quite quick as it does not involve any parsing,
|
// NOTE: This test is quite quick as it does not involve any parsing,
|
||||||
// however it may not recognize all kinds of SVG documents.
|
// however it may not recognize all kinds of SVG documents.
|
||||||
try {
|
try {
|
||||||
pInput.mark();
|
input.mark();
|
||||||
|
|
||||||
// TODO: This is not ok for UTF-16 and other wide encodings
|
// TODO: This is not ok for UTF-16 and other wide encodings
|
||||||
// TODO: Use an XML (encoding) aware Reader instance instead
|
// TODO: Use an XML (encoding) aware Reader instance instead
|
||||||
// Need to figure out pretty fast if this is XML or not
|
// Need to figure out pretty fast if this is XML or not
|
||||||
int b;
|
int b;
|
||||||
while (Character.isWhitespace((char) (b = pInput.read()))) {
|
while (Character.isWhitespace((char) (b = input.read()))) {
|
||||||
// Skip over leading WS
|
// Skip over leading WS
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,30 +95,30 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
|
|
||||||
byte[] buffer = new byte[4];
|
byte[] buffer = new byte[4];
|
||||||
while (true) {
|
while (true) {
|
||||||
pInput.readFully(buffer);
|
input.readFully(buffer);
|
||||||
|
|
||||||
if (buffer[0] == '?') {
|
if (buffer[0] == '?') {
|
||||||
// This is the XML declaration or a processing instruction
|
// This is the XML declaration or a processing instruction
|
||||||
while (!((pInput.readByte() & 0xFF) == '?' && pInput.read() == '>')) {
|
while (!((input.readByte() & 0xFF) == '?' && input.read() == '>')) {
|
||||||
// Skip until end of XML declaration or processing instruction or EOF
|
// Skip until end of XML declaration or processing instruction or EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (buffer[0] == '!') {
|
else if (buffer[0] == '!') {
|
||||||
if (buffer[1] == '-' && buffer[2] == '-') {
|
if (buffer[1] == '-' && buffer[2] == '-') {
|
||||||
// This is a comment
|
// This is a comment
|
||||||
while (!((pInput.readByte() & 0xFF) == '-' && pInput.read() == '-' && pInput.read() == '>')) {
|
while (!((input.readByte() & 0xFF) == '-' && input.read() == '-' && input.read() == '>')) {
|
||||||
// Skip until end of comment or EOF
|
// Skip until end of comment or EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
|
else if (buffer[1] == 'D' && buffer[2] == 'O' && buffer[3] == 'C'
|
||||||
&& pInput.read() == 'T' && pInput.read() == 'Y'
|
&& input.read() == 'T' && input.read() == 'Y'
|
||||||
&& pInput.read() == 'P' && pInput.read() == 'E') {
|
&& input.read() == 'P' && input.read() == 'E') {
|
||||||
// This is the DOCTYPE declaration
|
// This is the DOCTYPE declaration
|
||||||
while (Character.isWhitespace((char) (b = pInput.read()))) {
|
while (Character.isWhitespace((char) (b = input.read()))) {
|
||||||
// Skip over WS
|
// Skip over WS
|
||||||
}
|
}
|
||||||
|
|
||||||
if (b == 's' && pInput.read() == 'v' && pInput.read() == 'g') {
|
if (b == 's' && input.read() == 'v' && input.read() == 'g') {
|
||||||
// It's SVG, identified by DOCTYPE
|
// It's SVG, identified by DOCTYPE
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while ((pInput.readByte() & 0xFF) != '<') {
|
while ((input.readByte() & 0xFF) != '<') {
|
||||||
// Skip over, until next begin tag or EOF
|
// Skip over, until next begin tag or EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ public final class SVGImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
//noinspection ThrowFromFinallyBlock
|
//noinspection ThrowFromFinallyBlock
|
||||||
pInput.reset();
|
input.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -51,16 +51,16 @@ public class SVGReadParam extends ImageReadParam {
|
|||||||
return background;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBackgroundColor(Paint pColor) {
|
public void setBackgroundColor(Paint color) {
|
||||||
background = pColor;
|
background = color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBaseURI() {
|
public String getBaseURI() {
|
||||||
return baseURI;
|
return baseURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBaseURI(String pBaseURI) {
|
public void setBaseURI(String baseURI) {
|
||||||
baseURI = pBaseURI;
|
this.baseURI = baseURI;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAllowExternalResources(boolean allow) {
|
public void setAllowExternalResources(boolean allow) {
|
||||||
|
|||||||
+143
-141
@@ -1,141 +1,143 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2008, 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
|
||||||
* modification, are permitted provided that the following conditions are met:
|
* modification, are permitted provided that the following conditions are met:
|
||||||
*
|
*
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
* list of conditions and the following disclaimer.
|
* list of conditions and the following disclaimer.
|
||||||
*
|
*
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
* and/or other materials provided with the distribution.
|
* and/or other materials provided with the distribution.
|
||||||
*
|
*
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
* * Neither the name of the copyright holder nor the names of its
|
||||||
* contributors may be used to endorse or promote products derived from
|
* contributors may be used to endorse or promote products derived from
|
||||||
* this software without specific prior written permission.
|
* this software without specific prior written permission.
|
||||||
*
|
*
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
* 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
|
* 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.
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
|
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
|
||||||
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
|
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
|
||||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import org.apache.batik.transcoder.TranscoderException;
|
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||||
import org.apache.batik.transcoder.TranscoderInput;
|
|
||||||
import org.apache.batik.transcoder.TranscoderOutput;
|
import org.apache.batik.transcoder.TranscoderException;
|
||||||
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
|
import org.apache.batik.transcoder.TranscoderInput;
|
||||||
|
import org.apache.batik.transcoder.TranscoderOutput;
|
||||||
import javax.imageio.IIOException;
|
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import javax.imageio.ImageReadParam;
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.ImageTypeSpecifier;
|
import javax.imageio.ImageReadParam;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import java.awt.image.BufferedImage;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.io.*;
|
import java.awt.image.*;
|
||||||
import java.util.Iterator;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
/**
|
import java.io.OutputStreamWriter;
|
||||||
* WMFImageReader class description.
|
import java.io.Writer;
|
||||||
*
|
import java.nio.charset.StandardCharsets;
|
||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
import java.util.Iterator;
|
||||||
* @author last modified by $Author: haku $
|
|
||||||
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
|
/**
|
||||||
*/
|
* WMFImageReader class description.
|
||||||
// TODO: Probably possible to do less wrapping/unwrapping of data...
|
*
|
||||||
// TODO: Consider using temp file instead of in-memory stream
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
public final class WMFImageReader extends ImageReaderBase {
|
* @author last modified by $Author: haku $
|
||||||
|
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
|
||||||
private SVGImageReader reader = null;
|
*/
|
||||||
|
// TODO: Probably possible to do less wrapping/unwrapping of data...
|
||||||
public WMFImageReader(final ImageReaderSpi pProvider) {
|
public final class WMFImageReader extends ImageReaderBase {
|
||||||
super(pProvider);
|
|
||||||
}
|
private SVGImageReader reader = null;
|
||||||
|
|
||||||
protected void resetMembers() {
|
public WMFImageReader(final ImageReaderSpi pProvider) {
|
||||||
if (reader != null) {
|
super(pProvider);
|
||||||
reader.dispose();
|
}
|
||||||
}
|
|
||||||
|
protected void resetMembers() {
|
||||||
reader = null;
|
if (reader != null) {
|
||||||
}
|
reader.dispose();
|
||||||
|
}
|
||||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
|
||||||
init();
|
reader = null;
|
||||||
|
}
|
||||||
processImageStarted(pIndex);
|
|
||||||
|
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
||||||
BufferedImage image = reader.read(pIndex, pParam);
|
init();
|
||||||
if (abortRequested()) {
|
|
||||||
processReadAborted();
|
processImageStarted(pIndex);
|
||||||
return image;
|
|
||||||
}
|
BufferedImage image = reader.read(pIndex, pParam);
|
||||||
processImageProgress(100f);
|
if (abortRequested()) {
|
||||||
|
processReadAborted();
|
||||||
processImageComplete();
|
return image;
|
||||||
|
}
|
||||||
return image;
|
processImageProgress(100f);
|
||||||
}
|
|
||||||
|
processImageComplete();
|
||||||
private synchronized void init() throws IOException {
|
|
||||||
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
return image;
|
||||||
if (imageInput == null) {
|
}
|
||||||
throw new IllegalStateException("input == null");
|
|
||||||
}
|
private void init() throws IOException {
|
||||||
|
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
||||||
if (reader == null) {
|
if (imageInput == null) {
|
||||||
WMFTranscoder transcoder = new WMFTranscoder();
|
throw new IllegalStateException("input == null");
|
||||||
|
}
|
||||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
|
||||||
Writer writer = new OutputStreamWriter(output, "UTF8");
|
if (reader == null) {
|
||||||
try {
|
WMFTranscoder transcoder = new WMFTranscoder();
|
||||||
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
|
||||||
TranscoderOutput out = new TranscoderOutput(writer);
|
|
||||||
|
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
|
||||||
// TODO: Transcodinghints?
|
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||||
|
TranscoderOutput out = new TranscoderOutput(writer);
|
||||||
transcoder.transcode(in, out);
|
|
||||||
}
|
// TODO: Transcodinghints?
|
||||||
catch (TranscoderException e) {
|
|
||||||
throw new IIOException(e.getMessage(), e);
|
transcoder.transcode(in, out);
|
||||||
}
|
}
|
||||||
|
catch (TranscoderException e) {
|
||||||
reader = new SVGImageReader(getOriginatingProvider());
|
throw new IIOException(e.getMessage(), e);
|
||||||
reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
|
}
|
||||||
}
|
|
||||||
}
|
reader = new SVGImageReader(getOriginatingProvider());
|
||||||
|
reader.setInput(new ByteArrayImageInputStream(output.toByteArray()));
|
||||||
@Override
|
}
|
||||||
public ImageReadParam getDefaultReadParam() {
|
}
|
||||||
return new SVGReadParam();
|
|
||||||
}
|
@Override
|
||||||
|
public ImageReadParam getDefaultReadParam() {
|
||||||
public int getWidth(int pIndex) throws IOException {
|
return new SVGReadParam();
|
||||||
init();
|
}
|
||||||
return reader.getWidth(pIndex);
|
|
||||||
}
|
public int getWidth(int pIndex) throws IOException {
|
||||||
|
init();
|
||||||
public int getHeight(int pIndex) throws IOException {
|
return reader.getWidth(pIndex);
|
||||||
init();
|
}
|
||||||
return reader.getHeight(pIndex);
|
|
||||||
}
|
public int getHeight(int pIndex) throws IOException {
|
||||||
|
init();
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
return reader.getHeight(pIndex);
|
||||||
init();
|
}
|
||||||
return reader.getImageTypes(pImageIndex);
|
|
||||||
}
|
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
||||||
|
init();
|
||||||
}
|
return reader.getImageTypes(pImageIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+72
-5
@@ -43,8 +43,7 @@ import javax.imageio.event.IIOReadWarningListener;
|
|||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ImagingOpException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
@@ -53,7 +52,10 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.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.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -192,12 +194,12 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
||||||
reader.setInput(redSquare.getInputStream());
|
reader.setInput(redSquare.getInputStream());
|
||||||
BufferedImage imageRed = reader.read(0, param);
|
BufferedImage imageRed = reader.read(0, param);
|
||||||
assertEquals(0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF);
|
assertRGBEquals("Expected all red", 0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||||
|
|
||||||
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
||||||
reader.setInput(blueSquare.getInputStream());
|
reader.setInput(blueSquare.getInputStream());
|
||||||
BufferedImage imageBlue = reader.read(0, param);
|
BufferedImage imageBlue = reader.read(0, param);
|
||||||
assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF);
|
assertRGBEquals("Expected all blue", 0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -337,4 +339,69 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
|||||||
reader.dispose();
|
reader.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadWitSourceRenderSize() throws IOException {
|
||||||
|
URL resource = getClassLoaderResource("/svg/circle.svg");
|
||||||
|
|
||||||
|
SVGImageReader reader = createReader();
|
||||||
|
|
||||||
|
TestData data = new TestData(resource, (Dimension) null);
|
||||||
|
try (ImageInputStream stream = data.getInputStream()) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
SVGReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRenderSize(new Dimension(100, 100));
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(100, image.getWidth());
|
||||||
|
assertEquals(100, image.getHeight());
|
||||||
|
|
||||||
|
// Some quick samples
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 99), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 99), 0);
|
||||||
|
assertRGBEquals("Expected red center", 0xffff0000, image.getRGB(50, 50), 0);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadWitSourceRenderSizeViewBoxNegativeXY() throws IOException {
|
||||||
|
URL resource = getClassLoaderResource("/svg/Android_robot.svg");
|
||||||
|
|
||||||
|
SVGImageReader reader = createReader();
|
||||||
|
|
||||||
|
TestData data = new TestData(resource, (Dimension) null);
|
||||||
|
try (ImageInputStream stream = data.getInputStream()) {
|
||||||
|
reader.setInput(stream);
|
||||||
|
|
||||||
|
SVGReadParam param = reader.getDefaultReadParam();
|
||||||
|
param.setSourceRenderSize(new Dimension(219, 256)); // Aspect scaled to 256 boxed
|
||||||
|
BufferedImage image = reader.read(0, param);
|
||||||
|
|
||||||
|
assertNotNull(image);
|
||||||
|
assertEquals(219, image.getWidth());
|
||||||
|
assertEquals(256, image.getHeight());
|
||||||
|
|
||||||
|
// Some quick samples
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 0), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 255), 0);
|
||||||
|
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 255), 0);
|
||||||
|
assertRGBEquals("Expected green head", 0xffa4c639, image.getRGB(109, 20), 25);
|
||||||
|
assertRGBEquals("Expected green center", 0xffa4c639, image.getRGB(109, 128), 25);
|
||||||
|
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(80, 246), 25);
|
||||||
|
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(130, 246), 25);
|
||||||
|
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(0, 128), 0);
|
||||||
|
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(218, 128), 0);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
reader.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||||
|
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xml:space="preserve"
|
||||||
|
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
|
||||||
|
<circle cx="25" cy="25" r="25" fill="red"/></svg>
|
||||||
|
After Width: | Height: | Size: 436 B |
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-bmp</artifactId>
|
<artifactId>imageio-bmp</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||||
@@ -26,4 +26,23 @@
|
|||||||
<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>
|
||||||
|
|||||||
+29
-21
@@ -39,7 +39,11 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
|||||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.event.IIOReadUpdateListener;
|
import javax.imageio.event.IIOReadUpdateListener;
|
||||||
import javax.imageio.event.IIOReadWarningListener;
|
import javax.imageio.event.IIOReadWarningListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
@@ -47,7 +51,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
|
|||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.*;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -77,8 +81,8 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
super(new BMPImageReaderSpi());
|
super(new BMPImageReaderSpi());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected BMPImageReader(final ImageReaderSpi pProvider) {
|
BMPImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -125,6 +129,7 @@ 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);
|
||||||
@@ -182,30 +187,30 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getWidth(int pImageIndex) throws IOException {
|
public int getWidth(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
return header.getWidth();
|
return header.getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getHeight(int pImageIndex) throws IOException {
|
public int getHeight(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
return header.getHeight();
|
return header.getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(int pImageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
|
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
|
||||||
return Collections.singletonList(getRawImageType(pImageIndex)).iterator();
|
return Collections.singletonList(getRawImageType(imageIndex)).iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ImageTypeSpecifier getRawImageType(int pImageIndex) throws IOException {
|
public ImageTypeSpecifier getRawImageType(int imageIndex) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
if (header.getPlanes() != 1) {
|
if (header.getPlanes() != 1) {
|
||||||
throw new IIOException("Multiple planes not supported");
|
throw new IIOException("Multiple planes not supported");
|
||||||
@@ -358,14 +363,18 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageStarted(imageIndex);
|
processImageStarted(imageIndex);
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
switch (header.getBitCount()) {
|
int bitCount = header.getBitCount();
|
||||||
|
switch (bitCount) {
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
case 4:
|
case 4:
|
||||||
case 8:
|
case 8:
|
||||||
case 24:
|
case 24:
|
||||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||||
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
int bitsPerSample = bitCount == 24 ? 8 : bitCount;
|
||||||
|
int samplesPerPixel = bitCount == 24 ? 3 : 1;
|
||||||
|
|
||||||
|
readRowByte(input, height, srcRegion, xSub, ySub, bitsPerSample, samplesPerPixel, rowDataByte, destRaster, clippedRow, y);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 16:
|
case 16:
|
||||||
@@ -379,7 +388,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
|
throw new AssertionError("Unsupported pixel depth: " + bitCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100f * y / height);
|
processImageProgress(100f * y / height);
|
||||||
@@ -476,6 +485,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||||
|
int bitsPerSample, int samplesPerPixel,
|
||||||
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||||
// Flip into position?
|
// Flip into position?
|
||||||
int srcY = !header.topDown ? height - 1 - y : y;
|
int srcY = !header.topDown ? height - 1 - y : y;
|
||||||
@@ -492,9 +502,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
// Subsample horizontal
|
// Subsample horizontal
|
||||||
if (xSub != 1) {
|
if (xSub != 1) {
|
||||||
for (int x = 0; x < srcRegion.width / xSub; x++) {
|
IIOUtil.subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
|
||||||
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
destChannel.setDataElements(0, dstY, srcChannel);
|
destChannel.setDataElements(0, dstY, srcChannel);
|
||||||
@@ -678,8 +686,8 @@ public final class BMPImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" })
|
@SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" })
|
||||||
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
|
static <T extends Throwable> void throwAs(final Class<T> type, final Throwable throwable) throws T {
|
||||||
throw (T) pThrowable;
|
throw (T) throwable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
private class ListenerDelegator extends ProgressListenerBase implements IIOReadUpdateListener, IIOReadWarningListener {
|
||||||
|
|||||||
+8
-8
@@ -65,16 +65,16 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
|
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean canDecode(final ImageInputStream pInput) throws IOException {
|
private static boolean canDecode(final ImageInputStream input) throws IOException {
|
||||||
byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes)
|
byte[] fileHeader = new byte[18]; // Strictly: file header (14 bytes) + BMP header size field (4 bytes)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pInput.mark();
|
input.mark();
|
||||||
pInput.readFully(fileHeader);
|
input.readFully(fileHeader);
|
||||||
|
|
||||||
// Magic: BM
|
// Magic: BM
|
||||||
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
|
if (fileHeader[0] != 'B' || fileHeader[1] != 'M') {
|
||||||
@@ -112,15 +112,15 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
pInput.reset();
|
input.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
public ImageReader createReaderInstance(final Object extension) {
|
||||||
return new BMPImageReader(this);
|
return new BMPImageReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Windows Device Independent Bitmap Format (BMP) Reader";
|
return "Windows Device Independent Bitmap Format (BMP) Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ import java.nio.ByteOrder;
|
|||||||
* BMPImageWriter
|
* BMPImageWriter
|
||||||
*/
|
*/
|
||||||
public final class BMPImageWriter extends DIBImageWriter {
|
public final class BMPImageWriter extends DIBImageWriter {
|
||||||
protected BMPImageWriter(ImageWriterSpi provider) {
|
BMPImageWriter(ImageWriterSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.AbstractMetadata;
|
||||||
import com.twelvemonkeys.lang.Validate;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
import org.w3c.dom.Node;
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
@@ -141,7 +142,7 @@ final class BMPMetadata extends AbstractMetadata {
|
|||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardChromaNode() {
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
// NOTE: BMP files may contain a color map, even if true color...
|
// NOTE: BMP files may contain a color map, even if true color...
|
||||||
// Not sure if this is a good idea to expose to the meta data,
|
// Not sure if this is a good idea to expose to the metadata,
|
||||||
// as it might be unexpected... Then again...
|
// as it might be unexpected... Then again...
|
||||||
if (colorMap != null) {
|
if (colorMap != null) {
|
||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
||||||
|
|||||||
+7
-7
@@ -29,11 +29,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
import java.awt.image.*;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 pEntry, final DIBHeader pHeader) {
|
public BitmapDescriptor(final DirectoryEntry entry, final DIBHeader header) {
|
||||||
entry = notNull(pEntry, "entry");;
|
this.entry = notNull(entry, "entry");
|
||||||
header = notNull(pHeader, "header");
|
this.header = notNull(header, "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 + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void setMask(final BitmapMask mask) {
|
final void setMask(final BitmapMask mask) {
|
||||||
this.mask = mask;
|
this.mask = mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-22
@@ -29,10 +29,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.DataBuffer;
|
|
||||||
import java.awt.image.IndexColorModel;
|
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
import java.util.Hashtable;
|
import java.util.Hashtable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,12 +38,13 @@ import java.util.Hashtable;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapIndexed.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapIndexed extends BitmapDescriptor {
|
final class BitmapIndexed extends BitmapDescriptor {
|
||||||
protected final int[] bits;
|
final int[] bits;
|
||||||
protected final int[] colors;
|
final int[] colors;
|
||||||
|
|
||||||
|
public BitmapIndexed(final DirectoryEntry entry, final DIBHeader header) {
|
||||||
|
super(entry, header);
|
||||||
|
|
||||||
public BitmapIndexed(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
|
||||||
super(pEntry, pHeader);
|
|
||||||
bits = new int[getWidth() * getHeight()];
|
bits = new int[getWidth() * getHeight()];
|
||||||
|
|
||||||
// NOTE: We're adding space for one extra color, for transparency
|
// NOTE: We're adding space for one extra color, for transparency
|
||||||
@@ -59,20 +57,16 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
|
|
||||||
IndexColorModel icm = createColorModel();
|
IndexColorModel icm = createColorModel();
|
||||||
|
|
||||||
// This is slightly obscure, and should probably be moved..
|
// We add cursor hotspot as a property to images created from CUR format.
|
||||||
|
// This is slightly obscure, and should probably be moved...
|
||||||
Hashtable<String, Object> properties = null;
|
Hashtable<String, Object> properties = null;
|
||||||
if (entry instanceof DirectoryEntry.CUREntry) {
|
if (entry instanceof DirectoryEntry.CUREntry) {
|
||||||
properties = new Hashtable<>(1);
|
properties = new Hashtable<>(1);
|
||||||
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
properties.put("cursor_hotspot", ((DirectoryEntry.CUREntry) this.entry).getHotspot());
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferedImage image = new BufferedImage(
|
WritableRaster raster = icm.createCompatibleWritableRaster(getWidth(), getHeight());
|
||||||
icm,
|
BufferedImage image = new BufferedImage(icm, raster, icm.isAlphaPremultiplied(), properties);
|
||||||
icm.createCompatibleWritableRaster(getWidth(), getHeight()),
|
|
||||||
icm.isAlphaPremultiplied(), properties
|
|
||||||
);
|
|
||||||
|
|
||||||
WritableRaster raster = image.getRaster();
|
|
||||||
|
|
||||||
// Make pixels transparent according to mask
|
// Make pixels transparent according to mask
|
||||||
final int trans = icm.getTransparentPixel();
|
final int trans = icm.getTransparentPixel();
|
||||||
@@ -105,7 +99,7 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
int index = findTransparentIndexMaybeRemap(this.colors, this.bits);
|
||||||
|
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
// No duplicate found, increase bitcount
|
// No duplicate found, increase bit count
|
||||||
bits++;
|
bits++;
|
||||||
transparent = this.colors.length - 1;
|
transparent = this.colors.length - 1;
|
||||||
}
|
}
|
||||||
@@ -117,10 +111,8 @@ class BitmapIndexed extends BitmapDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
// NOTE: Setting hasAlpha to true, makes things work on 1.2
|
||||||
return new IndexColorModel(
|
return new IndexColorModel(bits, colors, this.colors, 0, true, transparent,
|
||||||
bits, colors, this.colors, 0, true, transparent,
|
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT);
|
||||||
bits <= 8 ? DataBuffer.TYPE_BYTE : DataBuffer.TYPE_USHORT
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
private static int findTransparentIndexMaybeRemap(final int[] colors, final int[] bits) {
|
||||||
|
|||||||
+8
-8
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -39,17 +39,17 @@ import java.awt.image.BufferedImage;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapMask.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapMask extends BitmapDescriptor {
|
final class BitmapMask extends BitmapDescriptor {
|
||||||
protected final BitmapIndexed bitMask;
|
final BitmapIndexed bitMask;
|
||||||
|
|
||||||
public BitmapMask(final DirectoryEntry pParent, final DIBHeader pHeader) {
|
public BitmapMask(final DirectoryEntry parent, final DIBHeader header) {
|
||||||
super(pParent, pHeader);
|
super(parent, header);
|
||||||
bitMask = new BitmapIndexed(pParent, pHeader);
|
bitMask = new BitmapIndexed(parent, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isTransparent(final int pX, final int pY) {
|
boolean isTransparent(final int x, final int y) {
|
||||||
// NOTE: 1: Fully transparent, 0: Opaque...
|
// NOTE: 1: Fully transparent, 0: Opaque...
|
||||||
return bitMask.bits[pX + pY * getWidth()] != 0;
|
return bitMask.bits[x + y * getWidth()] != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage getImage() {
|
public BufferedImage getImage() {
|
||||||
|
|||||||
+5
-6
@@ -31,8 +31,7 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
* Describes an RGB/true color bitmap structure (16, 24 and 32 bits per pixel).
|
||||||
@@ -40,10 +39,10 @@ import java.awt.image.WritableRaster;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapRGB.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapRGB extends BitmapDescriptor {
|
final class BitmapRGB extends BitmapDescriptor {
|
||||||
|
|
||||||
public BitmapRGB(final DirectoryEntry pEntry, final DIBHeader pHeader) {
|
public BitmapRGB(final DirectoryEntry entry, final DIBHeader header) {
|
||||||
super(pEntry, pHeader);
|
super(entry, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -71,7 +70,7 @@ class BitmapRGB extends BitmapDescriptor {
|
|||||||
|
|
||||||
WritableRaster alphaRaster = masked.getAlphaRaster();
|
WritableRaster alphaRaster = masked.getAlphaRaster();
|
||||||
|
|
||||||
byte[] trans = {0x0};
|
byte[] trans = {0x00};
|
||||||
for (int y = 0; y < getHeight(); y++) {
|
for (int y = 0; y < getHeight(); y++) {
|
||||||
for (int x = 0; x < getWidth(); x++) {
|
for (int x = 0; x < getWidth(); x++) {
|
||||||
if (mask.isTransparent(x, y)) {
|
if (mask.isTransparent(x, y)) {
|
||||||
|
|||||||
+7
-8
@@ -30,10 +30,9 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents bitmap structures we can't read.
|
* Represents bitmap structures we can't read.
|
||||||
@@ -42,13 +41,13 @@ import javax.imageio.IIOException;
|
|||||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
* @version $Id: BitmapUnsupported.java,v 1.0 25.feb.2006 00:29:44 haku Exp$
|
||||||
*/
|
*/
|
||||||
class BitmapUnsupported extends BitmapDescriptor {
|
final class BitmapUnsupported extends BitmapDescriptor {
|
||||||
private String message;
|
private final String message;
|
||||||
|
|
||||||
public BitmapUnsupported(final DirectoryEntry pEntry, DIBHeader header, final String pMessage) {
|
public BitmapUnsupported(final DirectoryEntry entry, DIBHeader header, final String message) {
|
||||||
super(pEntry, header);
|
super(entry, header);
|
||||||
|
|
||||||
message = pMessage;
|
this.message = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+5
-5
@@ -48,22 +48,22 @@ public final class CURImageReader extends DIBImageReader {
|
|||||||
super(new CURImageReaderSpi());
|
super(new CURImageReaderSpi());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CURImageReader(final ImageReaderSpi pProvider) {
|
CURImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the hot spot location for the cursor.
|
* Returns the hot spot location for the cursor.
|
||||||
*
|
*
|
||||||
* @param pImageIndex the index of the cursor in the current input.
|
* @param imageIndex the index of the cursor in the current input.
|
||||||
* @return the hot spot location for the cursor
|
* @return the hot spot location for the cursor
|
||||||
*
|
*
|
||||||
* @throws java.io.IOException if an I/O exception occurs during reading of image meta data
|
* @throws java.io.IOException if an I/O exception occurs during reading of image meta data
|
||||||
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
|
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
|
||||||
* the number of cursors in the file
|
* the number of cursors in the file
|
||||||
*/
|
*/
|
||||||
public final Point getHotSpot(final int pImageIndex) throws IOException {
|
public Point getHotSpot(final int imageIndex) throws IOException {
|
||||||
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(pImageIndex);
|
DirectoryEntry.CUREntry entry = (DirectoryEntry.CUREntry) getEntry(imageIndex);
|
||||||
return entry.getHotspot();
|
return entry.getHotspot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -49,15 +48,15 @@ public final class CURImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new CURProviderInfo());
|
super(new CURProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) pSource, DIB.TYPE_CUR);
|
return source instanceof ImageInputStream && ICOImageReaderSpi.canDecode((ImageInputStream) source, DIB.TYPE_CUR);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
public CURImageReader createReaderInstance(final Object extension) {
|
||||||
return new CURImageReader(this);
|
return new CURImageReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Windows Cursor Format (CUR) Reader";
|
return "Windows Cursor Format (CUR) Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+171
-103
@@ -30,12 +30,11 @@
|
|||||||
|
|
||||||
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.
|
||||||
*
|
*
|
||||||
@@ -91,17 +90,17 @@ abstract class DIBHeader {
|
|||||||
protected DIBHeader() {
|
protected DIBHeader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DIBHeader read(final DataInput pStream) throws IOException {
|
public static DIBHeader read(final DataInput stream) throws IOException {
|
||||||
int size = pStream.readInt();
|
int size = stream.readInt();
|
||||||
|
|
||||||
DIBHeader header = createHeader(size);
|
DIBHeader header = createHeader(size);
|
||||||
header.read(size, pStream);
|
header.read(size, stream);
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DIBHeader createHeader(final int pSize) throws IOException {
|
private static DIBHeader createHeader(final int size) throws IOException {
|
||||||
switch (pSize) {
|
switch (size) {
|
||||||
case DIB.BITMAP_CORE_HEADER_SIZE:
|
case DIB.BITMAP_CORE_HEADER_SIZE:
|
||||||
return new BitmapCoreHeader();
|
return new BitmapCoreHeader();
|
||||||
case DIB.OS2_V2_HEADER_16_SIZE:
|
case DIB.OS2_V2_HEADER_16_SIZE:
|
||||||
@@ -118,11 +117,12 @@ abstract class DIBHeader {
|
|||||||
case DIB.BITMAP_V5_INFO_HEADER_SIZE:
|
case DIB.BITMAP_V5_INFO_HEADER_SIZE:
|
||||||
return new BitmapV5InfoHeader();
|
return new BitmapV5InfoHeader();
|
||||||
default:
|
default:
|
||||||
throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", pSize));
|
throw new IIOException(String.format("Unknown Bitmap Information Header (size: %s)", size));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void read(int pSize, DataInput pStream) throws IOException;
|
protected abstract void read(int size, DataInput stream) throws IOException;
|
||||||
|
protected abstract void write(final DataOutput stream) throws IOException;
|
||||||
|
|
||||||
public final int getSize() {
|
public final int getSize() {
|
||||||
return size;
|
return size;
|
||||||
@@ -189,12 +189,12 @@ abstract class DIBHeader {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] readMasks(final DataInput pStream, final boolean hasAlphaMask) throws IOException {
|
private static int[] readMasks(final DataInput stream, final boolean hasAlphaMask) throws IOException {
|
||||||
int maskCount = hasAlphaMask ? 4 : 3;
|
int maskCount = hasAlphaMask ? 4 : 3;
|
||||||
int[] masks = new int[4];
|
int[] masks = new int[4];
|
||||||
|
|
||||||
for (int i = 0; i < maskCount; i++) {
|
for (int i = 0; i < maskCount; i++) {
|
||||||
masks[i] = pStream.readInt();
|
masks[i] = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
return masks;
|
return masks;
|
||||||
@@ -205,24 +205,30 @@ abstract class DIBHeader {
|
|||||||
// TODO: Get rid of code duplication below...
|
// TODO: Get rid of code duplication below...
|
||||||
|
|
||||||
static final class BitmapCoreHeader extends DIBHeader {
|
static final class BitmapCoreHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (pSize != DIB.BITMAP_CORE_HEADER_SIZE) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_CORE_HEADER_SIZE));
|
if (size != DIB.BITMAP_CORE_HEADER_SIZE) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_CORE_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
|
// NOTE: Unlike all other headers, width and height are unsigned SHORT values (16 bit)!
|
||||||
width = pStream.readUnsignedShort();
|
width = stream.readUnsignedShort();
|
||||||
height = pStream.readShort();
|
height = stream.readShort();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
@@ -242,45 +248,51 @@ abstract class DIBHeader {
|
|||||||
*/
|
*/
|
||||||
static final class BitmapCoreHeaderV2 extends DIBHeader {
|
static final class BitmapCoreHeaderV2 extends DIBHeader {
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (pSize != DIB.OS2_V2_HEADER_SIZE && pSize != DIB.OS2_V2_HEADER_16_SIZE) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.OS2_V2_HEADER_SIZE));
|
if (size != DIB.OS2_V2_HEADER_SIZE && size != DIB.OS2_V2_HEADER_16_SIZE) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.OS2_V2_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
|
|
||||||
if (pSize != DIB.OS2_V2_HEADER_16_SIZE) {
|
if (size != DIB.OS2_V2_HEADER_16_SIZE) {
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Use? These fields are not reflected in metadata as per now...
|
// TODO: Use? These fields are not reflected in metadata as per now...
|
||||||
int units = pStream.readShort();
|
int units = stream.readShort();
|
||||||
int reserved = pStream.readShort();
|
int reserved = stream.readShort();
|
||||||
int recording = pStream.readShort(); // Recording algorithm
|
int recording = stream.readShort(); // Recording algorithm
|
||||||
int rendering = pStream.readShort(); // Halftoning algorithm
|
int rendering = stream.readShort(); // Halftoning algorithm
|
||||||
int size1 = pStream.readInt(); // Reserved for halftoning use
|
int size1 = stream.readInt(); // Reserved for halftoning use
|
||||||
int size2 = pStream.readInt(); // Reserved for halftoning use
|
int size2 = stream.readInt(); // Reserved for halftoning use
|
||||||
int colorEncoding = pStream.readInt(); // Color model used in bitmap
|
int colorEncoding = stream.readInt(); // Color model used in bitmap
|
||||||
int identifier = pStream.readInt(); // Reserved for application use
|
int identifier = stream.readInt(); // Reserved for application use
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
@@ -288,7 +300,6 @@ abstract class DIBHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure.
|
* Represents the DIB (Device Independent Bitmap) Windows 3.0 Bitmap Information header structure.
|
||||||
* This is the common format for persistent DIB structures, even if Windows
|
* This is the common format for persistent DIB structures, even if Windows
|
||||||
@@ -304,44 +315,46 @@ abstract class DIBHeader {
|
|||||||
* @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>.
|
* @see <a href="https://forums.adobe.com/message/3272950#3272950">BITMAPV3INFOHEADER</a>.
|
||||||
*/
|
*/
|
||||||
static final class BitmapInfoHeader extends DIBHeader {
|
static final class BitmapInfoHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (!(pSize == DIB.BITMAP_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V2_INFO_HEADER_SIZE || pSize == DIB.BITMAP_V3_INFO_HEADER_SIZE)) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_INFO_HEADER_SIZE));
|
if (!(size == DIB.BITMAP_INFO_HEADER_SIZE || size == DIB.BITMAP_V2_INFO_HEADER_SIZE || size == DIB.BITMAP_V3_INFO_HEADER_SIZE)) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_INFO_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
|
|
||||||
// Read masks if we have V2 or V3
|
// Read masks if we have V2 or V3
|
||||||
// or if we have compression BITFIELDS or ALPHA_BITFIELDS
|
// or if we have compression BITFIELDS or ALPHA_BITFIELDS
|
||||||
if (size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) {
|
if (this.size == DIB.BITMAP_V2_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_BITFIELDS) {
|
||||||
masks = readMasks(pStream, false);
|
masks = readMasks(stream, false);
|
||||||
}
|
}
|
||||||
else if (size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) {
|
else if (this.size == DIB.BITMAP_V3_INFO_HEADER_SIZE || compression == DIB.COMPRESSION_ALPHA_BITFIELDS) {
|
||||||
masks = readMasks(pStream, true);
|
masks = readMasks(stream, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(final DataOutput stream) throws IOException {
|
@Override
|
||||||
|
protected void write(final DataOutput stream) throws IOException {
|
||||||
stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE);
|
stream.writeInt(DIB.BITMAP_INFO_HEADER_SIZE);
|
||||||
|
|
||||||
stream.writeInt(width);
|
stream.writeInt(width);
|
||||||
@@ -359,7 +372,7 @@ abstract class DIBHeader {
|
|||||||
stream.writeInt(colorsUsed);
|
stream.writeInt(colorsUsed);
|
||||||
stream.writeInt(colorsImportant);
|
stream.writeInt(colorsImportant);
|
||||||
|
|
||||||
// TODO: Write masks, if bitfields
|
// TODO: Write masks, if COMPRESSION_BITFIELDS/COMPRESSION_ALPHA_BITFIELDS
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
@@ -376,105 +389,160 @@ abstract class DIBHeader {
|
|||||||
* Represents the BITMAPV4INFOHEADER structure.
|
* Represents the BITMAPV4INFOHEADER structure.
|
||||||
*/
|
*/
|
||||||
static final class BitmapV4InfoHeader extends DIBHeader {
|
static final class BitmapV4InfoHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
@Override
|
||||||
if (pSize != DIB.BITMAP_V4_INFO_HEADER_SIZE) {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V4_INFO_HEADER_SIZE));
|
if (size != DIB.BITMAP_V4_INFO_HEADER_SIZE) {
|
||||||
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_V4_INFO_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
|
|
||||||
masks = readMasks(pStream, true);
|
masks = readMasks(stream, true);
|
||||||
|
|
||||||
colorSpaceType = pStream.readInt(); // Should be 0 for V4
|
colorSpaceType = stream.readInt(); // Should be 0 for V4
|
||||||
cieXYZEndpoints = new double[9];
|
cieXYZEndpoints = new double[9];
|
||||||
|
|
||||||
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
||||||
cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
|
cieXYZEndpoints[i] = stream.readInt(); // TODO: Hmmm...?
|
||||||
}
|
}
|
||||||
|
|
||||||
gamma = new int[3];
|
gamma = new int[3];
|
||||||
|
|
||||||
for (int i = 0; i < gamma.length; i++) {
|
for (int i = 0; i < gamma.length; i++) {
|
||||||
gamma[i] = pStream.readInt();
|
gamma[i] = stream.readInt();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
return "BMP v. 4.x";
|
return "BMP v. 4.x";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) throws IOException {
|
||||||
|
stream.writeInt(DIB.BITMAP_V4_INFO_HEADER_SIZE);
|
||||||
|
|
||||||
|
stream.writeInt(width);
|
||||||
|
stream.writeInt(topDown ? -height : height);
|
||||||
|
|
||||||
|
stream.writeShort(planes);
|
||||||
|
stream.writeShort(bitCount);
|
||||||
|
stream.writeInt(compression);
|
||||||
|
|
||||||
|
stream.writeInt(imageSize);
|
||||||
|
|
||||||
|
stream.writeInt(xPixelsPerMeter);
|
||||||
|
stream.writeInt(yPixelsPerMeter);
|
||||||
|
|
||||||
|
stream.writeInt(colorsUsed);
|
||||||
|
stream.writeInt(colorsImportant);
|
||||||
|
|
||||||
|
// Red, Green, Blue, Alpha masks
|
||||||
|
stream.writeInt(masks[0]);
|
||||||
|
stream.writeInt(masks[1]);
|
||||||
|
stream.writeInt(masks[2]);
|
||||||
|
stream.writeInt(masks[3]);
|
||||||
|
|
||||||
|
// color space ("sRGB" LITTLE endian)
|
||||||
|
stream.writeInt(DIB.LCS_sRGB);
|
||||||
|
|
||||||
|
// 36 bytes CIE XYZ triples, unused for sRGB
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
|
||||||
|
// Red gamma, unused for sRGB
|
||||||
|
// Green gamma, unused for sRGB
|
||||||
|
// Blue gamma, unused for sRGB
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
stream.writeInt(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the BITMAPV5INFOHEADER structure.
|
* Represents the BITMAPV5INFOHEADER structure.
|
||||||
*/
|
*/
|
||||||
static final class BitmapV5InfoHeader extends DIBHeader {
|
static final class BitmapV5InfoHeader extends DIBHeader {
|
||||||
protected void read(final int pSize, final DataInput pStream) throws IOException {
|
protected void read(final int size, final DataInput stream) throws IOException {
|
||||||
if (pSize != DIB.BITMAP_V5_INFO_HEADER_SIZE) {
|
if (size != DIB.BITMAP_V5_INFO_HEADER_SIZE) {
|
||||||
throw new IIOException(String.format("Size: %s !=: %s", pSize, DIB.BITMAP_V5_INFO_HEADER_SIZE));
|
throw new IIOException(String.format("Size: %s !=: %s", size, DIB.BITMAP_V5_INFO_HEADER_SIZE));
|
||||||
}
|
}
|
||||||
|
|
||||||
size = pSize;
|
this.size = size;
|
||||||
|
|
||||||
width = pStream.readInt();
|
width = stream.readInt();
|
||||||
height = pStream.readInt();
|
height = stream.readInt();
|
||||||
|
|
||||||
if (height < 0) {
|
if (height < 0) {
|
||||||
height = -height;
|
height = -height;
|
||||||
topDown = true;
|
topDown = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort();
|
planes = stream.readUnsignedShort();
|
||||||
bitCount = pStream.readUnsignedShort();
|
bitCount = stream.readUnsignedShort();
|
||||||
compression = pStream.readInt();
|
compression = stream.readInt();
|
||||||
|
|
||||||
imageSize = pStream.readInt();
|
imageSize = stream.readInt();
|
||||||
|
|
||||||
xPixelsPerMeter = pStream.readInt();
|
xPixelsPerMeter = stream.readInt();
|
||||||
yPixelsPerMeter = pStream.readInt();
|
yPixelsPerMeter = stream.readInt();
|
||||||
|
|
||||||
colorsUsed = pStream.readInt();
|
colorsUsed = stream.readInt();
|
||||||
colorsImportant = pStream.readInt();
|
colorsImportant = stream.readInt();
|
||||||
|
|
||||||
masks = readMasks(pStream, true);
|
masks = readMasks(stream, true);
|
||||||
|
|
||||||
colorSpaceType = pStream.readInt();
|
colorSpaceType = stream.readInt();
|
||||||
|
|
||||||
cieXYZEndpoints = new double[9];
|
cieXYZEndpoints = new double[9];
|
||||||
|
|
||||||
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
for (int i = 0; i < cieXYZEndpoints.length; i++) {
|
||||||
cieXYZEndpoints[i] = pStream.readInt(); // TODO: Hmmm...?
|
cieXYZEndpoints[i] = stream.readInt(); // TODO: Hmmm...?
|
||||||
}
|
}
|
||||||
|
|
||||||
gamma = new int[3];
|
gamma = new int[3];
|
||||||
|
|
||||||
for (int i = 0; i < gamma.length; i++) {
|
for (int i = 0; i < gamma.length; i++) {
|
||||||
gamma[i] = pStream.readInt();
|
gamma[i] = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
intent = pStream.readInt(); // TODO: Verify if this is same as ICC intent
|
intent = stream.readInt(); // TODO: Verify if this is same as ICC intent
|
||||||
profileData = pStream.readInt() & 0xffffffffL;
|
profileData = stream.readInt() & 0xffffffffL;
|
||||||
profileSize = pStream.readInt() & 0xffffffffL;
|
profileSize = stream.readInt() & 0xffffffffL;
|
||||||
pStream.readInt(); // Reserved
|
stream.readInt(); // Reserved
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void write(DataOutput stream) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getBMPVersion() {
|
public String getBMPVersion() {
|
||||||
|
|||||||
+153
-156
@@ -30,19 +30,11 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import java.awt.*;
|
import com.twelvemonkeys.image.ImageUtil;
|
||||||
import java.awt.color.ColorSpace;
|
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||||
import java.awt.event.WindowAdapter;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
import java.awt.event.WindowEvent;
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
import java.awt.image.*;
|
import com.twelvemonkeys.util.WeakWeakMap;
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.ByteOrder;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.WeakHashMap;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
@@ -52,12 +44,18 @@ import javax.imageio.ImageTypeSpecifier;
|
|||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
import com.twelvemonkeys.image.ImageUtil;
|
import java.awt.color.*;
|
||||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
import java.awt.event.*;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import java.awt.image.*;
|
||||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
import java.io.File;
|
||||||
import com.twelvemonkeys.util.WeakWeakMap;
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageReader for Microsoft Windows ICO (icon) format.
|
* ImageReader for Microsoft Windows ICO (icon) format.
|
||||||
@@ -81,13 +79,13 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
private Directory directory;
|
private Directory directory;
|
||||||
|
|
||||||
// TODO: Review these, make sure we don't have a memory leak
|
// TODO: Review these, make sure we don't have a memory leak
|
||||||
private Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
private final Map<DirectoryEntry, DIBHeader> headers = new WeakHashMap<>();
|
||||||
private Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
private final Map<DirectoryEntry, BitmapDescriptor> descriptors = new WeakWeakMap<>();
|
||||||
|
|
||||||
private ImageReader pngImageReader;
|
private ImageReader pngImageReader;
|
||||||
|
|
||||||
protected DIBImageReader(final ImageReaderSpi pProvider) {
|
protected DIBImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void resetMembers() {
|
protected void resetMembers() {
|
||||||
@@ -102,8 +100,8 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
public Iterator<ImageTypeSpecifier> getImageTypes(final int imageIndex) throws IOException {
|
||||||
DirectoryEntry entry = getEntry(pImageIndex);
|
DirectoryEntry entry = getEntry(imageIndex);
|
||||||
|
|
||||||
// NOTE: Delegate to PNG reader
|
// NOTE: Delegate to PNG reader
|
||||||
if (isPNG(entry)) {
|
if (isPNG(entry)) {
|
||||||
@@ -155,39 +153,39 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return getDirectory().count();
|
return getDirectory().count();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getWidth(final int pImageIndex) throws IOException {
|
public int getWidth(final int imageIndex) throws IOException {
|
||||||
return getEntry(pImageIndex).getWidth();
|
return getEntry(imageIndex).getWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getHeight(final int pImageIndex) throws IOException {
|
public int getHeight(final int imageIndex) throws IOException {
|
||||||
return getEntry(pImageIndex).getHeight();
|
return getEntry(imageIndex).getHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferedImage read(final int pImageIndex, final ImageReadParam pParam) throws IOException {
|
public BufferedImage read(final int imageIndex, final ImageReadParam param) throws IOException {
|
||||||
checkBounds(pImageIndex);
|
checkBounds(imageIndex);
|
||||||
|
|
||||||
processImageStarted(pImageIndex);
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
DirectoryEntry entry = getEntry(pImageIndex);
|
DirectoryEntry entry = getEntry(imageIndex);
|
||||||
|
|
||||||
BufferedImage destination;
|
BufferedImage destination;
|
||||||
|
|
||||||
if (isPNG(entry)) {
|
if (isPNG(entry)) {
|
||||||
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
|
// NOTE: Special case for Windows Vista, 256x256 PNG encoded images, with no DIB header...
|
||||||
destination = readPNG(entry, pParam);
|
destination = readPNG(entry, param);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
|
// NOTE: If param does not have explicit destination, we'll try to create a BufferedImage later,
|
||||||
// to allow for storing the cursor hotspot for CUR images
|
// to allow for storing the cursor hotspot for CUR images
|
||||||
destination = hasExplicitDestination(pParam) ?
|
destination = hasExplicitDestination(param) ?
|
||||||
getDestination(pParam, getImageTypes(pImageIndex), getWidth(pImageIndex), getHeight(pImageIndex)) : null;
|
getDestination(param, getImageTypes(imageIndex), getWidth(imageIndex), getHeight(imageIndex)) : null;
|
||||||
|
|
||||||
BufferedImage image = readBitmap(entry);
|
BufferedImage image = readBitmap(entry);
|
||||||
|
|
||||||
// TODO: Handle AOI and subsampling inline, probably not of big importance...
|
// TODO: Handle AOI and subsampling inline, probably not of big importance...
|
||||||
if (pParam != null) {
|
if (param != null) {
|
||||||
image = fakeAOI(image, pParam);
|
image = fakeAOI(image, param);
|
||||||
image = ImageUtil.toBuffered(fakeSubsampling(image, pParam));
|
image = ImageUtil.toBuffered(fakeSubsampling(image, param));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (destination == null) {
|
if (destination == null) {
|
||||||
@@ -213,10 +211,10 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isPNG(final DirectoryEntry pEntry) throws IOException {
|
private boolean isPNG(final DirectoryEntry entry) throws IOException {
|
||||||
long magic;
|
long magic;
|
||||||
|
|
||||||
imageInput.seek(pEntry.getOffset());
|
imageInput.seek(entry.getOffset());
|
||||||
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -229,22 +227,20 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return magic == DIB.PNG_MAGIC;
|
return magic == DIB.PNG_MAGIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readPNG(final DirectoryEntry pEntry, final ImageReadParam pParam) throws IOException {
|
private BufferedImage readPNG(final DirectoryEntry entry, final ImageReadParam param) throws IOException {
|
||||||
// TODO: Consider delegating listener calls
|
// TODO: Consider delegating listener calls
|
||||||
return initPNGReader(pEntry).read(0, pParam);
|
return initPNGReader(entry).read(0, param);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry pEntry) throws IOException {
|
private Iterator<ImageTypeSpecifier> getImageTypesPNG(final DirectoryEntry entry) throws IOException {
|
||||||
return initPNGReader(pEntry).getImageTypes(0);
|
return initPNGReader(entry).getImageTypes(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException {
|
private ImageReader initPNGReader(final DirectoryEntry entry) throws IOException {
|
||||||
ImageReader pngReader = getPNGReader();
|
ImageReader pngReader = getPNGReader();
|
||||||
|
|
||||||
imageInput.seek(pEntry.getOffset());
|
imageInput.seek(entry.getOffset());
|
||||||
// InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize());
|
ImageInputStream stream = new SubImageInputStream(imageInput, entry.getSize());
|
||||||
// ImageInputStream stream = ImageIO.createImageInputStream(inputStream);
|
|
||||||
ImageInputStream stream = new SubImageInputStream(imageInput, pEntry.getSize());
|
|
||||||
|
|
||||||
// NOTE: Will throw IOException on later reads if input is not PNG
|
// NOTE: Will throw IOException on later reads if input is not PNG
|
||||||
pngReader.setInput(stream);
|
pngReader.setInput(stream);
|
||||||
@@ -271,31 +267,31 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return pngImageReader;
|
return pngImageReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException {
|
private DIBHeader getHeader(final DirectoryEntry entry) throws IOException {
|
||||||
if (!headers.containsKey(pEntry)) {
|
if (!headers.containsKey(entry)) {
|
||||||
imageInput.seek(pEntry.getOffset());
|
imageInput.seek(entry.getOffset());
|
||||||
DIBHeader header = DIBHeader.read(imageInput);
|
DIBHeader header = DIBHeader.read(imageInput);
|
||||||
headers.put(pEntry, header);
|
headers.put(entry, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
return headers.get(pEntry);
|
return headers.get(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
private BufferedImage readBitmap(final DirectoryEntry pEntry) throws IOException {
|
private BufferedImage readBitmap(final DirectoryEntry entry) throws IOException {
|
||||||
// TODO: Get rid of the caching, as the images are mutable
|
// TODO: Get rid of the caching, as the images are mutable
|
||||||
BitmapDescriptor descriptor = descriptors.get(pEntry);
|
BitmapDescriptor descriptor = descriptors.get(entry);
|
||||||
|
|
||||||
if (descriptor == null || !descriptors.containsKey(pEntry)) {
|
if (descriptor == null || !descriptors.containsKey(entry)) {
|
||||||
DIBHeader header = getHeader(pEntry);
|
DIBHeader header = getHeader(entry);
|
||||||
|
|
||||||
int offset = pEntry.getOffset() + header.getSize();
|
int offset = entry.getOffset() + header.getSize();
|
||||||
if (offset != imageInput.getStreamPosition()) {
|
if (offset != imageInput.getStreamPosition()) {
|
||||||
imageInput.seek(offset);
|
imageInput.seek(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
|
// TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8
|
||||||
if (header.getCompression() != DIB.COMPRESSION_RGB) {
|
if (header.getCompression() != DIB.COMPRESSION_RGB) {
|
||||||
descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported compression: %d", header.getCompression()));
|
descriptor = new BitmapUnsupported(entry, header, String.format("Unsupported compression: %d", header.getCompression()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
int bitCount = header.getBitCount();
|
int bitCount = header.getBitCount();
|
||||||
@@ -305,75 +301,75 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
case 1:
|
case 1:
|
||||||
case 4:
|
case 4:
|
||||||
case 8: // TODO: Gray!
|
case 8: // TODO: Gray!
|
||||||
descriptor = new BitmapIndexed(pEntry, header);
|
descriptor = new BitmapIndexed(entry, header);
|
||||||
readBitmapIndexed((BitmapIndexed) descriptor);
|
readBitmapIndexed((BitmapIndexed) descriptor);
|
||||||
break;
|
break;
|
||||||
// RGB style
|
// RGB style
|
||||||
case 16:
|
case 16:
|
||||||
descriptor = new BitmapRGB(pEntry, header);
|
descriptor = new BitmapRGB(entry, header);
|
||||||
readBitmap16(descriptor);
|
readBitmap16(descriptor);
|
||||||
break;
|
break;
|
||||||
case 24:
|
case 24:
|
||||||
descriptor = new BitmapRGB(pEntry, header);
|
descriptor = new BitmapRGB(entry, header);
|
||||||
readBitmap24(descriptor);
|
readBitmap24(descriptor);
|
||||||
break;
|
break;
|
||||||
case 32:
|
case 32:
|
||||||
descriptor = new BitmapRGB(pEntry, header);
|
descriptor = new BitmapRGB(entry, header);
|
||||||
readBitmap32(descriptor);
|
readBitmap32(descriptor);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
descriptor = new BitmapUnsupported(pEntry, header, String.format("Unsupported bit count %d", bitCount));
|
descriptor = new BitmapUnsupported(entry, header, String.format("Unsupported bit count %d", bitCount));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
descriptors.put(pEntry, descriptor);
|
descriptors.put(entry, descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return descriptor.getImage();
|
return descriptor.getImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed(final BitmapIndexed pBitmap) throws IOException {
|
private void readBitmapIndexed(final BitmapIndexed bitmap) throws IOException {
|
||||||
readColorMap(pBitmap);
|
readColorMap(bitmap);
|
||||||
|
|
||||||
switch (pBitmap.getBitCount()) {
|
switch (bitmap.getBitCount()) {
|
||||||
case 1:
|
case 1:
|
||||||
readBitmapIndexed1(pBitmap, false);
|
readBitmapIndexed1(bitmap, false);
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
readBitmapIndexed4(pBitmap);
|
readBitmapIndexed4(bitmap);
|
||||||
break;
|
break;
|
||||||
case 8:
|
case 8:
|
||||||
readBitmapIndexed8(pBitmap);
|
readBitmapIndexed8(bitmap);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
BitmapMask mask = new BitmapMask(bitmap.entry, bitmap.header);
|
||||||
readBitmapIndexed1(mask.bitMask, true);
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
pBitmap.setMask(mask);
|
bitmap.setMask(mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readColorMap(final BitmapIndexed pBitmap) throws IOException {
|
private void readColorMap(final BitmapIndexed bitmap) throws IOException {
|
||||||
int colorCount = pBitmap.getColorCount();
|
int colorCount = bitmap.getColorCount();
|
||||||
|
|
||||||
for (int i = 0; i < colorCount; i++) {
|
for (int i = 0; i < colorCount; i++) {
|
||||||
// aRGB (a is "Reserved")
|
// aRGB (a is "Reserved")
|
||||||
pBitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
|
bitmap.colors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed1(final BitmapIndexed pBitmap, final boolean pAsMask) throws IOException {
|
private void readBitmapIndexed1(final BitmapIndexed bitmap, final boolean asMask) throws IOException {
|
||||||
int width = adjustToPadding((pBitmap.getWidth() + 7) >> 3);
|
int width = adjustToPadding((bitmap.getWidth() + 7) >> 3);
|
||||||
byte[] row = new byte[width];
|
byte[] row = new byte[width];
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
imageInput.readFully(row, 0, width);
|
imageInput.readFully(row, 0, width);
|
||||||
int rowPos = 0;
|
int rowPos = 0;
|
||||||
int xOrVal = 0x80;
|
int xOrVal = 0x80;
|
||||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
|
|
||||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
pBitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
|
bitmap.bits[pos++] = ((row[rowPos] & xOrVal) / xOrVal) & 0xFF;
|
||||||
|
|
||||||
if (xOrVal == 1) {
|
if (xOrVal == 1) {
|
||||||
xOrVal = 0x80;
|
xOrVal = 0x80;
|
||||||
@@ -384,29 +380,29 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: If we are reading the mask, we don't abort or report progress
|
// NOTE: If we are reading the mask, we can't abort or report progress
|
||||||
if (!pAsMask) {
|
if (!asMask) {
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed4(final BitmapIndexed pBitmap) throws IOException {
|
private void readBitmapIndexed4(final BitmapIndexed bitmap) throws IOException {
|
||||||
int width = adjustToPadding((pBitmap.getWidth() + 1) >> 1);
|
int width = adjustToPadding((bitmap.getWidth() + 1) >> 1);
|
||||||
byte[] row = new byte[width];
|
byte[] row = new byte[width];
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
imageInput.readFully(row, 0, width);
|
imageInput.readFully(row, 0, width);
|
||||||
int rowPos = 0;
|
int rowPos = 0;
|
||||||
boolean high4 = true;
|
boolean high4 = true;
|
||||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
|
|
||||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
int value;
|
int value;
|
||||||
|
|
||||||
if (high4) {
|
if (high4) {
|
||||||
@@ -417,7 +413,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
rowPos++;
|
rowPos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
pBitmap.bits[pos++] = value & 0xFF;
|
bitmap.bits[pos++] = value & 0xFF;
|
||||||
high4 = !high4;
|
high4 = !high4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,22 +422,22 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmapIndexed8(final BitmapIndexed pBitmap) throws IOException {
|
private void readBitmapIndexed8(final BitmapIndexed bitmap) throws IOException {
|
||||||
int width = adjustToPadding(pBitmap.getWidth());
|
int width = adjustToPadding(bitmap.getWidth());
|
||||||
|
|
||||||
byte[] row = new byte[width];
|
byte[] row = new byte[width];
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
imageInput.readFully(row, 0, width);
|
imageInput.readFully(row, 0, width);
|
||||||
int rowPos = 0;
|
int rowPos = 0;
|
||||||
int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int pos = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
|
|
||||||
for (int x = 0; x < pBitmap.getWidth(); x++) {
|
for (int x = 0; x < bitmap.getWidth(); x++) {
|
||||||
pBitmap.bits[pos++] = row[rowPos++] & 0xFF;
|
bitmap.bits[pos++] = row[rowPos++] & 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
@@ -449,40 +445,41 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param pWidth Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 1)
|
* @param width Bytes per scan line (i.e., 1BPP, width = 9 -> bytes = 2)
|
||||||
* @return padded width
|
* @return padded width
|
||||||
*/
|
*/
|
||||||
private static int adjustToPadding(final int pWidth) {
|
private static int adjustToPadding(final int width) {
|
||||||
if ((pWidth & 0x03) != 0) {
|
if ((width & 0x03) != 0) {
|
||||||
return (pWidth & ~0x03) + 4;
|
return (width & ~0x03) + 4;
|
||||||
}
|
}
|
||||||
return pWidth;
|
|
||||||
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap16(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap16(final BitmapDescriptor bitmap) throws IOException {
|
||||||
short[] pixels = new short[pBitmap.getWidth() * pBitmap.getHeight()];
|
short[] pixels = new short[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
|
|
||||||
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
// TODO: Support TYPE_USHORT_565 and the RGB 444/ARGB 4444 layouts
|
||||||
// Will create TYPE_USHORT_555
|
// Will create TYPE_USHORT_555
|
||||||
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
DirectColorModel cm = new DirectColorModel(16, 0x7C00, 0x03E0, 0x001F);
|
||||||
DataBuffer buffer = new DataBufferUShort(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferUShort(pixels, pixels.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(
|
WritableRaster raster = Raster.createPackedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth(), cm.getMasks(), null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int offset = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
imageInput.readFully(pixels, offset, pBitmap.getWidth());
|
imageInput.readFully(pixels, offset, bitmap.getWidth());
|
||||||
|
|
||||||
|
|
||||||
// Skip to 32 bit boundary
|
// Skip to 32 bit boundary
|
||||||
if (pBitmap.getWidth() % 2 != 0) {
|
if (bitmap.getWidth() % 2 != 0) {
|
||||||
imageInput.readShort();
|
imageInput.readShort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,14 +488,14 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Might be mask!?
|
// TODO: Might be mask!?
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap24(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap24(final BitmapDescriptor bitmap) throws IOException {
|
||||||
byte[] pixels = new byte[pBitmap.getWidth() * pBitmap.getHeight() * 3];
|
byte[] pixels = new byte[bitmap.getWidth() * bitmap.getHeight() * 3];
|
||||||
|
|
||||||
// Create TYPE_3BYTE_BGR
|
// Create TYPE_3BYTE_BGR
|
||||||
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
|
||||||
@@ -509,17 +506,17 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
cs, nBits, false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||||
);
|
);
|
||||||
|
|
||||||
int scanlineStride = pBitmap.getWidth() * 3;
|
int scanlineStride = bitmap.getWidth() * 3;
|
||||||
// BMP rows are padded to 4 byte boundary
|
// BMP rows are padded to 4 byte boundary
|
||||||
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
int rowSizeBytes = ((8 * scanlineStride + 31) / 32) * 4;
|
||||||
|
|
||||||
WritableRaster raster = Raster.createInterleavedRaster(
|
WritableRaster raster = Raster.createInterleavedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), scanlineStride, 3, bOffs, null
|
buffer, bitmap.getWidth(), bitmap.getHeight(), scanlineStride, 3, bOffs, null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * scanlineStride;
|
int offset = (bitmap.getHeight() - y - 1) * scanlineStride;
|
||||||
imageInput.readFully(pixels, offset, scanlineStride);
|
imageInput.readFully(pixels, offset, scanlineStride);
|
||||||
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
imageInput.skipBytes(rowSizeBytes - scanlineStride);
|
||||||
|
|
||||||
@@ -528,38 +525,38 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 24 bit icons usually have a bit mask
|
// 24 bit icons usually have a bit mask
|
||||||
if (pBitmap.hasMask()) {
|
if (bitmap.hasMask()) {
|
||||||
BitmapMask mask = new BitmapMask(pBitmap.entry, pBitmap.header);
|
BitmapMask mask = new BitmapMask(bitmap.entry, bitmap.header);
|
||||||
readBitmapIndexed1(mask.bitMask, true);
|
readBitmapIndexed1(mask.bitMask, true);
|
||||||
|
|
||||||
pBitmap.setMask(mask);
|
bitmap.setMask(mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readBitmap32(final BitmapDescriptor pBitmap) throws IOException {
|
private void readBitmap32(final BitmapDescriptor bitmap) throws IOException {
|
||||||
int[] pixels = new int[pBitmap.getWidth() * pBitmap.getHeight()];
|
int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
|
||||||
|
|
||||||
// Will create TYPE_INT_ARGB
|
// Will create TYPE_INT_ARGB
|
||||||
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
DirectColorModel cm = (DirectColorModel) ColorModel.getRGBdefault();
|
||||||
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
|
DataBuffer buffer = new DataBufferInt(pixels, pixels.length);
|
||||||
WritableRaster raster = Raster.createPackedRaster(
|
WritableRaster raster = Raster.createPackedRaster(
|
||||||
buffer, pBitmap.getWidth(), pBitmap.getHeight(), pBitmap.getWidth(), cm.getMasks(), null
|
buffer, bitmap.getWidth(), bitmap.getHeight(), bitmap.getWidth(), cm.getMasks(), null
|
||||||
);
|
);
|
||||||
pBitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
bitmap.image = new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
|
||||||
|
|
||||||
for (int y = 0; y < pBitmap.getHeight(); y++) {
|
for (int y = 0; y < bitmap.getHeight(); y++) {
|
||||||
int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth();
|
int offset = (bitmap.getHeight() - y - 1) * bitmap.getWidth();
|
||||||
imageInput.readFully(pixels, offset, pBitmap.getWidth());
|
imageInput.readFully(pixels, offset, bitmap.getWidth());
|
||||||
|
|
||||||
if (abortRequested()) {
|
if (abortRequested()) {
|
||||||
processReadAborted();
|
processReadAborted();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
processImageProgress(100 * y / (float) pBitmap.getHeight());
|
processImageProgress(100 * y / (float) bitmap.getHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
// There might be a mask here as well, but we'll ignore it,
|
// There might be a mask here as well, but we'll ignore it,
|
||||||
@@ -590,18 +587,18 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
directory = Directory.read(type, imageCount, imageInput);
|
directory = Directory.read(type, imageCount, imageInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DirectoryEntry getEntry(final int pImageIndex) throws IOException {
|
final DirectoryEntry getEntry(final int imageIndex) throws IOException {
|
||||||
Directory directory = getDirectory();
|
Directory directory = getDirectory();
|
||||||
if (pImageIndex < 0 || pImageIndex >= directory.count()) {
|
if (imageIndex < 0 || imageIndex >= directory.count()) {
|
||||||
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", pImageIndex, directory.count()));
|
throw new IndexOutOfBoundsException(String.format("Index: %d, ImageCount: %d", imageIndex, directory.count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return directory.getEntry(pImageIndex);
|
return directory.getEntry(imageIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test code below, ignore.. :-)
|
/// Test code below, ignore.. :-)
|
||||||
public static void main(final String[] pArgs) throws IOException {
|
public static void main(final String[] args) throws IOException {
|
||||||
if (pArgs.length == 0) {
|
if (args.length == 0) {
|
||||||
System.err.println("Please specify the icon file name");
|
System.err.println("Please specify the icon file name");
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
@@ -613,7 +610,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
String title = new File(pArgs[0]).getName();
|
String title = new File(args[0]).getName();
|
||||||
JFrame frame = createWindow(title);
|
JFrame frame = createWindow(title);
|
||||||
JPanel root = new JPanel(new FlowLayout());
|
JPanel root = new JPanel(new FlowLayout());
|
||||||
JScrollPane scroll =
|
JScrollPane scroll =
|
||||||
@@ -629,7 +626,7 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
ImageReader reader = readers.next();
|
ImageReader reader = readers.next();
|
||||||
|
|
||||||
for (String arg : pArgs) {
|
for (String arg : args) {
|
||||||
JPanel panel = new JPanel(null);
|
JPanel panel = new JPanel(null);
|
||||||
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
|
||||||
readImagesInFile(arg, reader, panel);
|
readImagesInFile(arg, reader, panel);
|
||||||
@@ -640,28 +637,28 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
frame.setVisible(true);
|
frame.setVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void readImagesInFile(String pFileName, ImageReader pReader, final Container pContainer) throws IOException {
|
private static void readImagesInFile(String fileName, ImageReader reader, final Container container) throws IOException {
|
||||||
File file = new File(pFileName);
|
File file = new File(fileName);
|
||||||
if (!file.isFile()) {
|
if (!file.isFile()) {
|
||||||
System.err.println(pFileName + " not found, or is no file");
|
System.err.println(fileName + " not found, or is no file");
|
||||||
}
|
}
|
||||||
|
|
||||||
pReader.setInput(ImageIO.createImageInputStream(file));
|
reader.setInput(ImageIO.createImageInputStream(file));
|
||||||
int imageCount = pReader.getNumImages(true);
|
int imageCount = reader.getNumImages(true);
|
||||||
for (int i = 0; i < imageCount; i++) {
|
for (int i = 0; i < imageCount; i++) {
|
||||||
try {
|
try {
|
||||||
addImage(pContainer, pReader, i);
|
addImage(container, reader, i);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
System.err.println("FileName: " + pFileName);
|
System.err.println("FileName: " + fileName);
|
||||||
System.err.println("Icon: " + i);
|
System.err.println("Icon: " + i);
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JFrame createWindow(final String pTitle) {
|
private static JFrame createWindow(final String title) {
|
||||||
JFrame frame = new JFrame(pTitle);
|
JFrame frame = new JFrame(title);
|
||||||
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
|
||||||
frame.addWindowListener(new WindowAdapter() {
|
frame.addWindowListener(new WindowAdapter() {
|
||||||
public void windowClosed(WindowEvent e) {
|
public void windowClosed(WindowEvent e) {
|
||||||
@@ -671,15 +668,15 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addImage(final Container pParent, final ImageReader pReader, final int pImageNo) throws IOException {
|
private static void addImage(final Container parent, final ImageReader reader, final int imageNo) throws IOException {
|
||||||
final JButton button = new JButton();
|
final JButton button = new JButton();
|
||||||
|
|
||||||
BufferedImage image = pReader.read(pImageNo);
|
BufferedImage image = reader.read(imageNo);
|
||||||
button.setIcon(new ImageIcon(image) {
|
button.setIcon(new ImageIcon(image) {
|
||||||
TexturePaint texture;
|
TexturePaint texture;
|
||||||
|
|
||||||
private void createTexture(final GraphicsConfiguration pGraphicsConfiguration) {
|
private void createTexture(final GraphicsConfiguration graphicsConfiguration) {
|
||||||
BufferedImage pattern = pGraphicsConfiguration.createCompatibleImage(20, 20);
|
BufferedImage pattern = graphicsConfiguration.createCompatibleImage(20, 20);
|
||||||
Graphics2D g = pattern.createGraphics();
|
Graphics2D g = pattern.createGraphics();
|
||||||
try {
|
try {
|
||||||
g.setColor(Color.LIGHT_GRAY);
|
g.setColor(Color.LIGHT_GRAY);
|
||||||
@@ -714,6 +711,6 @@ abstract class DIBImageReader extends ImageReaderBase {
|
|||||||
String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
|
String.valueOf(((IndexColorModel) image.getColorModel()).getMapSize()) :
|
||||||
"TrueColor"));
|
"TrueColor"));
|
||||||
|
|
||||||
pParent.add(button);
|
parent.add(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-9
@@ -44,24 +44,25 @@ import java.util.List;
|
|||||||
class Directory {
|
class Directory {
|
||||||
private final List<DirectoryEntry> entries;
|
private final List<DirectoryEntry> entries;
|
||||||
|
|
||||||
private Directory(int pImageCount) {
|
private Directory(int imageCount) {
|
||||||
entries = Arrays.asList(new DirectoryEntry[pImageCount]);
|
entries = Arrays.asList(new DirectoryEntry[imageCount]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Directory read(final int pType, final int pImageCount, final DataInput pStream) throws IOException {
|
public static Directory read(final int type, final int imageCount, final DataInput stream) throws IOException {
|
||||||
Directory directory = new Directory(pImageCount);
|
Directory directory = new Directory(imageCount);
|
||||||
directory.readEntries(pType, pStream);
|
directory.readEntries(type, stream);
|
||||||
|
|
||||||
return directory;
|
return directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void readEntries(final int pType, final DataInput pStream) throws IOException {
|
private void readEntries(final int type, final DataInput stream) throws IOException {
|
||||||
for (int i = 0; i < entries.size(); i++) {
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
entries.set(i, DirectoryEntry.read(pType, pStream));
|
entries.set(i, DirectoryEntry.read(type, stream));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public DirectoryEntry getEntry(final int pEntryIndex) {
|
public DirectoryEntry getEntry(final int entryIndex) {
|
||||||
return entries.get(pEntryIndex);
|
return entries.get(entryIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int count() {
|
public int count() {
|
||||||
|
|||||||
+19
-24
@@ -32,8 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.ColorModel;
|
import java.awt.image.*;
|
||||||
import java.awt.image.IndexColorModel;
|
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.io.DataOutput;
|
import java.io.DataOutput;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -58,47 +57,43 @@ abstract class DirectoryEntry {
|
|||||||
DirectoryEntry() {
|
DirectoryEntry() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DirectoryEntry read(final int pType, final DataInput pStream) throws IOException {
|
public static DirectoryEntry read(final int type, final DataInput stream) throws IOException {
|
||||||
DirectoryEntry entry = createEntry(pType);
|
DirectoryEntry entry = createEntry(type);
|
||||||
entry.read(pStream);
|
entry.read(stream);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static DirectoryEntry createEntry(int pType) throws IIOException {
|
private static DirectoryEntry createEntry(int type) throws IIOException {
|
||||||
switch (pType) {
|
switch (type) {
|
||||||
case DIB.TYPE_ICO:
|
case DIB.TYPE_ICO:
|
||||||
return new ICOEntry();
|
return new ICOEntry();
|
||||||
case DIB.TYPE_CUR:
|
case DIB.TYPE_CUR:
|
||||||
return new CUREntry();
|
return new CUREntry();
|
||||||
default:
|
default:
|
||||||
throw new IIOException(
|
throw new IIOException(String.format("Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)", type, DIB.TYPE_ICO, DIB.TYPE_CUR));
|
||||||
String.format(
|
|
||||||
"Unknown DIB type: %s, expected: %s (ICO) or %s (CUR)",
|
|
||||||
pType, DIB.TYPE_ICO, DIB.TYPE_CUR
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void read(final DataInput pStream) throws IOException {
|
protected void read(final DataInput stream) throws IOException {
|
||||||
// Width/height = 0, means 256
|
// Width/height = 0, means 256
|
||||||
int w = pStream.readUnsignedByte();
|
int w = stream.readUnsignedByte();
|
||||||
width = w == 0 ? 256 : w;
|
width = w == 0 ? 256 : w;
|
||||||
int h = pStream.readUnsignedByte();
|
int h = stream.readUnsignedByte();
|
||||||
height = h == 0 ? 256 : h;
|
height = h == 0 ? 256 : h;
|
||||||
|
|
||||||
// Color count = 0, means 256 or more colors
|
// Color count = 0, means 256 or more colors
|
||||||
colorCount = pStream.readUnsignedByte();
|
colorCount = stream.readUnsignedByte();
|
||||||
|
|
||||||
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
|
// Ignore. Should be 0, but .NET (System.Drawing.Icon.Save) sets this value to 255, according to Wikipedia
|
||||||
pStream.readUnsignedByte();
|
stream.readUnsignedByte();
|
||||||
|
|
||||||
planes = pStream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
|
planes = stream.readUnsignedShort(); // Should be 0 or 1 for ICO, x hotspot for CUR
|
||||||
bitCount = pStream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
|
bitCount = stream.readUnsignedShort(); // bit count for ICO, y hotspot for CUR
|
||||||
|
|
||||||
// Size of bitmap in bytes
|
// Size of bitmap in bytes
|
||||||
size = pStream.readInt();
|
size = stream.readInt();
|
||||||
offset = pStream.readInt();
|
offset = stream.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
void write(final DataOutput output) throws IOException {
|
void write(final DataOutput output) throws IOException {
|
||||||
@@ -157,8 +152,8 @@ abstract class DirectoryEntry {
|
|||||||
private int yHotspot;
|
private int yHotspot;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void read(final DataInput pStream) throws IOException {
|
protected void read(final DataInput stream) throws IOException {
|
||||||
super.read(pStream);
|
super.read(stream);
|
||||||
|
|
||||||
// NOTE: This is a hack...
|
// NOTE: This is a hack...
|
||||||
xHotspot = planes;
|
xHotspot = planes;
|
||||||
|
|||||||
+2
-2
@@ -46,7 +46,7 @@ public final class ICOImageReader extends DIBImageReader {
|
|||||||
super(new ICOImageReaderSpi());
|
super(new ICOImageReaderSpi());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ICOImageReader(final ImageReaderSpi pProvider) {
|
ICOImageReader(final ImageReaderSpi provider) {
|
||||||
super(pProvider);
|
super(provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-11
@@ -32,7 +32,6 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
|
|
||||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||||
|
|
||||||
import javax.imageio.ImageReader;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -49,32 +48,32 @@ public final class ICOImageReaderSpi extends ImageReaderSpiBase {
|
|||||||
super(new ICOProviderInfo());
|
super(new ICOProviderInfo());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
public boolean canDecodeInput(final Object source) throws IOException {
|
||||||
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource, DIB.TYPE_ICO);
|
return source instanceof ImageInputStream && canDecode((ImageInputStream) source, DIB.TYPE_ICO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean canDecode(final ImageInputStream pInput, final int pType) throws IOException {
|
static boolean canDecode(final ImageInputStream input, final int type) throws IOException {
|
||||||
byte[] signature = new byte[4];
|
byte[] signature = new byte[4];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pInput.mark();
|
input.mark();
|
||||||
pInput.readFully(signature);
|
input.readFully(signature);
|
||||||
|
|
||||||
int count = pInput.readByte() + (pInput.readByte() << 8);
|
int count = input.readByte() + (input.readByte() << 8);
|
||||||
|
|
||||||
return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == pType
|
return (signature[0] == 0x0 && signature[1] == 0x0 && signature[2] == type
|
||||||
&& signature[3] == 0x0 && count > 0);
|
&& signature[3] == 0x0 && count > 0);
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
pInput.reset();
|
input.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
public ICOImageReader createReaderInstance(final Object extension) {
|
||||||
return new ICOImageReader(this);
|
return new ICOImageReader(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Windows Icon Format (ICO) Reader";
|
return "Windows Icon Format (ICO) Reader";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-6
@@ -33,15 +33,19 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
|||||||
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
|
||||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.IIOImage;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.ImageWriteParam;
|
||||||
|
import javax.imageio.ImageWriter;
|
||||||
import javax.imageio.event.IIOWriteWarningListener;
|
import javax.imageio.event.IIOWriteWarningListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.spi.ImageWriterSpi;
|
import javax.imageio.spi.ImageWriterSpi;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import javax.imageio.stream.ImageOutputStream;
|
import javax.imageio.stream.ImageOutputStream;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.ColorModel;
|
|
||||||
import java.awt.image.RenderedImage;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
@@ -64,7 +68,7 @@ public final class ICOImageWriter extends DIBImageWriter {
|
|||||||
|
|
||||||
private ImageWriter pngDelegate;
|
private ImageWriter pngDelegate;
|
||||||
|
|
||||||
protected ICOImageWriter(final ImageWriterSpi provider) {
|
ICOImageWriter(final ImageWriterSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +128,7 @@ public final class ICOImageWriter extends DIBImageWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void endWriteSequence() throws IOException {
|
public void endWriteSequence() {
|
||||||
assertOutput();
|
assertOutput();
|
||||||
|
|
||||||
if (sequenceIndex < 0) {
|
if (sequenceIndex < 0) {
|
||||||
|
|||||||
+26
-29
@@ -30,24 +30,14 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||||
import static org.junit.Assume.assumeNoException;
|
import com.twelvemonkeys.xml.XMLSerializer;
|
||||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
|
||||||
import static org.mockito.Mockito.atLeastOnce;
|
|
||||||
import static org.mockito.Mockito.inOrder;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
import java.awt.*;
|
import org.junit.Ignore;
|
||||||
import java.awt.image.BufferedImage;
|
import org.junit.Test;
|
||||||
import java.io.ByteArrayOutputStream;
|
import org.mockito.InOrder;
|
||||||
import java.io.IOException;
|
import org.w3c.dom.Node;
|
||||||
import java.lang.reflect.Constructor;
|
import org.w3c.dom.NodeList;
|
||||||
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;
|
||||||
@@ -57,16 +47,25 @@ 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 org.junit.Ignore;
|
import static org.junit.Assert.*;
|
||||||
import org.junit.Test;
|
import static org.junit.Assume.assumeNoException;
|
||||||
import org.mockito.InOrder;
|
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||||
import org.w3c.dom.Node;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import org.w3c.dom.NodeList;
|
import static org.mockito.Mockito.atLeastOnce;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
import static org.mockito.Mockito.mock;
|
||||||
import com.twelvemonkeys.xml.XMLSerializer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BMPImageReaderTest
|
* BMPImageReaderTest
|
||||||
@@ -326,10 +325,8 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
|||||||
public void testMetadataEqualsJRE() throws IOException {
|
public void testMetadataEqualsJRE() throws IOException {
|
||||||
ImageReader jreReader;
|
ImageReader jreReader;
|
||||||
try {
|
try {
|
||||||
@SuppressWarnings("unchecked")
|
ImageReaderSpi provider = (ImageReaderSpi) IIORegistry.getDefaultInstance().getServiceProviderByClass(Class.forName("com.sun.imageio.plugins.bmp.BMPImageReaderSpi"));
|
||||||
Class<ImageReader> jreReaderClass = (Class<ImageReader>) Class.forName("com.sun.imageio.plugins.bmp.BMPImageReader");
|
jreReader = provider.createReaderInstance();
|
||||||
Constructor<ImageReader> constructor = jreReaderClass.getConstructor(ImageReaderSpi.class);
|
|
||||||
jreReader = constructor.newInstance(new Object[] {null});
|
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 257 KiB |
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-clippath</artifactId>
|
<artifactId>imageio-clippath</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||||
@@ -32,4 +32,13 @@
|
|||||||
<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,6 +42,7 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
|||||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||||
|
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||||
|
|
||||||
import javax.imageio.*;
|
import javax.imageio.*;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
@@ -307,7 +308,7 @@ public final class Paths {
|
|||||||
throw new IllegalArgumentException("output == null!");
|
throw new IllegalArgumentException("output == null!");
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageTypeSpecifier type = ImageTypeSpecifier.createFromRenderedImage(image);
|
ImageTypeSpecifier type = ImageTypeSpecifiers.createFromRenderedImage(image);
|
||||||
Iterator<ImageWriter> writers = ImageIO.getImageWriters(type, formatName);
|
Iterator<ImageWriter> writers = ImageIO.getImageWriters(type, formatName);
|
||||||
|
|
||||||
if (writers.hasNext()) {
|
if (writers.hasNext()) {
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-core</artifactId>
|
<artifactId>imageio-core</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||||
|
<description>
|
||||||
|
TwelveMonkeys ImageIO core support classes.
|
||||||
|
</description>
|
||||||
|
|
||||||
<properties>
|
<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>
|
||||||
@@ -28,4 +31,20 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Provide-Capability>
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageInputStreamSpi
|
||||||
|
</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+627
@@ -0,0 +1,627 @@
|
|||||||
|
package com.twelvemonkeys.imageio;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.color.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for easy read-only implementation of the standard image metadata format.
|
||||||
|
* Chroma, Data and Transparency nodes values are based on the required
|
||||||
|
* {@link ImageTypeSpecifier}.
|
||||||
|
* Other values or overrides may be specified using the builder.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
*/
|
||||||
|
public class StandardImageMetadataSupport extends AbstractMetadata {
|
||||||
|
|
||||||
|
// The only required field, most standard metadata can be extracted from the type
|
||||||
|
private final ImageTypeSpecifier type;
|
||||||
|
protected final ColorSpaceType colorSpaceType;
|
||||||
|
protected final boolean blackIsZero;
|
||||||
|
private final IndexColorModel palette;
|
||||||
|
protected final String compressionName;
|
||||||
|
protected final boolean compressionLossless;
|
||||||
|
protected final PlanarConfiguration planarConfiguration;
|
||||||
|
private final int[] bitsPerSample;
|
||||||
|
private final int[] significantBits;
|
||||||
|
private final int[] sampleMSB;
|
||||||
|
protected final Double pixelAspectRatio;
|
||||||
|
protected final ImageOrientation orientation;
|
||||||
|
protected final String formatVersion;
|
||||||
|
protected final SubimageInterpretation subimageInterpretation;
|
||||||
|
private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API...
|
||||||
|
private final Collection<TextEntry> textEntries;
|
||||||
|
|
||||||
|
protected StandardImageMetadataSupport(Builder builder) {
|
||||||
|
notNull(builder, "builder");
|
||||||
|
|
||||||
|
// Baseline
|
||||||
|
type = builder.type;
|
||||||
|
|
||||||
|
// Chroma
|
||||||
|
colorSpaceType = builder.colorSpaceType;
|
||||||
|
blackIsZero = builder.blackIsZero;
|
||||||
|
palette = builder.palette;
|
||||||
|
|
||||||
|
// Compression
|
||||||
|
compressionName = builder.compressionName;
|
||||||
|
compressionLossless = builder.compressionLossless;
|
||||||
|
|
||||||
|
// Data
|
||||||
|
planarConfiguration = builder.planarConfiguration;
|
||||||
|
bitsPerSample = builder.bitsPerSample;
|
||||||
|
significantBits = builder.significantBits;
|
||||||
|
sampleMSB = builder.sampleMSB;
|
||||||
|
|
||||||
|
// Dimension
|
||||||
|
orientation = builder.orientation;
|
||||||
|
pixelAspectRatio = builder.pixelAspectRatio;
|
||||||
|
|
||||||
|
// Document
|
||||||
|
formatVersion = builder.formatVersion;
|
||||||
|
documentCreationTime = builder.documentCreationTime;
|
||||||
|
subimageInterpretation = builder.subimageInterpretation;
|
||||||
|
|
||||||
|
// Text
|
||||||
|
textEntries = builder.textEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder(ImageTypeSpecifier type) {
|
||||||
|
return new Builder(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Builder {
|
||||||
|
private final ImageTypeSpecifier type;
|
||||||
|
private ColorSpaceType colorSpaceType;
|
||||||
|
private boolean blackIsZero = true;
|
||||||
|
private IndexColorModel palette;
|
||||||
|
private String compressionName;
|
||||||
|
private boolean compressionLossless = true;
|
||||||
|
private PlanarConfiguration planarConfiguration;
|
||||||
|
public int[] bitsPerSample;
|
||||||
|
private int[] significantBits;
|
||||||
|
private int[] sampleMSB;
|
||||||
|
private Double pixelAspectRatio;
|
||||||
|
private ImageOrientation orientation = ImageOrientation.Normal;
|
||||||
|
private String formatVersion;
|
||||||
|
private SubimageInterpretation subimageInterpretation;
|
||||||
|
private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type
|
||||||
|
private final Collection<TextEntry> textEntries = new ArrayList<>();
|
||||||
|
|
||||||
|
protected Builder(ImageTypeSpecifier type) {
|
||||||
|
this.type = notNull(type, "type");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withColorSpaceType(ColorSpaceType colorSpaceType) {
|
||||||
|
this.colorSpaceType = colorSpaceType;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withBlackIsZero(boolean blackIsZero) {
|
||||||
|
this.blackIsZero = blackIsZero;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPalette(IndexColorModel palette) {
|
||||||
|
this.palette = palette;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withCompressionTypeName(String compressionName) {
|
||||||
|
this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withCompressionLossless(boolean lossless) {
|
||||||
|
this.compressionLossless = isTrue(lossless || compressionName != null, lossless, "Lossy compression requires compression name");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) {
|
||||||
|
this.planarConfiguration = planarConfiguration;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withBitsPerSample(int... bitsPerSample) {
|
||||||
|
this.bitsPerSample = bitsPerSample;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSignificantBitsPerSample(int... significantBits) {
|
||||||
|
this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(),
|
||||||
|
significantBits,
|
||||||
|
String.format("single value or %d values expected", type.getNumBands()));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSampleMSB(int... sampleMSB) {
|
||||||
|
this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(),
|
||||||
|
sampleMSB,
|
||||||
|
String.format("single value or %d values expected", type.getNumBands()));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withPixelAspectRatio(Double pixelAspectRatio) {
|
||||||
|
this.pixelAspectRatio = pixelAspectRatio;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withOrientation(ImageOrientation orientation) {
|
||||||
|
this.orientation = notNull(orientation, "orientation");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withFormatVersion(String formatVersion) {
|
||||||
|
this.formatVersion = notNull(formatVersion, "formatVersion");
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withSubimageInterpretation(SubimageInterpretation interpretation) {
|
||||||
|
this.subimageInterpretation = interpretation;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withDocumentCreationTime(Calendar creationTime) {
|
||||||
|
this.documentCreationTime = creationTime;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withTextEntries(Map<String, String> entries) {
|
||||||
|
return withTextEntries(toTextEntries(notNull(entries, "entries").entrySet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<TextEntry> toTextEntries(Collection<Map.Entry<String, String>> entries) {
|
||||||
|
TextEntry[] result = new TextEntry[entries.size()];
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (Map.Entry<String, String> entry : entries) {
|
||||||
|
result[i++] = new TextEntry(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arrays.asList(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withTextEntries(Collection<TextEntry> entries) {
|
||||||
|
this.textEntries.addAll(notNull(entries, "entries"));
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Builder withTextEntry(String keyword, String value) {
|
||||||
|
if (value != null && !value.isEmpty()) {
|
||||||
|
this.textEntries.add(new TextEntry(notNull(keyword, "keyword"), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IIOMetadata build() {
|
||||||
|
return new StandardImageMetadataSupport(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum ColorSpaceType {
|
||||||
|
XYZ(3),
|
||||||
|
Lab(3),
|
||||||
|
Luv(3),
|
||||||
|
YCbCr(3),
|
||||||
|
Yxy(3),
|
||||||
|
YCCK(4),
|
||||||
|
PhotoYCC(3),
|
||||||
|
RGB(3),
|
||||||
|
GRAY(1),
|
||||||
|
HSV(3),
|
||||||
|
HLS(3),
|
||||||
|
CMYK(3),
|
||||||
|
CMY(3),
|
||||||
|
|
||||||
|
// Generic types (so much extra work, because Java names can't start with a number, phew...)
|
||||||
|
GENERIC_2CLR(2, "2CLR"),
|
||||||
|
GENERIC_3CLR(3, "3CLR"),
|
||||||
|
GENERIC_4CLR(4, "4CLR"),
|
||||||
|
GENERIC_5CLR(5, "5CLR"),
|
||||||
|
GENERIC_6CLR(6, "6CLR"),
|
||||||
|
GENERIC_7CLR(7, "7CLR"),
|
||||||
|
GENERIC_8CLR(8, "8CLR"),
|
||||||
|
GENERIC_9CLR(9, "9CLR"),
|
||||||
|
GENERIC_ACLR(0xA, "ACLR"),
|
||||||
|
GENERIC_BCLR(0xB, "BCLR"),
|
||||||
|
GENERIC_CCLR(0xC, "CCLR"),
|
||||||
|
GENERIC_DCLR(0xD, "DCLR"),
|
||||||
|
GENERIC_ECLR(0xE, "ECLR"),
|
||||||
|
GENERIC_FCLR(0xF, "FCLR");
|
||||||
|
|
||||||
|
final int numChannels;
|
||||||
|
private final String nameOverride;
|
||||||
|
|
||||||
|
ColorSpaceType(int numChannels) {
|
||||||
|
this(numChannels, null);
|
||||||
|
}
|
||||||
|
ColorSpaceType(int numChannels, String nameOverride) {
|
||||||
|
this.numChannels = numChannels;
|
||||||
|
this.nameOverride = nameOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return nameOverride != null ? nameOverride : super.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum PlanarConfiguration {
|
||||||
|
PixelInterleaved,
|
||||||
|
PlaneInterleaved,
|
||||||
|
LineInterleaved,
|
||||||
|
TileInterleaved
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum ImageOrientation {
|
||||||
|
Normal,
|
||||||
|
Rotate90,
|
||||||
|
Rotate180,
|
||||||
|
Rotate270,
|
||||||
|
FlipH,
|
||||||
|
FlipV,
|
||||||
|
FlipHRotate90,
|
||||||
|
FlipVRotate90
|
||||||
|
}
|
||||||
|
|
||||||
|
protected enum SubimageInterpretation {
|
||||||
|
Standalone,
|
||||||
|
SinglePage,
|
||||||
|
FullResolution,
|
||||||
|
ReducedResolution,
|
||||||
|
PyramidLayer,
|
||||||
|
Preview,
|
||||||
|
VolumeSlice,
|
||||||
|
ObjectView,
|
||||||
|
Panorama,
|
||||||
|
AnimationFrame,
|
||||||
|
TransparencyMask,
|
||||||
|
CompositingLayer,
|
||||||
|
SpectralSlice,
|
||||||
|
Unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardChromaNode() {
|
||||||
|
IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma");
|
||||||
|
|
||||||
|
ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel();
|
||||||
|
ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace());
|
||||||
|
int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents();
|
||||||
|
|
||||||
|
IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType");
|
||||||
|
chromaNode.appendChild(colorSpaceTypeNode);
|
||||||
|
colorSpaceTypeNode.setAttribute("name", csType.toString());
|
||||||
|
|
||||||
|
IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels");
|
||||||
|
numChannelsNode.setAttribute("value", String.valueOf(numComponents));
|
||||||
|
chromaNode.appendChild(numChannelsNode);
|
||||||
|
|
||||||
|
IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero");
|
||||||
|
blackIsZeroNode.setAttribute("value", booleanString(blackIsZero));
|
||||||
|
chromaNode.appendChild(blackIsZeroNode);
|
||||||
|
|
||||||
|
if (colorModel instanceof IndexColorModel || palette != null) {
|
||||||
|
IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel;
|
||||||
|
|
||||||
|
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
|
||||||
|
chromaNode.appendChild(paletteNode);
|
||||||
|
|
||||||
|
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||||
|
IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry");
|
||||||
|
paletteNode.appendChild(paletteEntryNode);
|
||||||
|
|
||||||
|
paletteEntryNode.setAttribute("index", Integer.toString(i));
|
||||||
|
paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||||
|
paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||||
|
paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||||
|
|
||||||
|
// Assumption: BITMASK transparency will use single transparent pixel
|
||||||
|
if (colorMap.getTransparency() == Transparency.TRANSLUCENT) {
|
||||||
|
paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (colorMap.getTransparentPixel() != -1) {
|
||||||
|
IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex");
|
||||||
|
chromaNode.appendChild(backgroundIndexNode);
|
||||||
|
backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: BackgroundColor?
|
||||||
|
|
||||||
|
return chromaNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) {
|
||||||
|
switch (colorSpace.getType()) {
|
||||||
|
case ColorSpace.TYPE_XYZ:
|
||||||
|
return XYZ;
|
||||||
|
case ColorSpace.TYPE_Lab:
|
||||||
|
return Lab;
|
||||||
|
case ColorSpace.TYPE_Luv:
|
||||||
|
return Luv;
|
||||||
|
case ColorSpace.TYPE_YCbCr:
|
||||||
|
return YCbCr;
|
||||||
|
case ColorSpace.TYPE_Yxy:
|
||||||
|
return Yxy;
|
||||||
|
// Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace
|
||||||
|
case ColorSpace.TYPE_RGB:
|
||||||
|
return RGB;
|
||||||
|
case ColorSpace.TYPE_GRAY:
|
||||||
|
return GRAY;
|
||||||
|
case ColorSpace.TYPE_HSV:
|
||||||
|
return HSV;
|
||||||
|
case ColorSpace.TYPE_HLS:
|
||||||
|
return HLS;
|
||||||
|
case ColorSpace.TYPE_CMYK:
|
||||||
|
return CMYK;
|
||||||
|
case ColorSpace.TYPE_CMY:
|
||||||
|
return CMY;
|
||||||
|
default:
|
||||||
|
int numComponents = colorSpace.getNumComponents();
|
||||||
|
if (numComponents == 1) {
|
||||||
|
return GRAY;
|
||||||
|
}
|
||||||
|
else if (numComponents < 16) {
|
||||||
|
return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static final class TextEntry {
|
||||||
|
static final List<String> COMPRESSIONS = Arrays.asList("none", "lzw", "zip", "bzip", "other");
|
||||||
|
|
||||||
|
final String keyword;
|
||||||
|
final String value;
|
||||||
|
final String language;
|
||||||
|
final String encoding;
|
||||||
|
final String compression;
|
||||||
|
|
||||||
|
public TextEntry(final String keyword, final String value) {
|
||||||
|
this(keyword, value, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextEntry(final String keyword, final String value, final String language, final String encoding, final String compression) {
|
||||||
|
this.keyword = keyword;
|
||||||
|
this.value = notNull(value, "value");
|
||||||
|
this.language = language;
|
||||||
|
this.encoding = encoding;
|
||||||
|
this.compression = isTrue(compression == null || COMPRESSIONS.contains(compression), compression, String.format("Unknown compression: %s (expected: %s)", compression, COMPRESSIONS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardCompressionNode() {
|
||||||
|
if (compressionName == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||||
|
|
||||||
|
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||||
|
compressionTypeName.setAttribute("value", compressionName);
|
||||||
|
node.appendChild(compressionTypeName);
|
||||||
|
|
||||||
|
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||||
|
lossless.setAttribute("value", booleanString(compressionLossless));
|
||||||
|
node.appendChild(lossless);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String booleanString(boolean booleanValue) {
|
||||||
|
return booleanValue ? "TRUE" : "FALSE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDataNode() {
|
||||||
|
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||||
|
|
||||||
|
IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration");
|
||||||
|
dataNode.appendChild(planarConfigurationNode);
|
||||||
|
planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() :
|
||||||
|
(type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved"));
|
||||||
|
|
||||||
|
String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel
|
||||||
|
? "Index"
|
||||||
|
: sampleFormat(type.getSampleModel());
|
||||||
|
|
||||||
|
if (sampleFormatValue != null) {
|
||||||
|
IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat");
|
||||||
|
sampleFormatNode.setAttribute("value", sampleFormatValue);
|
||||||
|
dataNode.appendChild(sampleFormatNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize();
|
||||||
|
IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample");
|
||||||
|
bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample));
|
||||||
|
dataNode.appendChild(bitsPerSampleNode);
|
||||||
|
|
||||||
|
if (significantBits != null) {
|
||||||
|
String significantBitsValue = createListValue(type.getNumBands(), significantBits);
|
||||||
|
if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) {
|
||||||
|
IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample");
|
||||||
|
significantBitsPerSampleNode.setAttribute("value", significantBitsValue);
|
||||||
|
dataNode.appendChild(significantBitsPerSampleNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sampleMSB != null) {
|
||||||
|
// TODO: Only if different from default!
|
||||||
|
IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB");
|
||||||
|
sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB));
|
||||||
|
dataNode.appendChild(sampleMSBNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String createListValue(final int itemCount, final int... values) {
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
|
||||||
|
for (int i = 0; i < itemCount; i++) {
|
||||||
|
if (buffer.length() > 0) {
|
||||||
|
buffer.append(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.append(values[i % values.length]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sampleFormat(SampleModel sampleModel) {
|
||||||
|
switch (sampleModel.getDataType()) {
|
||||||
|
case DataBuffer.TYPE_SHORT:
|
||||||
|
case DataBuffer.TYPE_INT:
|
||||||
|
if (sampleModel instanceof ComponentSampleModel) {
|
||||||
|
return "SignedIntegral";
|
||||||
|
}
|
||||||
|
// Otherwise fall-through, most likely a *PixelPackedSampleModel
|
||||||
|
case DataBuffer.TYPE_BYTE:
|
||||||
|
case DataBuffer.TYPE_USHORT:
|
||||||
|
return "UnsignedIntegral";
|
||||||
|
case DataBuffer.TYPE_FLOAT:
|
||||||
|
case DataBuffer.TYPE_DOUBLE:
|
||||||
|
return "Real";
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDimensionNode() {
|
||||||
|
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||||
|
|
||||||
|
if (pixelAspectRatio != null) {
|
||||||
|
IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio");
|
||||||
|
pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio));
|
||||||
|
dimensionNode.appendChild(pixelAspectRatioNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation");
|
||||||
|
imageOrientationNode.setAttribute("value", orientation.toString());
|
||||||
|
dimensionNode.appendChild(imageOrientationNode);
|
||||||
|
|
||||||
|
return dimensionNode.hasChildNodes() ? dimensionNode : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardDocumentNode() {
|
||||||
|
IIOMetadataNode documentNode = new IIOMetadataNode("Document");
|
||||||
|
|
||||||
|
if (formatVersion != null) {
|
||||||
|
IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion");
|
||||||
|
documentNode.appendChild(formatVersionNode);
|
||||||
|
formatVersionNode.setAttribute("value", formatVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subimageInterpretation != null) {
|
||||||
|
IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation");
|
||||||
|
documentNode.appendChild(subImageInterpretationNode);
|
||||||
|
subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (documentCreationTime != null) {
|
||||||
|
IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime");
|
||||||
|
documentNode.appendChild(imageCreationTimeNode);
|
||||||
|
|
||||||
|
imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR)));
|
||||||
|
imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1));
|
||||||
|
imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH)));
|
||||||
|
imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY)));
|
||||||
|
imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE)));
|
||||||
|
imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return documentNode.hasChildNodes() ? documentNode : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTextNode() {
|
||||||
|
if (textEntries.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIOMetadataNode textNode = new IIOMetadataNode("Text");
|
||||||
|
|
||||||
|
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||||
|
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||||
|
|
||||||
|
for (TextEntry entry : textEntries) {
|
||||||
|
IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry");
|
||||||
|
textNode.appendChild(textEntryNode);
|
||||||
|
if (entry.keyword != null) {
|
||||||
|
textEntryNode.setAttribute("keyword", entry.keyword);
|
||||||
|
}
|
||||||
|
textEntryNode.setAttribute("value", entry.value);
|
||||||
|
if (entry.language != null) {
|
||||||
|
textEntryNode.setAttribute("language", entry.language);
|
||||||
|
}
|
||||||
|
if (entry.encoding != null) {
|
||||||
|
textEntryNode.setAttribute("encoding", entry.encoding);
|
||||||
|
}
|
||||||
|
if (entry.compression != null) {
|
||||||
|
textEntryNode.setAttribute("compression", entry.compression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textNode;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||||
|
IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency");
|
||||||
|
|
||||||
|
ColorModel colorModel = type.getColorModel();
|
||||||
|
|
||||||
|
IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha");
|
||||||
|
transparencyNode.appendChild(alphaNode);
|
||||||
|
alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none");
|
||||||
|
|
||||||
|
if (colorModel instanceof IndexColorModel) {
|
||||||
|
IndexColorModel icm = (IndexColorModel) colorModel;
|
||||||
|
if (icm.getTransparentPixel() != -1) {
|
||||||
|
IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex");
|
||||||
|
transparencyNode.appendChild(transparentIndexNode);
|
||||||
|
transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transparencyNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 *some times* after validation of bad profiles,
|
// This breaks *sometimes* after validation of bad profiles,
|
||||||
// we'll let it blow up early in this case
|
// we'll let it blow up early in this case
|
||||||
cs.getProfile().getData();
|
cs.getProfile().getData();
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-20
@@ -54,8 +54,8 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
// Our IndexColorModel delegate
|
// Our IndexColorModel delegate
|
||||||
private final IndexColorModel icm;
|
private final IndexColorModel icm;
|
||||||
|
|
||||||
|
private final int extraSamples;
|
||||||
private final int samples;
|
private final int samples;
|
||||||
private final boolean hasAlpha;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
|
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
|
||||||
@@ -86,33 +86,33 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.icm = icm;
|
this.icm = icm;
|
||||||
|
this.extraSamples = extraSamples;
|
||||||
this.samples = 1 + extraSamples;
|
this.samples = 1 + extraSamples;
|
||||||
this.hasAlpha = hasAlpha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNumComponents() {
|
public int getNumComponents() {
|
||||||
return samples;
|
return getNumColorComponents() + extraSamples;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getRed(final int pixel) {
|
public int getRed(final int pixel) {
|
||||||
return icm.getRed(pixel);
|
return icm.getRed(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getGreen(final int pixel) {
|
public int getGreen(final int pixel) {
|
||||||
return icm.getGreen(pixel);
|
return icm.getGreen(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getBlue(final int pixel) {
|
public int getBlue(final int pixel) {
|
||||||
return icm.getBlue(pixel);
|
return icm.getBlue(pixel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getAlpha(final int pixel) {
|
public int getAlpha(final int pixel) {
|
||||||
return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
|
return hasAlpha() ? (int) ((((float) pixel) / ((1 << getComponentSize(3)) - 1)) * 255.0f + 0.5f) : 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getSample(final Object inData, final int index) {
|
private int getSample(final Object inData, final int index) {
|
||||||
@@ -120,15 +120,15 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
|
|
||||||
switch (transferType) {
|
switch (transferType) {
|
||||||
case DataBuffer.TYPE_BYTE:
|
case DataBuffer.TYPE_BYTE:
|
||||||
byte bdata[] = (byte[]) inData;
|
byte[] bdata = (byte[]) inData;
|
||||||
pixel = bdata[index] & 0xff;
|
pixel = bdata[index] & 0xff;
|
||||||
break;
|
break;
|
||||||
case DataBuffer.TYPE_USHORT:
|
case DataBuffer.TYPE_USHORT:
|
||||||
short sdata[] = (short[]) inData;
|
short[] sdata = (short[]) inData;
|
||||||
pixel = sdata[index] & 0xffff;
|
pixel = sdata[index] & 0xffff;
|
||||||
break;
|
break;
|
||||||
case DataBuffer.TYPE_INT:
|
case DataBuffer.TYPE_INT:
|
||||||
int idata[] = (int[]) inData;
|
int[] idata = (int[]) inData;
|
||||||
pixel = idata[index];
|
pixel = idata[index];
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -139,27 +139,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getRed(final Object inData) {
|
public int getRed(final Object inData) {
|
||||||
return getRed(getSample(inData, 0));
|
return getRed(getSample(inData, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getGreen(final Object inData) {
|
public int getGreen(final Object inData) {
|
||||||
return getGreen(getSample(inData, 0));
|
return getGreen(getSample(inData, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getBlue(final Object inData) {
|
public int getBlue(final Object inData) {
|
||||||
return getBlue(getSample(inData, 0));
|
return getBlue(getSample(inData, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final int getAlpha(final Object inData) {
|
public int getAlpha(final Object inData) {
|
||||||
return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
|
return hasAlpha() ? getAlpha(getSample(inData, 1)) : 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final SampleModel createCompatibleSampleModel(final int w, final int h) {
|
public SampleModel createCompatibleSampleModel(final int w, final int h) {
|
||||||
return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
|
return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,17 +174,17 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCompatibleSampleModel(final SampleModel sm) {
|
public boolean isCompatibleSampleModel(final SampleModel sm) {
|
||||||
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
|
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
public WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
||||||
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
|
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCompatibleRaster(final Raster raster) {
|
public boolean isCompatibleRaster(final Raster raster) {
|
||||||
int size = raster.getSampleModel().getSampleSize(0);
|
int size = raster.getSampleModel().getSampleSize(0);
|
||||||
return ((raster.getTransferType() == transferType) &&
|
return ((raster.getTransferType() == transferType) &&
|
||||||
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
|
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
|
||||||
|
|||||||
+348
@@ -0,0 +1,348 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStreamImpl;
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.StandardOpenOption;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
|
||||||
|
* and provides greatly improved performance
|
||||||
|
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
|
||||||
|
* for shorter reads, like single byte or bit reads.
|
||||||
|
*/
|
||||||
|
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
|
||||||
|
private static final Closeable CLOSEABLE_STUB = new Closeable() {
|
||||||
|
@Override public void close() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||||
|
|
||||||
|
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
|
||||||
|
private byte[] buffer = byteBuffer.array();
|
||||||
|
private int bufferPos;
|
||||||
|
private int bufferLimit;
|
||||||
|
|
||||||
|
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
|
||||||
|
private final byte[] integralCacheArray = integralCache.array();
|
||||||
|
|
||||||
|
private SeekableByteChannel channel;
|
||||||
|
private Closeable closeable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
|
||||||
|
*
|
||||||
|
* @param file a {@code File} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||||
|
* @throws IOException if an I/O error occurs while opening the file.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final File file) throws IOException {
|
||||||
|
this(notNull(file, "file").toPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
|
||||||
|
*
|
||||||
|
* @param file a {@code Path} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
|
||||||
|
* @throws IOException if an I/O error occurs while opening the file.
|
||||||
|
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final Path file) throws IOException {
|
||||||
|
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
|
||||||
|
*
|
||||||
|
* @param file a {@code RandomAccessFile} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final RandomAccessFile file) {
|
||||||
|
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
|
||||||
|
this(notNull(file, "file").getChannel(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param inputStream a {@code FileInputStream} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
|
||||||
|
this(notNull(inputStream, "inputStream").getChannel(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param channel a {@code SeekableByteChannel} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||||
|
*/
|
||||||
|
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
|
||||||
|
this(notNull(channel, "channel"), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
|
||||||
|
* <p>
|
||||||
|
* Closing this stream will close the {@code Cache}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param cache a {@code SeekableByteChannel} to read from.
|
||||||
|
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||||
|
*/
|
||||||
|
BufferedChannelImageInputStream(final Cache cache) {
|
||||||
|
this(notNull(cache, "cache"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||||
|
private boolean fillBuffer() throws IOException {
|
||||||
|
byteBuffer.rewind();
|
||||||
|
int length = channel.read(byteBuffer);
|
||||||
|
bufferPos = 0;
|
||||||
|
bufferLimit = max(length, 0);
|
||||||
|
|
||||||
|
return bufferLimit > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean bufferEmpty() {
|
||||||
|
return bufferPos >= bufferLimit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setByteOrder(ByteOrder byteOrder) {
|
||||||
|
super.setByteOrder(byteOrder);
|
||||||
|
integralCache.order(byteOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (bufferEmpty() && !fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
streamPos++;
|
||||||
|
|
||||||
|
return buffer[bufferPos++] & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
if (bufferEmpty()) {
|
||||||
|
// Bypass buffer if buffer is empty for reads longer than buffer
|
||||||
|
if (length >= buffer.length) {
|
||||||
|
return readDirect(bytes, offset, length);
|
||||||
|
}
|
||||||
|
else if (!fillBuffer()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fromBuffer = readBuffered(bytes, offset, length);
|
||||||
|
|
||||||
|
if (length > fromBuffer) {
|
||||||
|
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
|
||||||
|
// we'll read as much as possible from the buffer, and the rest directly after
|
||||||
|
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fromBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||||
|
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
|
||||||
|
bufferLimit = 0;
|
||||||
|
|
||||||
|
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
|
||||||
|
int read = 0;
|
||||||
|
while (wrapped.hasRemaining()) {
|
||||||
|
int count = channel.read(wrapped);
|
||||||
|
if (count == -1) {
|
||||||
|
if (read == 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
read += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos += read;
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBuffered(final byte[] bytes, final int offset, final int length) {
|
||||||
|
// Read as much as possible from buffer
|
||||||
|
int available = Math.min(bufferLimit - bufferPos, length);
|
||||||
|
|
||||||
|
if (available > 0) {
|
||||||
|
System.arraycopy(buffer, bufferPos, bytes, offset, available);
|
||||||
|
bufferPos += available;
|
||||||
|
streamPos += available;
|
||||||
|
}
|
||||||
|
|
||||||
|
return available;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long length() {
|
||||||
|
// WTF?! This method is allowed to throw IOException in the interface...
|
||||||
|
try {
|
||||||
|
checkClosed();
|
||||||
|
return channel.size();
|
||||||
|
}
|
||||||
|
catch (IOException ignore) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
super.close();
|
||||||
|
|
||||||
|
buffer = null;
|
||||||
|
byteBuffer = null;
|
||||||
|
|
||||||
|
channel = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
closeable.close();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
closeable = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to override the readShort(), readInt() and readLong() methods,
|
||||||
|
// because the implementations in ImageInputStreamImpl expects the
|
||||||
|
// read(byte[], int, int) to always read the expected number of bytes,
|
||||||
|
// causing uninitialized values, alignment issues and EOFExceptions at
|
||||||
|
// random places...
|
||||||
|
// Notes:
|
||||||
|
// * readUnsignedXx() is covered by their signed counterparts
|
||||||
|
// * readChar() is covered by readShort()
|
||||||
|
// * readFloat() and readDouble() is covered by readInt() and readLong()
|
||||||
|
// respectively.
|
||||||
|
// * readLong() may be covered by two readInt()s, we'll override to be safe
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short readShort() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 2);
|
||||||
|
|
||||||
|
return integralCache.getShort(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readInt() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 4);
|
||||||
|
|
||||||
|
return integralCache.getInt(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readLong() throws IOException {
|
||||||
|
readFully(integralCacheArray, 0, 8);
|
||||||
|
|
||||||
|
return integralCache.getLong(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long position) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (position < flushedPos) {
|
||||||
|
throw new IndexOutOfBoundsException("position < flushedPos!");
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
if (streamPos == position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optimized to not invalidate buffer if new position is within current buffer
|
||||||
|
long newBufferPos = bufferPos + position - streamPos;
|
||||||
|
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
|
||||||
|
bufferPos = (int) newBufferPos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Will invalidate buffer
|
||||||
|
bufferLimit = 0;
|
||||||
|
channel.position(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushBefore(final long pos) throws IOException {
|
||||||
|
super.flushBefore(pos);
|
||||||
|
|
||||||
|
if (channel instanceof Cache) {
|
||||||
|
// In case of memory cache, free up memory
|
||||||
|
((Cache) channel).flushBefore(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-3
@@ -49,6 +49,7 @@ 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:
|
||||||
@@ -57,6 +58,7 @@ 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;
|
||||||
|
|
||||||
@@ -190,10 +192,10 @@ public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
|||||||
public void close() throws IOException {
|
public void close() throws IOException {
|
||||||
super.close();
|
super.close();
|
||||||
|
|
||||||
raf.close();
|
|
||||||
|
|
||||||
raf = null;
|
|
||||||
buffer = null;
|
buffer = null;
|
||||||
|
|
||||||
|
raf.close();
|
||||||
|
raf = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to override the readShort(), readInt() and readLong() methods,
|
// Need to override the readShort(), readInt() and readLong() methods,
|
||||||
|
|||||||
+9
-6
@@ -37,18 +37,19 @@ 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 class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
public BufferedFileImageInputStreamSpi() {
|
public BufferedFileImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
}
|
}
|
||||||
@@ -69,12 +70,13 @@ public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
|
@Override
|
||||||
|
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 BufferedFileImageInputStream((File) input);
|
return new BufferedChannelImageInputStream((File) input);
|
||||||
}
|
}
|
||||||
catch (FileNotFoundException e) {
|
catch (FileNotFoundException | NoSuchFileException 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,
|
||||||
@@ -91,7 +93,8 @@ public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
@Override
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||||
|
|
||||||
|
import javax.imageio.spi.ImageInputStreamSpi;
|
||||||
|
import javax.imageio.spi.ServiceRegistry;
|
||||||
|
import javax.imageio.stream.ImageInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BufferedInputStreamImageInputStreamSpi.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
|
||||||
|
*/
|
||||||
|
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
|
public BufferedInputStreamImageInputStreamSpi() {
|
||||||
|
this(new StreamProviderInfo());
|
||||||
|
}
|
||||||
|
|
||||||
|
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
|
||||||
|
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||||
|
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
|
||||||
|
|
||||||
|
while (providers.hasNext()) {
|
||||||
|
ImageInputStreamSpi provider = providers.next();
|
||||||
|
if (provider != this) {
|
||||||
|
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||||
|
if (input instanceof InputStream) {
|
||||||
|
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
|
||||||
|
|
||||||
|
if (channel instanceof SeekableByteChannel) {
|
||||||
|
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
|
||||||
|
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, create a cache for backwards seeking
|
||||||
|
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canUseCacheFile() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDescription(final Locale locale) {
|
||||||
|
return "Service provider that instantiates an ImageInputStream from an InputStream";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class InputStreamFilter implements ServiceRegistry.Filter {
|
||||||
|
@Override
|
||||||
|
public boolean filter(final Object provider) {
|
||||||
|
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-4
@@ -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 class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
public BufferedRAFImageInputStreamSpi() {
|
public BufferedRAFImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
}
|
}
|
||||||
@@ -69,9 +69,10 @@ public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
|
@Override
|
||||||
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
|
||||||
if (input instanceof RandomAccessFile) {
|
if (input instanceof RandomAccessFile) {
|
||||||
return new BufferedFileImageInputStream((RandomAccessFile) input);
|
return new BufferedChannelImageInputStream((RandomAccessFile) input);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
|
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
|
||||||
@@ -82,7 +83,8 @@ public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
@Override
|
||||||
|
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";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
|
|||||||
private final int dataOffset;
|
private final int dataOffset;
|
||||||
private final int dataLength;
|
private final int dataLength;
|
||||||
|
|
||||||
public ByteArrayImageInputStream(final byte[] pData) {
|
public ByteArrayImageInputStream(final byte[] data) {
|
||||||
this(pData, 0, pData != null ? pData.length : -1);
|
this(data, 0, data != null ? data.length : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
|
public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
|
||||||
data = notNull(pData, "data");
|
this.data = notNull(data, "data");
|
||||||
dataOffset = isBetween(0, pData.length, offset, "offset");
|
dataOffset = isMax(data.length, offset, "offset");
|
||||||
dataLength = isBetween(0, pData.length - offset, length, "length");
|
dataLength = isMax(data.length - offset, length, "length");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int isBetween(final int low, final int high, final int value, final String name) {
|
private static int isMax(final int high, final int value, final String name) {
|
||||||
return isTrue(value >= low && value <= high, value, String.format("%s out of range [%d, %d]: %d", name, low, high, value));
|
return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
|
|||||||
return data[((int) streamPos++) + dataOffset] & 0xff;
|
return data[((int) streamPos++) + dataOffset] & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
public int read(byte[] buffer, int offset, int len) throws IOException {
|
||||||
if (streamPos >= dataLength) {
|
if (streamPos >= dataLength) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int length = (int) Math.min(this.dataLength - streamPos, pLength);
|
int length = (int) Math.min(dataLength - streamPos, len);
|
||||||
bitOffset = 0;
|
bitOffset = 0;
|
||||||
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
|
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
|
||||||
streamPos += length;
|
streamPos += length;
|
||||||
|
|
||||||
return length;
|
return length;
|
||||||
|
|||||||
+9
-8
@@ -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 class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
|
|
||||||
public ByteArrayImageInputStreamSpi() {
|
public ByteArrayImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
@@ -55,16 +55,17 @@ public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
|
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
|
@Override
|
||||||
if (pInput instanceof byte[]) {
|
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) {
|
||||||
return new ByteArrayImageInputStream((byte[]) pInput);
|
if (input instanceof byte[]) {
|
||||||
}
|
return new ByteArrayImageInputStream((byte[]) input);
|
||||||
else {
|
|
||||||
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new IllegalArgumentException("Expected input of type byte[]: " + input);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(Locale pLocale) {
|
@Override
|
||||||
|
public String getDescription(Locale locale) {
|
||||||
return "Service provider that instantiates an ImageInputStream from a byte array";
|
return "Service provider that instantiates an ImageInputStream from a byte array";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
|
||||||
|
interface Cache extends SeekableByteChannel {
|
||||||
|
void flushBefore(long pos);
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2022, Harald Kuhr
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
* list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* * Neither the name of the copyright holder nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import javax.imageio.stream.ImageInputStreamImpl;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@code ImageInputStream} that adapts an {@code InputSteam},
|
||||||
|
* by reading directly from the stream without and form of caching or buffering.
|
||||||
|
* <p>
|
||||||
|
* Note: This is <em>not</em> a general-purpose {@code ImageInputStream}, and is designed for reading large chunks,
|
||||||
|
* typically of pixel data, from an {@code InputStream}.
|
||||||
|
* It does <em>not</em> support backwards seeking, or reading bits.
|
||||||
|
* </p>
|
||||||
|
*/
|
||||||
|
public final class DirectImageInputStream extends ImageInputStreamImpl {
|
||||||
|
private final InputStream stream;
|
||||||
|
private final long length;
|
||||||
|
|
||||||
|
public DirectImageInputStream(final InputStream stream) {
|
||||||
|
this(stream, -1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DirectImageInputStream(final InputStream stream, long length) {
|
||||||
|
this.stream = notNull(stream, "stream");
|
||||||
|
this.length = isTrue(length >= 0L || length == -1L, length, "negative length: %d");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
bitOffset = 0;
|
||||||
|
streamPos++;
|
||||||
|
return stream.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(final byte[] bytes, int off, int len) throws IOException {
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
int read = stream.read(bytes, off, len);
|
||||||
|
if (read > 0) {
|
||||||
|
streamPos += read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seek(long pos) throws IOException {
|
||||||
|
checkClosed();
|
||||||
|
|
||||||
|
if (pos < streamPos) {
|
||||||
|
// Handle as if flushedPos == streamPos at any time
|
||||||
|
throw new IndexOutOfBoundsException("pos < flushedPos");
|
||||||
|
}
|
||||||
|
|
||||||
|
bitOffset = 0;
|
||||||
|
|
||||||
|
while (streamPos < pos) {
|
||||||
|
long skipped = stream.skip(pos - streamPos);
|
||||||
|
|
||||||
|
if (skipped <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
streamPos += skipped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getFlushedPosition() {
|
||||||
|
// Handle as if flushedPos == streamPos at any time
|
||||||
|
return streamPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long length() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows")
|
||||||
|
@Override
|
||||||
|
public int readBit() throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Bit reading not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantThrows")
|
||||||
|
@Override
|
||||||
|
public long readBits(int numBits) throws IOException {
|
||||||
|
throw new UnsupportedOperationException("Bit reading not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
// We could seek to EOF here, but the usual case is we know where the next chunk of data is
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
super.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.nio.channels.NonWritableChannelException;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.max;
|
||||||
|
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
|
||||||
|
import static java.nio.file.StandardOpenOption.READ;
|
||||||
|
import static java.nio.file.StandardOpenOption.WRITE;
|
||||||
|
|
||||||
|
// Note: We could consider creating a memory-mapped version...
|
||||||
|
// But, from java.nio.channels.FileChannel.map:
|
||||||
|
// For most operating systems, mapping a file into memory is more
|
||||||
|
// expensive than reading or writing a few tens of kilobytes of data via
|
||||||
|
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||||
|
// standpoint of performance it is generally only worth mapping relatively
|
||||||
|
// large files into memory.
|
||||||
|
final class FileCache implements Cache {
|
||||||
|
final static int BLOCK_SIZE = 1 << 13;
|
||||||
|
|
||||||
|
private final FileChannel cache;
|
||||||
|
private final ReadableByteChannel channel;
|
||||||
|
|
||||||
|
// TODO: Perhaps skip this constructor?
|
||||||
|
FileCache(InputStream stream, File cacheDir) throws IOException {
|
||||||
|
// Stream will be closed with channel, documented behavior
|
||||||
|
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
|
||||||
|
|
||||||
|
// Create a temp file to hold our cache,
|
||||||
|
// will be deleted when this channel is closed, as we close the cache
|
||||||
|
Path cacheFile = cacheDir == null
|
||||||
|
? Files.createTempFile("imageio", ".tmp")
|
||||||
|
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
|
||||||
|
|
||||||
|
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("StatementWithEmptyBody")
|
||||||
|
void fetch() throws IOException {
|
||||||
|
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
|
||||||
|
// Continue transfer...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return channel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
cache.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ByteBuffer dest) throws IOException {
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
if (cache.position() >= cache.size()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.read(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long position() throws IOException {
|
||||||
|
return cache.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||||
|
cache.position(newPosition);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long size() {
|
||||||
|
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int write(ByteBuffer src) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel truncate(long size) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void flushBefore(long pos) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
package com.twelvemonkeys.imageio.stream;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.Channels;
|
||||||
|
import java.nio.channels.NonWritableChannelException;
|
||||||
|
import java.nio.channels.ReadableByteChannel;
|
||||||
|
import java.nio.channels.SeekableByteChannel;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
|
final class MemoryCache implements Cache {
|
||||||
|
|
||||||
|
final static int BLOCK_SIZE = 1 << 13;
|
||||||
|
|
||||||
|
private static final byte[] NULL_BLOCK = new byte[0];
|
||||||
|
|
||||||
|
private final List<byte[]> cache = new ArrayList<>();
|
||||||
|
private final ReadableByteChannel channel;
|
||||||
|
|
||||||
|
private int maxBlock = Integer.MAX_VALUE;
|
||||||
|
private long length;
|
||||||
|
private long position;
|
||||||
|
private long start;
|
||||||
|
|
||||||
|
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
|
||||||
|
MemoryCache(InputStream stream) {
|
||||||
|
this(Channels.newChannel(notNull(stream, "stream")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MemoryCache(ReadableByteChannel channel) {
|
||||||
|
this.channel = notNull(channel, "channel");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] fetchBlock() throws IOException {
|
||||||
|
long currPos = position;
|
||||||
|
long index = currPos / BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (index >= Integer.MAX_VALUE) {
|
||||||
|
throw new IOException("Memory cache max size exceeded");
|
||||||
|
}
|
||||||
|
if (index > maxBlock) {
|
||||||
|
return NULL_BLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index >= cache.size()) {
|
||||||
|
byte[] block;
|
||||||
|
try {
|
||||||
|
block = new byte[BLOCK_SIZE];
|
||||||
|
}
|
||||||
|
catch (OutOfMemoryError e) {
|
||||||
|
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
cache.add(block);
|
||||||
|
int bytesRead = readBlock(block);
|
||||||
|
length += bytesRead;
|
||||||
|
|
||||||
|
if (bytesRead < BLOCK_SIZE) {
|
||||||
|
// Last block, EOF found
|
||||||
|
maxBlock = (int) index;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cache.get((int) index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int readBlock(final byte[] block) throws IOException {
|
||||||
|
ByteBuffer wrapped = ByteBuffer.wrap(block);
|
||||||
|
|
||||||
|
while (wrapped.hasRemaining()) {
|
||||||
|
int count = channel.read(wrapped);
|
||||||
|
if (count == -1) {
|
||||||
|
// Last block, EOF found
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapped.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return channel.isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(ByteBuffer dest) throws IOException {
|
||||||
|
byte[] buffer = fetchBlock();
|
||||||
|
|
||||||
|
if (position >= length) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bufferPos = (int) (position % BLOCK_SIZE);
|
||||||
|
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
|
||||||
|
dest.put(buffer, bufferPos, len);
|
||||||
|
|
||||||
|
position += len;
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long position() throws IOException {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||||
|
if (newPosition < start) {
|
||||||
|
throw new IOException("Seek before flush position");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.position = newPosition;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long size() throws IOException {
|
||||||
|
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int write(ByteBuffer src) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SeekableByteChannel truncate(long size) {
|
||||||
|
throw new NonWritableChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flushBefore(long pos) {
|
||||||
|
if (pos < start) {
|
||||||
|
throw new IndexOutOfBoundsException("pos < flushed position");
|
||||||
|
}
|
||||||
|
if (pos > position) {
|
||||||
|
throw new IndexOutOfBoundsException("pos > current position");
|
||||||
|
}
|
||||||
|
|
||||||
|
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
|
||||||
|
|
||||||
|
// Clear blocks no longer needed
|
||||||
|
for (int i = 0; i < blocks; i++) {
|
||||||
|
cache.set(i, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
start = pos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
+21
-21
@@ -53,20 +53,20 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
|||||||
/**
|
/**
|
||||||
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
|
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
|
||||||
*
|
*
|
||||||
* @param pStream the underlying stream
|
* @param stream the underlying stream
|
||||||
* @param pLength the maximum length to read from the stream.
|
* @param length the maximum length to read from the stream.
|
||||||
* Note that {@code pStream} may contain less than this maximum number of bytes.
|
* Note that {@code stream} may contain less than this maximum number of bytes.
|
||||||
*
|
*
|
||||||
* @throws IOException if {@code pStream}'s position can't be determined.
|
* @throws IOException if {@code stream}'s position can't be determined.
|
||||||
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
|
* @throws IllegalArgumentException if {@code stream == null} or {@code length < 0}
|
||||||
*/
|
*/
|
||||||
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
|
public SubImageInputStream(final ImageInputStream stream, final long length) throws IOException {
|
||||||
Validate.notNull(pStream, "stream");
|
Validate.notNull(stream, "stream");
|
||||||
Validate.isTrue(pLength >= 0, pLength, "length < 0: %d");
|
Validate.isTrue(length >= 0, length, "length < 0: %d");
|
||||||
|
|
||||||
stream = pStream;
|
this.stream = stream;
|
||||||
startPos = pStream.getStreamPosition();
|
this.startPos = stream.getStreamPosition();
|
||||||
length = pLength;
|
this.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read() throws IOException {
|
public int read() throws IOException {
|
||||||
@@ -84,14 +84,14 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
public int read(final byte[] bytes, final int off, final int len) throws IOException {
|
||||||
if (streamPos >= length) { // Local EOF
|
if (streamPos >= length) { // Local EOF
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safe cast, as pLength can never cause int overflow
|
// Safe cast, as len can never cause int overflow
|
||||||
int length = (int) Math.min(pLength, this.length - streamPos);
|
int length = (int) Math.min(len, this.length - streamPos);
|
||||||
int count = stream.read(pBytes, pOffset, length);
|
int count = stream.read(bytes, off, length);
|
||||||
|
|
||||||
if (count >= 0) {
|
if (count >= 0) {
|
||||||
streamPos += count;
|
streamPos += count;
|
||||||
@@ -113,18 +113,18 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void seek(final long pPosition) throws IOException {
|
public void seek(final long position) throws IOException {
|
||||||
if (pPosition < getFlushedPosition()) {
|
if (position < getFlushedPosition()) {
|
||||||
throw new IndexOutOfBoundsException("pos < flushedPosition");
|
throw new IndexOutOfBoundsException("pos < flushedPosition");
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.seek(startPos + pPosition);
|
stream.seek(startPos + position);
|
||||||
streamPos = pPosition;
|
streamPos = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
|
@SuppressWarnings("MethodDoesntCallSuperMethod")
|
||||||
@Override
|
@Override
|
||||||
protected void finalize() throws Throwable {
|
protected void finalize() {
|
||||||
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
|
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-41
@@ -33,9 +33,7 @@ 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;
|
||||||
@@ -52,7 +50,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 class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||||
public URLImageInputStreamSpi() {
|
public URLImageInputStreamSpi() {
|
||||||
this(new StreamProviderInfo());
|
this(new StreamProviderInfo());
|
||||||
}
|
}
|
||||||
@@ -64,53 +62,28 @@ public 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..
|
||||||
public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
|
@Override
|
||||||
if (pInput instanceof URL) {
|
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||||
URL url = (URL) pInput;
|
if (input instanceof URL) {
|
||||||
|
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 BufferedFileImageInputStream(new File(url.toURI()));
|
return new BufferedChannelImageInputStream(new File(url.toURI()));
|
||||||
}
|
}
|
||||||
catch (URISyntaxException ignore) {
|
catch (URISyntaxException shouldNeverHappen) {
|
||||||
// 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
|
||||||
ignore.printStackTrace();
|
shouldNeverHappen.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise revert to cached
|
// Otherwise revert to cached
|
||||||
final InputStream urlStream = url.openStream();
|
InputStream urlStream = url.openStream();
|
||||||
if (pUseCache) {
|
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
|
||||||
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
|
||||||
@@ -118,7 +91,7 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDescription(final Locale pLocale) {
|
public String getDescription(final Locale locale) {
|
||||||
return "Service provider that instantiates an ImageInputStream from a URL";
|
return "Service provider that instantiates an ImageInputStream from a URL";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -81,7 +81,7 @@ class IIOInputStreamAdapter extends InputStream {
|
|||||||
|
|
||||||
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
|
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
|
||||||
Validate.notNull(pInput, "stream");
|
Validate.notNull(pInput, "stream");
|
||||||
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %f");
|
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %d");
|
||||||
|
|
||||||
input = pInput;
|
input = pInput;
|
||||||
left = pLength;
|
left = pLength;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import java.io.BufferedInputStream;
|
|||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
|
|
||||||
@@ -175,15 +176,22 @@ public final class IIOUtil {
|
|||||||
* @param providerClassName name of the provider class.
|
* @param providerClassName name of the provider class.
|
||||||
* @param category provider category
|
* @param category provider category
|
||||||
*
|
*
|
||||||
* @return the provider instance, or {@code null}.
|
* @return the provider instance, or {@code null} if not found
|
||||||
*/
|
*/
|
||||||
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
|
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
|
||||||
try {
|
// NOTE: While more verbose, this is more OSGi-friendly than using
|
||||||
return category.cast(registry.getServiceProviderByClass(Class.forName(providerClassName)));
|
// registry.getServiceProviderByClass(Class.forName(providerClassName))
|
||||||
}
|
Iterator<T> providers = registry.getServiceProviders(category, true);
|
||||||
catch (ClassNotFoundException ignore) {
|
|
||||||
return null;
|
while (providers.hasNext()) {
|
||||||
|
T provider = providers.next();
|
||||||
|
|
||||||
|
if (provider.getClass().getName().equals(providerClassName)) {
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+99
-46
@@ -30,21 +30,14 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.util;
|
package com.twelvemonkeys.imageio.util;
|
||||||
|
|
||||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
|
||||||
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 javax.imageio.ImageTypeSpecifier;
|
||||||
|
import java.awt.color.*;
|
||||||
|
import java.awt.image.*;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
|
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||||
|
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class for creating {@code ImageTypeSpecifier}s.
|
* Factory class for creating {@code ImageTypeSpecifier}s.
|
||||||
@@ -58,28 +51,52 @@ import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
|
|||||||
*/
|
*/
|
||||||
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 USHORT types
|
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types
|
||||||
|
case BufferedImage.TYPE_INT_RGB:
|
||||||
|
return TYPE_INT_RGB;
|
||||||
|
|
||||||
|
case BufferedImage.TYPE_INT_BGR:
|
||||||
|
return TYPE_INT_BGR;
|
||||||
|
|
||||||
case BufferedImage.TYPE_USHORT_565_RGB:
|
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||||
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
return TYPE_USHORT_565_RGB;
|
||||||
0xF800,
|
|
||||||
0x07E0,
|
|
||||||
0x001F,
|
|
||||||
0x0,
|
|
||||||
DataBuffer.TYPE_USHORT,
|
|
||||||
false);
|
|
||||||
|
|
||||||
case BufferedImage.TYPE_USHORT_555_RGB:
|
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||||
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
return TYPE_USHORT_555_RGB;
|
||||||
0x7C00,
|
|
||||||
0x03E0,
|
|
||||||
0x001F,
|
|
||||||
0x0,
|
|
||||||
DataBuffer.TYPE_USHORT,
|
|
||||||
false);
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,23 +107,41 @@ 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) {
|
||||||
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
|
int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask);
|
||||||
|
if (bits != 32) {
|
||||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
|
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
|
||||||
notNull(colorSpace, "colorSpace");
|
return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||||
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,
|
||||||
@@ -222,17 +257,35 @@ 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 pColorModel) {
|
public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel colorModel) {
|
||||||
return new IndexedImageTypeSpecifier(pColorModel);
|
return new IndexedImageTypeSpecifier(colorModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) {
|
public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel colorModel) {
|
||||||
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel);
|
ColorModel discreteAlphaIndexColorModel = new DiscreteAlphaIndexColorModel(colorModel);
|
||||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
return new ImageTypeSpecifier(discreteAlphaIndexColorModel, discreteAlphaIndexColorModel.createCompatibleSampleModel(1, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel pColorModel, int extraSamples, boolean hasAlpha) {
|
public static ImageTypeSpecifier createDiscreteExtraSamplesIndexedFromIndexColorModel(final IndexColorModel colorModel, int extraSamples, boolean hasAlpha) {
|
||||||
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
|
ColorModel discreteAlphaIndexColorModel = new DiscreteAlphaIndexColorModel(colorModel, extraSamples, hasAlpha);
|
||||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
return new ImageTypeSpecifier(discreteAlphaIndexColorModel, discreteAlphaIndexColorModel.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -52,12 +52,12 @@ final class IndexedImageTypeSpecifier extends ImageTypeSpecifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) {
|
public BufferedImage createBufferedImage(final int width, final int height) {
|
||||||
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(pWidth, pHeight);
|
WritableRaster raster = colorModel.createCompatibleWritableRaster(width, height);
|
||||||
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
|
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
|
||||||
}
|
}
|
||||||
catch (NegativeArraySizeException e) {
|
catch (NegativeArraySizeException e) {
|
||||||
|
|||||||
+1
@@ -1,4 +1,5 @@
|
|||||||
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
|
||||||
|
|||||||
+333
@@ -0,0 +1,333 @@
|
|||||||
|
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()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
-1
@@ -37,7 +37,9 @@ import java.awt.image.*;
|
|||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
public class DiscreteAlphaIndexColorModelTest {
|
public class DiscreteAlphaIndexColorModelTest {
|
||||||
|
|
||||||
@@ -204,6 +206,25 @@ public class DiscreteAlphaIndexColorModelTest {
|
|||||||
assertThat(raster.getTransferType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE));
|
assertThat(raster.getTransferType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNumComponents() {
|
||||||
|
int[] colors = createIntLut(1 << 8);
|
||||||
|
IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||||
|
|
||||||
|
ColorModel colorModelDiscreteAlpha = new DiscreteAlphaIndexColorModel(icm, 1, true);
|
||||||
|
ColorModel colorModelDiscreteAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 2, true);
|
||||||
|
ColorModel colorModelNoAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 42, false);
|
||||||
|
|
||||||
|
assertEquals(3, colorModelDiscreteAlpha.getNumColorComponents());
|
||||||
|
assertEquals(4, colorModelDiscreteAlpha.getNumComponents());
|
||||||
|
|
||||||
|
assertEquals(3, colorModelDiscreteAlphaExtra.getNumColorComponents());
|
||||||
|
assertEquals(5, colorModelDiscreteAlphaExtra.getNumComponents()); // Questionable
|
||||||
|
|
||||||
|
assertEquals(3, colorModelNoAlphaExtra.getNumColorComponents());
|
||||||
|
assertEquals(45, colorModelNoAlphaExtra.getNumComponents()); // Questionable
|
||||||
|
}
|
||||||
|
|
||||||
private static int[] createIntLut(final int count) {
|
private static int[] createIntLut(final int count) {
|
||||||
int[] lut = new int[count];
|
int[] lut = new int[count];
|
||||||
|
|
||||||
|
|||||||
+15
@@ -36,9 +36,11 @@ import java.awt.color.ColorSpace;
|
|||||||
import java.awt.color.ICC_ColorSpace;
|
import java.awt.color.ICC_ColorSpace;
|
||||||
import java.awt.color.ICC_Profile;
|
import java.awt.color.ICC_Profile;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import static org.junit.Assert.assertArrayEquals;
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assume.assumeFalse;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
public class KCMSSanitizerStrategyTest {
|
public class KCMSSanitizerStrategyTest {
|
||||||
@@ -56,6 +58,8 @@ public class KCMSSanitizerStrategyTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFixProfileUpdateHeader() throws Exception {
|
public void testFixProfileUpdateHeader() throws Exception {
|
||||||
|
assumeICC_ProfileNotSealed(); // Ignores test for JDK 19+
|
||||||
|
|
||||||
byte[] header = new byte[128];
|
byte[] header = new byte[128];
|
||||||
header[ICC_Profile.icHdrRenderingIntent + 3] = 1;
|
header[ICC_Profile.icHdrRenderingIntent + 3] = 1;
|
||||||
ICC_Profile profile = mock(ICC_Profile.class);
|
ICC_Profile profile = mock(ICC_Profile.class);
|
||||||
@@ -69,6 +73,17 @@ public class KCMSSanitizerStrategyTest {
|
|||||||
verify(profile).setData(eq(ICC_Profile.icSigHead), any(byte[].class));
|
verify(profile).setData(eq(ICC_Profile.icSigHead), any(byte[].class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void assumeICC_ProfileNotSealed() {
|
||||||
|
try {
|
||||||
|
Method isSealed = Class.class.getMethod("isSealed");
|
||||||
|
Boolean result = (Boolean) isSealed.invoke(ICC_Profile.class);
|
||||||
|
assumeFalse("Can't mock ICC_Profile, class is sealed (as of JDK 19).", result);
|
||||||
|
}
|
||||||
|
catch (ReflectiveOperationException ignore) {
|
||||||
|
// We can't have sealed classes if we don't have the isSealed method...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFixProfileCorbisRGB() throws IOException {
|
public void testFixProfileCorbisRGB() throws IOException {
|
||||||
// TODO: Consider re-writing this using mocks, to avoid dependencies on the CMS implementation
|
// TODO: Consider re-writing this using mocks, to avoid dependencies on the CMS implementation
|
||||||
|
|||||||
+3
@@ -34,6 +34,7 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.awt.color.ICC_Profile;
|
import java.awt.color.ICC_Profile;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.color.KCMSSanitizerStrategyTest.assumeICC_ProfileNotSealed;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
@@ -46,6 +47,8 @@ public class LCMSSanitizerStrategyTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testFixProfile() throws Exception {
|
public void testFixProfile() throws Exception {
|
||||||
|
assumeICC_ProfileNotSealed(); // Ignores test for JDK 19+
|
||||||
|
|
||||||
ICC_Profile profile = mock(ICC_Profile.class);
|
ICC_Profile profile = mock(ICC_Profile.class);
|
||||||
new LCMSSanitizerStrategy().fixProfile(profile);
|
new LCMSSanitizerStrategy().fixProfile(profile);
|
||||||
|
|
||||||
|
|||||||
+434
@@ -0,0 +1,434 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+452
@@ -0,0 +1,452 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+431
@@ -0,0 +1,431 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-1
@@ -45,7 +45,9 @@ 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.*;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.only;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BufferedFileImageInputStreamTestCase
|
* BufferedFileImageInputStreamTestCase
|
||||||
@@ -54,6 +56,7 @@ import static org.mockito.Mockito.*;
|
|||||||
* @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);
|
||||||
|
|
||||||
@@ -72,6 +75,7 @@ public class BufferedFileImageInputStreamTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("resource")
|
||||||
@Test
|
@Test
|
||||||
public void testCreateNullFile() throws IOException {
|
public void testCreateNullFile() throws IOException {
|
||||||
try {
|
try {
|
||||||
|
|||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -53,6 +53,7 @@ import static org.mockito.Mockito.*;
|
|||||||
* @author last modified by $Author: haraldk$
|
* @author last modified by $Author: haraldk$
|
||||||
* @version $Id: BufferedImageInputStreamTest.java,v 1.0 Jun 30, 2008 3:07:42 PM haraldk Exp$
|
* @version $Id: BufferedImageInputStreamTest.java,v 1.0 Jun 30, 2008 3:07:42 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
public class BufferedImageInputStreamTest {
|
public class BufferedImageInputStreamTest {
|
||||||
private final Random random = new Random(3450972865211L);
|
private final Random random = new Random(3450972865211L);
|
||||||
|
|
||||||
@@ -433,7 +434,7 @@ public class BufferedImageInputStreamTest {
|
|||||||
* and {@code pFirstOffset == pSecondOffset}.
|
* and {@code pFirstOffset == pSecondOffset}.
|
||||||
* Otherwise {@code false}.
|
* Otherwise {@code false}.
|
||||||
*/
|
*/
|
||||||
static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
|
public static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
|
||||||
if (pFirst == pSecond && pFirstOffset == pSecondOffset) {
|
if (pFirst == pSecond && pFirstOffset == pSecondOffset) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Executable
+380
@@ -0,0 +1,380 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.Ignore;
|
||||||
|
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.util.Random;
|
||||||
|
|
||||||
|
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NonSeekableImageInputStreamTest
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||||
|
* @author last modified by $Author: haraldk$
|
||||||
|
* @version $Id: NonSeekableImageInputStreamTest.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||||
|
*/
|
||||||
|
public class DirectImageInputStreamTest {
|
||||||
|
private final Random random = new Random(170984354357234566L);
|
||||||
|
|
||||||
|
private InputStream randomData(byte[] data) {
|
||||||
|
random.nextBytes(data);
|
||||||
|
|
||||||
|
return new ByteArrayInputStream(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreate() throws IOException {
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(new byte[0]), 0)) {
|
||||||
|
assertEquals("Data length should be same as stream length", 0, stream.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateNullFile() throws IOException {
|
||||||
|
try (@SuppressWarnings("unused") DirectImageInputStream stream = new DirectImageInputStream(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 testRead() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 1024];
|
||||||
|
InputStream input = randomData(data);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
|
||||||
|
for (byte value : data) {
|
||||||
|
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadArray() throws IOException {
|
||||||
|
byte[] data = new byte[1024 * 10];
|
||||||
|
InputStream input = randomData(data);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
|
||||||
|
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];
|
||||||
|
InputStream input = randomData(data);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
|
||||||
|
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[24 * 18];
|
||||||
|
InputStream input = randomData(data);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
|
||||||
|
byte[] result = new byte[9];
|
||||||
|
|
||||||
|
for (int i = 0; i < data.length / (2 * result.length); i++) {
|
||||||
|
long newPos = i * 2 * 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Ignore("Bit reading requires backwards seek or buffer...")
|
||||||
|
@Test
|
||||||
|
public void testReadBitRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomData(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
|
||||||
|
for (int i = 1; i <= 64; i++) {
|
||||||
|
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Ignore("Bit reading requires backwards seek or buffer...")
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandom() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomData(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@Ignore("Bit reading requires backwards seek or buffer...")
|
||||||
|
@Test
|
||||||
|
public void testReadBitsRandomOffset() throws IOException {
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
InputStream input = randomData(bytes);
|
||||||
|
long value = ByteBuffer.wrap(bytes).getLong();
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(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 * 2L % 8, stream.getBitOffset());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadShort() throws IOException {
|
||||||
|
byte[] bytes = new byte[31];
|
||||||
|
InputStream input = randomData(bytes);
|
||||||
|
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
|
||||||
|
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[31];
|
||||||
|
InputStream input = randomData(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
// Create stream
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
|
||||||
|
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 = randomData(bytes);
|
||||||
|
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
|
||||||
|
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 = randomData(bytes);
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
|
||||||
|
for (byte value : bytes) {
|
||||||
|
assertEquals(value, stream.readByte());
|
||||||
|
}
|
||||||
|
|
||||||
|
assertEquals(-1, stream.read());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testClose() throws IOException {
|
||||||
|
// Create wrapper stream
|
||||||
|
InputStream input = mock(InputStream.class);
|
||||||
|
ImageInputStream stream = new DirectImageInputStream(input);
|
||||||
|
|
||||||
|
stream.close();
|
||||||
|
verify(input, only()).close();
|
||||||
|
}
|
||||||
|
}
|
||||||
+44
-39
@@ -31,6 +31,7 @@
|
|||||||
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;
|
||||||
@@ -38,14 +39,18 @@ 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.*;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.imageio.ImageReadParam;
|
||||||
|
import javax.imageio.ImageReader;
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.event.IIOReadProgressListener;
|
import javax.imageio.event.IIOReadProgressListener;
|
||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.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.AffineTransform;
|
import java.awt.geom.*;
|
||||||
import java.awt.image.*;
|
import java.awt.image.*;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -53,6 +58,7 @@ 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;
|
||||||
|
|
||||||
@@ -602,7 +608,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, final int imageIndex) throws IOException {
|
protected void assertReadWithSourceRegionParamEqualImage(final Rectangle r, final TestData data, @SuppressWarnings("SameParameterValue") 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);
|
||||||
@@ -1828,52 +1834,51 @@ 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 pInput, final Dimension... pSizes) {
|
public TestData(final Object input, final Dimension... dimensions) {
|
||||||
this(pInput, Arrays.asList(pSizes), null);
|
this(input, Arrays.asList(dimensions), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestData(final Object pInput, final BufferedImage... pImages) {
|
public TestData(final Object input, final BufferedImage... images) {
|
||||||
this(pInput, null, Arrays.asList(pImages));
|
this(input, null, Arrays.asList(images));
|
||||||
}
|
}
|
||||||
|
|
||||||
public TestData(final Object pInput, final List<Dimension> pSizes, final List<BufferedImage> pImages) {
|
public TestData(final Object input, final List<Dimension> dimensions, final List<BufferedImage> images) {
|
||||||
if (pInput == null) {
|
Validate.notNull(input, "input");
|
||||||
throw new IllegalArgumentException("input == null");
|
Validate.isTrue(dimensions != null || images != null, "Need either dimensions or image");
|
||||||
}
|
|
||||||
|
|
||||||
sizes = new ArrayList<>();
|
List<Dimension> combinedDimensions;
|
||||||
images = new ArrayList<>();
|
if (dimensions == null) {
|
||||||
|
// Copy dimensions from images
|
||||||
|
combinedDimensions = new ArrayList<>(images.size());
|
||||||
|
|
||||||
List<Dimension> sizes = pSizes;
|
for (BufferedImage image : images) {
|
||||||
if (sizes == null) {
|
combinedDimensions.add(new Dimension(image.getWidth(), image.getHeight()));
|
||||||
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) {
|
else {
|
||||||
if (pImages.size() != pSizes.size()) {
|
// Validate equal dimensions
|
||||||
throw new IllegalArgumentException("Size parameter and image size differs");
|
if (images != null) {
|
||||||
}
|
if (images.size() != dimensions.size()) {
|
||||||
for (int i = 0; i < sizes.size(); i++) {
|
throw new IllegalArgumentException("Dimensions and images parameter's size differs");
|
||||||
if (!new Dimension(pImages.get(i).getWidth(), pImages.get(i).getHeight()).equals(sizes.get(i))) {
|
|
||||||
throw new IllegalArgumentException("Size parameter and image size differs");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < dimensions.size(); i++) {
|
||||||
|
if (!new Dimension(images.get(i).getWidth(), images.get(i).getHeight()).equals(dimensions.get(i))) {
|
||||||
|
throw new IllegalArgumentException("Dimensions and images parameter's dimensions differ");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
combinedDimensions = new ArrayList<>(dimensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sizes.addAll(sizes);
|
this.sizes = Collections.unmodifiableList(combinedDimensions);
|
||||||
if (pImages != null) {
|
|
||||||
images.addAll(pImages);
|
|
||||||
}
|
|
||||||
|
|
||||||
input = pInput;
|
this.images = images != null
|
||||||
|
? Collections.unmodifiableList(new ArrayList<>(images))
|
||||||
|
: Collections.<BufferedImage>emptyList();
|
||||||
|
|
||||||
|
this.input = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getInput() {
|
public Object getInput() {
|
||||||
@@ -1898,13 +1903,13 @@ public abstract class ImageReaderAbstractTest<T extends ImageReader> {
|
|||||||
return sizes.size();
|
return sizes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dimension getDimension(final int pIndex) {
|
public Dimension getDimension(final int index) {
|
||||||
return sizes.get(pIndex);
|
return sizes.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public BufferedImage getImage(final int pIndex) {
|
public BufferedImage getImage(final int index) {
|
||||||
return images.get(pIndex);
|
return images.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+103
-36
@@ -30,20 +30,16 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.util;
|
package com.twelvemonkeys.imageio.util;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import com.twelvemonkeys.lang.Validate;
|
||||||
|
|
||||||
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 com.twelvemonkeys.lang.Validate;
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
|
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 {
|
||||||
|
|
||||||
@@ -70,12 +66,19 @@ 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, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
|
expected = createPacked(sRGB, 16, 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, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
|
expected = createPacked(sRGB, 15, 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);
|
||||||
@@ -86,12 +89,24 @@ public class ImageTypeSpecifiersTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePacked32() {
|
public void testCreatePacked24() {
|
||||||
// TYPE_INT_RGB
|
// TYPE_INT_RGB
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
|
createPacked(sRGB, 24, 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),
|
||||||
@@ -102,35 +117,36 @@ 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
|
|
||||||
assertEquals(
|
|
||||||
ImageTypeSpecifier.createPacked(sRGB, 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)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testCreatePacked16() {
|
public void testCreatePacked15() {
|
||||||
// TYPE_USHORT_555_RGB
|
// TYPE_USHORT_555_RGB
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
createPacked(sRGB, 15, 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)
|
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)
|
// "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
|
||||||
|
public void testCreatePacked16() {
|
||||||
// TYPE_USHORT_565_RGB
|
// TYPE_USHORT_565_RGB
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
|
createPacked(sRGB, 16, 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, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
|
createPacked(sRGB, 16,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, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
|
createPacked(sRGB, 16, 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)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -142,17 +158,17 @@ public class ImageTypeSpecifiersTest {
|
|||||||
public void testCreatePacked8() {
|
public void testCreatePacked8() {
|
||||||
// "BYTE 332 RGB"
|
// "BYTE 332 RGB"
|
||||||
assertEquals(
|
assertEquals(
|
||||||
createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
|
createPacked(sRGB, 8, 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, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
|
createPacked(sRGB, 8, 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, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
|
createPacked(sRGB, 8, 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)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -160,15 +176,12 @@ 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,
|
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, final int bits,
|
||||||
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, "transferType: %s");
|
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT || transferType == DataBuffer.TYPE_INT, transferType, "transferType: %s");
|
||||||
|
|
||||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
|
||||||
|
|
||||||
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));
|
||||||
}
|
}
|
||||||
@@ -716,10 +729,63 @@ 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) count;
|
lut[i] = (byte) (i * 255 / count);
|
||||||
}
|
}
|
||||||
return lut;
|
return lut;
|
||||||
}
|
}
|
||||||
@@ -728,7 +794,8 @@ 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++) {
|
||||||
lut[i] = 0xff000000 | count << 16 | count << 8 | count;
|
int val = (i * 255 / count);
|
||||||
|
lut[i] = 0xff000000 | val << 16 | val << 8 | val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return lut;
|
return lut;
|
||||||
|
|||||||
+7
-5
@@ -43,15 +43,17 @@ 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.BufferedImage;
|
import java.awt.image.*;
|
||||||
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.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -108,8 +110,8 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
|
|||||||
return getTestData().get(index);
|
return getTestData().get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected URL getClassLoaderResource(final String pName) {
|
protected URL getClassLoaderResource(final String name) {
|
||||||
return getClass().getResource(pName);
|
return getClass().getResource(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||||
<artifactId>imageio</artifactId>
|
<artifactId>imageio</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.10.2-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<artifactId>imageio-hdr</artifactId>
|
<artifactId>imageio-hdr</artifactId>
|
||||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||||
@@ -32,4 +32,21 @@
|
|||||||
<artifactId>imageio-metadata</artifactId>
|
<artifactId>imageio-metadata</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.felix</groupId>
|
||||||
|
<artifactId>maven-bundle-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<instructions>
|
||||||
|
<Provide-Capability>
|
||||||
|
osgi.serviceloader;
|
||||||
|
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi
|
||||||
|
</Provide-Capability>
|
||||||
|
</instructions>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
+6
-12
@@ -40,11 +40,8 @@ import javax.imageio.ImageTypeSpecifier;
|
|||||||
import javax.imageio.metadata.IIOMetadata;
|
import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.spi.ImageReaderSpi;
|
import javax.imageio.spi.ImageReaderSpi;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.color.ColorSpace;
|
import java.awt.color.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.*;
|
||||||
import java.awt.image.DataBuffer;
|
|
||||||
import java.awt.image.Raster;
|
|
||||||
import java.awt.image.WritableRaster;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -62,7 +59,7 @@ public final class HDRImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
private HDRHeader header;
|
private HDRHeader header;
|
||||||
|
|
||||||
protected HDRImageReader(final ImageReaderSpi provider) {
|
HDRImageReader(final ImageReaderSpi provider) {
|
||||||
super(provider);
|
super(provider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +196,7 @@ public final class HDRImageReader extends ImageReaderBase {
|
|||||||
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
int ySub = param != null ? param.getSourceYSubsampling() : 1;
|
||||||
|
|
||||||
byte[] rowRGBE = new byte[width * 4];
|
byte[] rowRGBE = new byte[width * 4];
|
||||||
byte[] pixelRGBE = new byte[width];
|
byte[] pixelRGBE = new byte[4];
|
||||||
|
|
||||||
processImageStarted(imageIndex);
|
processImageStarted(imageIndex);
|
||||||
|
|
||||||
@@ -234,7 +231,7 @@ public final class HDRImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
processImageComplete();
|
processImageComplete();
|
||||||
|
|
||||||
return destination.getRaster();
|
return raster;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -244,10 +241,7 @@ public final class HDRImageReader extends ImageReaderBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
|
||||||
checkBounds(imageIndex);
|
return new HDRMetadata(getRawImageType(imageIndex), header);
|
||||||
readHeader();
|
|
||||||
|
|
||||||
return new HDRMetadata(header);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(final String[] args) throws IOException {
|
public static void main(final String[] args) throws IOException {
|
||||||
|
|||||||
+9
-107
@@ -1,83 +1,19 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2015, Harald Kuhr
|
|
||||||
* All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are met:
|
|
||||||
*
|
|
||||||
* * Redistributions of source code must retain the above copyright notice, this
|
|
||||||
* list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
* this list of conditions and the following disclaimer in the documentation
|
|
||||||
* and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* * Neither the name of the copyright holder nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
||||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.twelvemonkeys.imageio.plugins.hdr;
|
package com.twelvemonkeys.imageio.plugins.hdr;
|
||||||
|
|
||||||
import com.twelvemonkeys.imageio.AbstractMetadata;
|
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
|
||||||
|
|
||||||
|
import javax.imageio.ImageTypeSpecifier;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
|
|
||||||
final class HDRMetadata extends AbstractMetadata {
|
final class HDRMetadata extends StandardImageMetadataSupport {
|
||||||
private final HDRHeader header;
|
public HDRMetadata(ImageTypeSpecifier type, HDRHeader header) {
|
||||||
|
super(builder(type)
|
||||||
HDRMetadata(final HDRHeader header) {
|
.withCompressionTypeName("RLE")
|
||||||
this.header = header;
|
.withTextEntry("Software", header.getSoftware()));
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IIOMetadataNode getStandardChromaNode() {
|
|
||||||
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
|
|
||||||
|
|
||||||
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
|
|
||||||
chroma.appendChild(csType);
|
|
||||||
csType.setAttribute("name", "RGB");
|
|
||||||
// TODO: Support XYZ
|
|
||||||
|
|
||||||
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
|
|
||||||
numChannels.setAttribute("value", "3");
|
|
||||||
chroma.appendChild(numChannels);
|
|
||||||
|
|
||||||
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
|
|
||||||
blackIsZero.setAttribute("value", "TRUE");
|
|
||||||
chroma.appendChild(blackIsZero);
|
|
||||||
|
|
||||||
return chroma;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No compression
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IIOMetadataNode getStandardCompressionNode() {
|
|
||||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
|
||||||
|
|
||||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
|
||||||
compressionTypeName.setAttribute("value", "RLE");
|
|
||||||
node.appendChild(compressionTypeName);
|
|
||||||
|
|
||||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
|
||||||
lossless.setAttribute("value", "TRUE");
|
|
||||||
node.appendChild(lossless);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For HDR, the stored sample data is UnsignedIntegral and data is 4 channels (RGB+Exp),
|
||||||
|
// but decoded to Real (float) 3 chanel RGB
|
||||||
@Override
|
@Override
|
||||||
protected IIOMetadataNode getStandardDataNode() {
|
protected IIOMetadataNode getStandardDataNode() {
|
||||||
IIOMetadataNode node = new IIOMetadataNode("Data");
|
IIOMetadataNode node = new IIOMetadataNode("Data");
|
||||||
@@ -92,38 +28,4 @@ final class HDRMetadata extends AbstractMetadata {
|
|||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IIOMetadataNode getStandardDimensionNode() {
|
|
||||||
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
|
|
||||||
|
|
||||||
// TODO: Support other orientations
|
|
||||||
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
|
|
||||||
imageOrientation.setAttribute("value", "Normal");
|
|
||||||
dimension.appendChild(imageOrientation);
|
|
||||||
|
|
||||||
return dimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No document node
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected IIOMetadataNode getStandardTextNode() {
|
|
||||||
if (header.getSoftware() != null) {
|
|
||||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
|
||||||
|
|
||||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
|
||||||
textEntry.setAttribute("keyword", "Software");
|
|
||||||
textEntry.setAttribute("value", header.getSoftware());
|
|
||||||
text.appendChild(textEntry);
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// No tiling
|
|
||||||
|
|
||||||
// No transparency
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user