mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-18 00:00:03 -04:00
Compare commits
100 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 92ba91b412 | |||
| c8cb472407 | |||
| 2f56650444 | |||
| ff3d403002 | |||
| ea6070a94e | |||
| 2c7a1d1f91 | |||
| 45ce7aabd8 | |||
| a4d48116ee | |||
| 87729d1558 | |||
| dc322d49dc | |||
| e5a82216d2 | |||
| 5f1e746411 | |||
| 243edc581c | |||
| f982484767 | |||
| e2b18795ca | |||
| bf75cae596 | |||
| b92611151c | |||
| c57ce3dcaf | |||
| 7565a8cbf1 | |||
| 2d25670a7e | |||
| 3d0855c7c6 | |||
| 43216c2045 | |||
| 6152b2b57c | |||
| cbf60c191d | |||
| 32a56cadab | |||
| c796715f0a | |||
| 2494359f42 | |||
| d1f65a2f70 | |||
| b7207f302e | |||
| 9c6d983129 | |||
| e989b4a204 | |||
| aa1568e1a4 | |||
| 84a1800cd1 | |||
| a78faf2b31 | |||
| c5cb54e3e5 | |||
| f492807e71 | |||
| fe382ac89f | |||
| 87927ce9f2 | |||
| 7a8cbc40f8 | |||
| 1aeabff1d9 | |||
| 6adb649816 | |||
| afdbbecf70 | |||
| 412987120e | |||
| 628f2dd510 | |||
| 4c2399d246 | |||
| 9c57fcb327 | |||
| 2ab998db03 | |||
| 3e27d2970b | |||
| 34ec884578 | |||
| beb88e2453 | |||
| 0b23644b8d | |||
| e6c0f72cef | |||
| 9b47c236a5 | |||
| 091319a599 | |||
| 33eac436b9 | |||
| f27b5700e8 | |||
| 67b9e7df11 | |||
| 4a51850e9b | |||
| bf55209bf6 | |||
| 3cee4d0112 | |||
| 50dabcd838 | |||
| c7c8e3372b | |||
| a085a454e0 | |||
| 5140846fb6 | |||
| c5b0f4a05f | |||
| 98104222cf | |||
| cb1dc3f4b5 | |||
| 7e584cdab4 | |||
| 6312c65622 | |||
| 924b7d809c | |||
| 458be0380e | |||
| 7deaf47e65 | |||
| 214f0acf29 | |||
| 1ea443cdcf | |||
| 6186928374 | |||
| b4db69859d | |||
| 7b3f0254fd | |||
| 8627c3fc32 | |||
| ab5d40828e | |||
| dbb7c07695 | |||
| 6840f31fa3 | |||
| debf7d0207 | |||
| 0538db7103 | |||
| 1e981242ad | |||
| 135a631bcc | |||
| f2624d5193 | |||
| ada3a84bec | |||
| 7e7aaa293e | |||
| 5d623cce9f | |||
| 055838aaaf | |||
| a8327c3c67 | |||
| 36c91f67e4 | |||
| 4cc53d822f | |||
| 9875de0383 | |||
| 6ed858a4ca | |||
| 38192ae835 | |||
| b5e8853e6b | |||
| a98224e652 | |||
| 73a58266be | |||
| edd523534c |
@@ -0,0 +1,13 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maven/Java library updates
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
# GitHub actions updates
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/workflows"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
+27
-15
@@ -1,6 +1,14 @@
|
||||
name: CI
|
||||
|
||||
on: [ push, pull_request ]
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!dependabot/**'
|
||||
pull_request:
|
||||
branches: [ 'master' ]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -9,20 +17,22 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
java: [ 8, 11, 17, 18 ]
|
||||
java: [ 8, 11, 17, 20 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: ${{ matrix.java }}
|
||||
java-package: jdk
|
||||
cache: 'maven'
|
||||
- name: Run Tests
|
||||
run: mvn test
|
||||
run: mvn --batch-mode --no-transfer-progress test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||
@@ -31,15 +41,17 @@ jobs:
|
||||
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@v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- run: |
|
||||
download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292"
|
||||
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with:
|
||||
distribution: 'jdkfile'
|
||||
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||
@@ -52,9 +64,9 @@ jobs:
|
||||
- name: Display Java version
|
||||
run: java -version
|
||||
- name: Run Tests
|
||||
run: mvn test
|
||||
run: mvn --batch-mode --no-transfer-progress test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||
@@ -66,9 +78,9 @@ jobs:
|
||||
if: github.ref == 'refs/heads/master' # only perform on latest master
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Set up Maven Central
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with: # running setup-java again overwrites the settings.xml
|
||||
distribution: 'temurin'
|
||||
java-version: '8'
|
||||
@@ -80,11 +92,11 @@ jobs:
|
||||
gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE # env variable for GPG private key passphrase (3)
|
||||
- name: Get Project Version
|
||||
run: |
|
||||
echo "PROJECT_VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
|
||||
echo "PROJECT_VERSION=$(mvn --batch-mode help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
|
||||
- name: Publish to Maven Central
|
||||
if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
|
||||
run: mvn deploy -P release -DskipTests
|
||||
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)
|
||||
MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # must be the same env variable name as (3)
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
||||
[](https://paypal.me/haraldk76/100)
|
||||
|
||||

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