mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5da934e11b | |||
| 51297ad496 | |||
| 80a534cd62 | |||
| 24130d466d | |||
| 7559686782 | |||
| b6988c37a7 | |||
| bbffb1d416 | |||
| c68de3bc92 | |||
| a12b6044c6 | |||
| 9a0e2d9659 | |||
| b904f8952f | |||
| 187c952b8e | |||
| ca86605923 | |||
| 8bc863298f | |||
| 3447d1782c | |||
| bf245fde5f | |||
| c0748dcfd7 | |||
| c293516201 | |||
| f6dae36b7e | |||
| a14b481e9e | |||
| d9c1a39c37 | |||
| e9d9f99bb0 | |||
| b9749b94b0 | |||
| 4996dff6e4 | |||
| b1a2244c7f | |||
| c6fe747ca3 | |||
| e1af4d7da9 | |||
| 33556cc0ec | |||
| eaf13b102f | |||
| 74718f1ffb | |||
| bd3700ea59 | |||
| 8fccd9445f | |||
| cf0ed8f95c | |||
| c12e4e5646 | |||
| 287b73c732 | |||
| 24271b8cad | |||
| 3e2f54ee7c | |||
| 2511b2d0cd | |||
| a15d54c92c | |||
| 47b7c4b16c | |||
| 9e1b01a7fd | |||
| 769acc8726 | |||
| 1ace3a6d5f | |||
| a06eb53cd2 | |||
| 1e1a640a6c | |||
| 2444bc5ad4 | |||
| 2e656a45f9 | |||
| 657928f4a5 | |||
| 08f7e070dc | |||
| b205226f0c | |||
| 821df11d90 | |||
| a62a838a0d | |||
| 75ff0f265f | |||
| 15c7cfe9a6 | |||
| 1286077b02 | |||
| 5757743db7 | |||
| fbaa13d48d | |||
| f12df442e9 | |||
| 0c0712ab30 | |||
| 9267842788 | |||
| bcffeb04ec | |||
| d1a1bab18c | |||
| 6b5e75a22b |
@@ -1 +0,0 @@
|
||||
github: haraldk
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Reported bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**Version information**
|
||||
1. The version of the TwelveMonkeys ImageIO library in use.
|
||||
For example: 4.0.0
|
||||
|
||||
2. The *exact* output of `java --version` (or `java -version` for older Java releases).
|
||||
For example:
|
||||
|
||||
java version "1.8.0_271"
|
||||
Java(TM) SE Runtime Environment (build 1.8.0_271-b09)
|
||||
Java HotSpot(TM) 64-Bit Server VM (build 25.271-b09, mixed mode)
|
||||
|
||||
3. Extra information about OS version, server version, standalone program or web application packaging, executable wrapper, etc. Please state exact version numbers where applicable.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Compile the below sample code
|
||||
2. Download the sample image file
|
||||
3. Run the code with the sample file
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Example code**
|
||||
Preferably as a failing JUnit test, or a standalone program with a `main` method that showcases the problem.
|
||||
|
||||
Less is more. Don't add your entire project, only the code required to reproduce the problem. 😀
|
||||
|
||||
**Sample file(s)**
|
||||
Attach any sample files needed to reproduce the problem. Use a ZIP-file if the format is not directly supported by GitHub.
|
||||
|
||||
**Stak trace**
|
||||
Always include the stack trace you experience.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
Do not add screenshots of code or stack traces. 😀
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: New feature
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a use case or a problem you are working on? Please describe.**
|
||||
A clear and concise description of what the problem or use case is. Understanding the rationale is key, to be able to implemeent the right solution.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've already considered, and why they won't work.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
|
||||
@@ -1,13 +0,0 @@
|
||||
---
|
||||
name: Trouble shooting and programming help
|
||||
about: "General programming issues will reach a wider audience at StackOverflow. Tag
|
||||
questions with javax-imageio and/or twelvemonkeys \U0001F600 "
|
||||
title: ''
|
||||
labels: Trouble-shooting
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
General programming issues and problems will reach a much wider audience at StackOverflow, we suggest you ask them there. This will offload our work with maintaining the library, and make sure you get better help sooner.
|
||||
|
||||
Tag the question with `javax-imageio` and/or `twelvemonkeys` and we'll find them there.
|
||||
@@ -1,11 +0,0 @@
|
||||
**What is fixed** Add link to the issue this PR fixes.
|
||||
|
||||
Example: Fixes #42.
|
||||
|
||||
**Why is this change proposed** If this change does *not* fix an open issue, briefly describe the rationale for this PR.
|
||||
|
||||
**What is changed** Briefly describe the changes proposed in this pull request:
|
||||
|
||||
* Fixed rare exception happening in `x >= 42` case
|
||||
* Small optimization of `decompress()` method
|
||||
* Corrected API doc for `compress()` method to reflect current implementation
|
||||
@@ -1,13 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
# Maven/Java library updates
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 10
|
||||
# GitHub actions updates
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/.github/workflows"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -1,102 +0,0 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
- '!dependabot/**'
|
||||
pull_request:
|
||||
branches: [ 'master' ]
|
||||
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
||||
java: [ 8, 11, 17, 20 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
checks: write
|
||||
steps:
|
||||
- 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 --batch-mode --no-transfer-progress test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||
check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
|
||||
|
||||
test_oracle:
|
||||
name: Test Oracle JDK 8 with KCMS=${{ matrix.kcms }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
checks: write
|
||||
strategy:
|
||||
matrix:
|
||||
kcms: [ true, false ]
|
||||
steps:
|
||||
- uses: actions/checkout@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@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with:
|
||||
distribution: 'jdkfile'
|
||||
jdkFile: ${{ runner.temp }}/java_package.tar.gz
|
||||
java-version: '8'
|
||||
cache: 'maven'
|
||||
- name: Set MAVEN_OPTS
|
||||
if: ${{ matrix.kcms }}
|
||||
run: |
|
||||
echo "MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider" >> $GITHUB_ENV
|
||||
- name: Display Java version
|
||||
run: java -version
|
||||
- name: Run Tests
|
||||
run: mvn --batch-mode --no-transfer-progress test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@150e2f992e4fad1379da2056d1d1c279f520e058 # v3.8.0
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST*.xml"
|
||||
check_name: Unit Test Results for Oracle JDK 8 with KCMS=${{ matrix.kcms }}
|
||||
|
||||
release:
|
||||
name: Deploy
|
||||
needs: [ test, test_oracle ]
|
||||
if: github.ref == 'refs/heads/master' # only perform on latest master
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
- name: Set up Maven Central
|
||||
uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0
|
||||
with: # running setup-java again overwrites the settings.xml
|
||||
distribution: 'temurin'
|
||||
java-version: '8'
|
||||
java-package: jdk
|
||||
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-username: MAVEN_CENTRAL_USERNAME # env variable for username in deploy (1)
|
||||
server-password: MAVEN_CENTRAL_PASSWORD # env variable for token in deploy (2)
|
||||
gpg-private-key: ${{ secrets.GPG_KEY }} # Value of the GPG private key to import
|
||||
gpg-passphrase: MAVEN_CENTRAL_GPG_PASSPHRASE # env variable for GPG private key passphrase (3)
|
||||
- name: Get Project Version
|
||||
run: |
|
||||
echo "PROJECT_VERSION=$(mvn --batch-mode help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
|
||||
- name: Publish to Maven Central
|
||||
if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
|
||||
run: mvn --batch-mode --no-transfer-progress deploy -P release -DskipTests
|
||||
env:
|
||||
MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_USERNAME }} # must be the same env variable name as (1)
|
||||
MAVEN_CENTRAL_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} # must be the same env variable name as (2)
|
||||
MAVEN_CENTRAL_GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} # must be the same env variable name as (3)
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
dist: trusty
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
# Oracle JDK 7 no longer supported, we use env matrix to test various CMM providers
|
||||
# - oraclejdk7
|
||||
# Some JPEGImageReader tests fail on OpenJDK, need to investigate/fix before enabling
|
||||
# - openjdk7
|
||||
env:
|
||||
- MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
|
||||
- MAVEN_OPTS=""
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.m2
|
||||
@@ -1,62 +1,56 @@
|
||||
[](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml)
|
||||
[](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio)
|
||||
[](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/)
|
||||
[](https://stackoverflow.com/questions/tagged/twelvemonkeys)
|
||||
[](https://travis-ci.org/haraldk/TwelveMonkeys)
|
||||
[](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
|
||||
[](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.
|
||||
TwelveMonkeys ImageIO is a collection of plugins and extensions for Java's ImageIO.
|
||||
|
||||
The main goal of this project is to provide support for formats not covered by the JRE itself.
|
||||
Support for these formats is important, to be able to read data found
|
||||
These plugins extend the number of image file formats supported in Java, using the `javax.imageio.*` package.
|
||||
The main purpose of this project is to provide support for formats not covered by the JRE itself.
|
||||
|
||||
Support for formats is important, both to be able to read data found
|
||||
"in the wild", as well as to maintain access to data in legacy formats.
|
||||
As there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
|
||||
Because there is lots of legacy data out there, we see the need for open implementations of readers for popular formats.
|
||||
The goal is to create a set of efficient and robust ImageIO plug-ins, that can be distributed independently.
|
||||
|
||||
----
|
||||
|
||||
## File formats supported
|
||||
|
||||
| Plugin | Format | Description | R | W | Metadata | Notes |
|
||||
| ------ | -------- |---------------------------------------------------------|:---:|:---:| -------- | ----- |
|
||||
| Batik | **SVG** | Scalable Vector Graphics | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) |
|
||||
| | WMF | MS Windows Metafile | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/) |
|
||||
| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/bmp_metadata.html), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | CUR | MS Windows Cursor Format | ✔ | - | - |
|
||||
| | ICO | MS Windows Icon Format | ✔ | ✔ | - |
|
||||
| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | ✔ | ✔ | - |
|
||||
| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | JPEG Lossless | | ✔ | - | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/jpeg_metadata.html#image), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | DCX | Multi-page PCX fax document | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple QuickTime Picture Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PNTG | Apple MacPaint Picture Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PBM | NetPBM Portable Bit Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PGM | NetPBM Portable Grey Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PPM | NetPBM Portable Pix Map | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PFM | Portable Float Map | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | (✔) | Native, [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | PSB | Adobe Photoshop Large Document | ✔ | - | Native, [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | ✔ | ✔ | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | ✔ | - | - | OLE2 Compound Document based format only |
|
||||
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| | BigTIFF | | ✔ | ✔ | [Native](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/tiff_metadata.html#ImageMetadata), [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| XWD | XWD | X11 Window Dump Format | ✔ | - | [Standard](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html) |
|
||||
| Plugin | Format | Description | Read | Write | Metadata | Notes |
|
||||
| ------ | -------- | ----------- |:----:|:-----:| -------- | ----- |
|
||||
| Batik | **SVG** | Scalable Vector Graphics | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/)
|
||||
| | WMF | MS Windows Metafile | ✔ | - | - | Requires [Batik](https://xmlgraphics.apache.org/batik/)
|
||||
| [BMP](https://github.com/haraldk/TwelveMonkeys/wiki/BMP-Plugin) | **BMP** | MS Windows and IBM OS/2 Device Independent Bitmap | ✔ | ✔ | Native & Standard |
|
||||
| | CUR | MS Windows Cursor Format | ✔ | - | - |
|
||||
| | ICO | MS Windows Icon Format | ✔ | ✔ | - |
|
||||
| [HDR](https://github.com/haraldk/TwelveMonkeys/wiki/HDR-Plugin) | HDR | Radiance High Dynamic Range RGBE Format | ✔ | - | Standard |
|
||||
| [ICNS](https://github.com/haraldk/TwelveMonkeys/wiki/ICNS-Plugin) | ICNS | Apple Icon Image | ✔ | ✔ | - |
|
||||
| [IFF](https://github.com/haraldk/TwelveMonkeys/wiki/IFF-Plugin) | IFF | Commodore Amiga/Electronic Arts Interchange File Format | ✔ | ✔ | - |
|
||||
| [JPEG](https://github.com/haraldk/TwelveMonkeys/wiki/JPEG-Plugin) | **JPEG** | Joint Photographers Expert Group | ✔ | ✔ | Native & Standard |
|
||||
| [PCX](https://github.com/haraldk/TwelveMonkeys/wiki/PCX-Plugin) | PCX | ZSoft Paintbrush Format | ✔ | - | Standard |
|
||||
| | DCX | Multi-page PCX fax document | ✔ | - | Standard |
|
||||
| [PICT](https://github.com/haraldk/TwelveMonkeys/wiki/PICT-Plugin) | PICT | Apple Mac Paint Picture Format | ✔ | - | - |
|
||||
| [PNM](https://github.com/haraldk/TwelveMonkeys/wiki/PNM-Plugin) | PAM | NetPBM Portable Any Map | ✔ | ✔ | Standard |
|
||||
| | PBM | NetPBM Portable Bit Map | ✔ | - | Standard |
|
||||
| | PGM | NetPBM Portable Grey Map | ✔ | - | Standard |
|
||||
| | PPM | NetPBM Portable Pix Map | ✔ | ✔ | Standard |
|
||||
| | PFM | Portable Float Map | ✔ | - | Standard |
|
||||
| [PSD](https://github.com/haraldk/TwelveMonkeys/wiki/PSD-Plugin) | **PSD** | Adobe Photoshop Document | ✔ | - | Native & Standard |
|
||||
| | PSB | Adobe Photoshop Large Document | ✔ | - | Native & Standard |
|
||||
| [SGI](https://github.com/haraldk/TwelveMonkeys/wiki/SGI-Plugin) | SGI | Silicon Graphics Image Format | ✔ | - | Standard |
|
||||
| [TGA](https://github.com/haraldk/TwelveMonkeys/wiki/TGA-Plugin) | TGA | Truevision TGA Image Format | ✔ | ✔ | Standard |
|
||||
|ThumbsDB| Thumbs.db| MS Windows Thumbs DB | ✔ | - | - | OLE2 Compound Document based format only
|
||||
| [TIFF](https://github.com/haraldk/TwelveMonkeys/wiki/TIFF-Plugin) | **TIFF** | Aldus/Adobe Tagged Image File Format | ✔ | ✔ | Native & Standard |
|
||||
| | BigTIFF | | ✔ | - | Native & Standard |
|
||||
| [WebP](https://github.com/haraldk/TwelveMonkeys/wiki/WebP-Plugin) | **WebP** | Google WebP Format | ✔ | - | Standard | In progress
|
||||
| XWD | XWD | X11 Window Dump Format | ✔ | - | Standard |
|
||||
|
||||
|
||||
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
|
||||
and make sure you use an updated and secure version.*
|
||||
|
||||
Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the
|
||||
[JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html).
|
||||
For BMP, JPEG, and TIFF formats the TwelveMonkeys plugins provides extended format support and additional features.
|
||||
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](http://xmlgraphics.apache.org/security.html),
|
||||
and make sure you use either version 1.6.1, 1.7.1, 1.8+ or later.*
|
||||
|
||||
## Basic usage
|
||||
|
||||
@@ -85,8 +79,10 @@ The plugins are discovered automatically at run time. See the [FAQ](#faq) for mo
|
||||
If you need more control of read parameters and the reading process, the common idiom for reading is something like:
|
||||
|
||||
```java
|
||||
// Create input stream (in try-with-resource block to avoid leaks)
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(file)) {
|
||||
// Create input stream
|
||||
ImageInputStream input = ImageIO.createImageInputStream(file);
|
||||
|
||||
try {
|
||||
// Get the reader
|
||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
|
||||
|
||||
@@ -123,6 +119,10 @@ try (ImageInputStream input = ImageIO.createImageInputStream(file)) {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Close stream in finally block to avoid resource leaks
|
||||
input.close();
|
||||
}
|
||||
```
|
||||
|
||||
Query the reader for source image dimensions using `reader.getWidth(n)` and `reader.getHeight(n)` without reading the
|
||||
@@ -144,8 +144,10 @@ if (!writers.hasNext()) {
|
||||
ImageWriter writer = writers.next();
|
||||
|
||||
try {
|
||||
// Create output stream (in try-with-resource block to avoid leaks)
|
||||
try (ImageOutputStream output = ImageIO.createImageOutputStream(file)) {
|
||||
// Create output stream
|
||||
ImageOutputStream output = ImageIO.createImageOutputStream(file);
|
||||
|
||||
try {
|
||||
writer.setOutput(output);
|
||||
|
||||
// Optionally, listen to progress, warnings, etc.
|
||||
@@ -158,6 +160,10 @@ try {
|
||||
// Optionally, provide thumbnails and image/stream metadata
|
||||
writer.write(..., new IIOImage(..., image, ...), param);
|
||||
}
|
||||
finally {
|
||||
// Close stream in finally block to avoid resource leaks
|
||||
output.close();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
// Dispose writer in finally block to avoid memory leaks
|
||||
@@ -166,7 +172,7 @@ finally {
|
||||
```
|
||||
|
||||
For more advanced usage, and information on how to use the ImageIO API, I suggest you read the
|
||||
[Java Image I/O API Guide](https://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
|
||||
[Java Image I/O API Guide](http://docs.oracle.com/javase/7/docs/technotes/guides/imageio/spec/imageio_guideTOC.fm.html)
|
||||
from Oracle.
|
||||
|
||||
#### Adobe Clipping Path support
|
||||
@@ -220,18 +226,18 @@ BufferedImage output = ditherer.filter(input, null);
|
||||
|
||||
## Building
|
||||
|
||||
Download the project (using [Git](https://git-scm.com/downloads)):
|
||||
Download the project (using [Git](http://git-scm.com/downloads)):
|
||||
|
||||
$ git clone git@github.com:haraldk/TwelveMonkeys.git
|
||||
|
||||
This should create a folder named `TwelveMonkeys` in your current directory. Change directory to the `TwelveMonkeys`
|
||||
folder, and issue the command below to build.
|
||||
|
||||
Build the project (using [Maven](https://maven.apache.org/download.cgi)):
|
||||
Build the project (using [Maven](http://maven.apache.org/download.cgi)):
|
||||
|
||||
$ mvn package
|
||||
|
||||
Currently, the recommended JDK for making a build is Oracle JDK 8.x.
|
||||
Currently, the recommended JDK for making a build is Oracle JDK 7.x or 8.x.
|
||||
|
||||
It's possible to build using OpenJDK, but some tests might fail due to some minor differences between the color management systems used. You will need to either disable the tests in question, or build without tests altogether.
|
||||
|
||||
@@ -274,32 +280,22 @@ 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.9.4</version>
|
||||
<version>3.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>3.9.4</version>
|
||||
<version>3.6.1</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Optional dependency. Needed only if you deploy ImageIO plugins as part of a web app.
|
||||
Make sure you add the IIOProviderContextListener to your web.xml, see above.
|
||||
Optional dependency. Needed only if you deploy `ImageIO` plugins as part of a web app.
|
||||
Make sure you add the `IIOProviderContextListener` to your `web.xml`, see above.
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||
<artifactId>servlet</artifactId>
|
||||
<version>3.9.4</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
Or Jakarta version, for Servlet API 5.0
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.servlet</groupId>
|
||||
<artifactId>servlet</artifactId>
|
||||
<version>3.9.4</version>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>3.6.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
@@ -308,13 +304,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.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
|
||||
twelvemonkeys-common-lang-3.6.1.jar
|
||||
twelvemonkeys-common-io-3.6.1.jar
|
||||
twelvemonkeys-common-image-3.6.1.jar
|
||||
twelvemonkeys-imageio-core-3.6.1.jar
|
||||
twelvemonkeys-imageio-metadata-3.6.1.jar
|
||||
twelvemonkeys-imageio-jpeg-3.6.1.jar
|
||||
twelvemonkeys-imageio-tiff-3.6.1.jar
|
||||
|
||||
#### Deploying the plugins in a web app
|
||||
|
||||
@@ -380,50 +376,79 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
|
||||
|
||||
### Links to prebuilt binaries
|
||||
|
||||
##### Latest version (3.9.4)
|
||||
##### Latest version (3.6.1)
|
||||
|
||||
The latest version that will run on Java 7 is 3.9.4. Later versions will require Java 8 or later.
|
||||
Requires Java 7 or later.
|
||||
|
||||
Common dependencies
|
||||
* [common-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)
|
||||
* [common-lang-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.6.1/common-lang-3.6.1.jar)
|
||||
* [common-io-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.6.1/common-io-3.6.1.jar)
|
||||
* [common-image-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.6.1/common-image-3.6.1.jar)
|
||||
|
||||
ImageIO dependencies
|
||||
* [imageio-core-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.9.4/imageio-core-3.9.4.jar)
|
||||
* [imageio-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-core-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.6.1/imageio-core-3.6.1.jar)
|
||||
* [imageio-metadata-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.6.1/imageio-metadata-3.6.1.jar)
|
||||
|
||||
ImageIO plugins
|
||||
* [imageio-bmp-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.9.4/imageio-bmp-3.9.4.jar)
|
||||
* [imageio-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-bmp-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.6.1/imageio-bmp-3.6.1.jar)
|
||||
* [imageio-jpeg-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.6.1/imageio-jpeg-3.6.1.jar)
|
||||
* [imageio-tiff-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.6.1/imageio-tiff-3.6.1.jar)
|
||||
* [imageio-pnm-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.6.1/imageio-pnm-3.6.1.jar)
|
||||
* [imageio-psd-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.6.1/imageio-psd-3.6.1.jar)
|
||||
* [imageio-hdr-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.6.1/imageio-hdr-3.6.1.jar)
|
||||
* [imageio-iff-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.6.1/imageio-iff-3.6.1.jar)
|
||||
* [imageio-pcx-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.6.1/imageio-pcx-3.6.1.jar)
|
||||
* [imageio-pict-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.6.1/imageio-pict-3.6.1.jar)
|
||||
* [imageio-sgi-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.6.1/imageio-sgi-3.6.1.jar)
|
||||
* [imageio-tga-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.6.1/imageio-tga-3.6.1.jar)
|
||||
* [imageio-icns-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.6.1/imageio-icns-3.6.1.jar)
|
||||
* [imageio-thumbsdb-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.6.1/imageio-thumbsdb-3.6.1.jar)
|
||||
|
||||
ImageIO plugins requiring 3rd party libs
|
||||
* [imageio-batik-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.9.4/imageio-batik-3.9.4.jar)
|
||||
* [imageio-batik-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.6.1/imageio-batik-3.6.1.jar)
|
||||
|
||||
Photoshop Path support for ImageIO
|
||||
* [imageio-clippath-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.9.4/imageio-clippath-3.9.4.jar)
|
||||
* [imageio-clippath-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.6.1/imageio-clippath-3.6.1.jar)
|
||||
|
||||
Servlet support
|
||||
* [servlet-3.9.4.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.9.4/servlet-3.9.4.jar)
|
||||
* [servlet-3.6.1.jar](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.6.1/servlet-3.6.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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://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](http://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.0.2/servlet-3.0.2.jar)
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is provided under the OSI approved [BSD license](https://opensource.org/licenses/BSD-3-Clause):
|
||||
The project is distributed under the OSI approved [BSD license](http://opensource.org/licenses/BSD-3-Clause):
|
||||
|
||||
Copyright (c) 2008-2022, Harald Kuhr
|
||||
Copyright (c) 2008-2020, Harald Kuhr
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
@@ -455,9 +480,8 @@ 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, 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.
|
||||
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.
|
||||
|
||||
|
||||
q: What changes do I have to make to my code in order to use the plug-ins?
|
||||
@@ -475,41 +499,22 @@ 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 to do, is to make sure you have the TwelveMonkeys ImageIO JARs in your classpath.
|
||||
All you have have to do, is to make sure you have the TwelveMonkeys JARs in your classpath.
|
||||
|
||||
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](https://docs.oracle.com/javase/7/docs/api/javax/imageio/spi/IIORegistry.html).
|
||||
You can read more about the registry and the lookup mechanism in the [IIORegistry API doc](http://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`, `BMPImageReader` `TIFFImageReader`, and the Apple provided `TIFFImageReader` on OS X,
|
||||
the Sun/Oracle provided `JPEGImageReader` and `BMPImageReader`, and the Apple provided `TIFFImageReader` on OS X,
|
||||
respectively. Using the pairwise ordering will not remove any functionality form these implementations, but in most
|
||||
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 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.
|
||||
a: While JAI (and jai-imageio in particular) have support for some of the formats, JAI has some major issues.
|
||||
The most obvious being:
|
||||
- It's not actively developed. No issue has been fixed for years.
|
||||
- It's not actively developed. No issues has been fixed for years.
|
||||
- To get full format support, you need native libs.
|
||||
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
|
||||
Some environments may also prevent deployment of native libs, which brings us back to square one.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Security Policy
|
||||
|
||||
To report a security issue, please disclose it at [security advisory](https://github.com/haraldk/TwelveMonkeys/security/advisories/new).
|
||||
|
||||
Vulnerabilities will be disclosed in a best effort base.
|
||||
+1
-6
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
@@ -123,11 +123,6 @@
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-xwd</artifactId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -13,10 +13,6 @@
|
||||
The TwelveMonkeys Common Image support
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.common.image</project.jpms.module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
@@ -31,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>jmagick</groupId>
|
||||
<artifactId>jmagick</artifactId>
|
||||
<version>6.6.9</version>
|
||||
<version>6.2.4</version>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -34,13 +34,7 @@ import java.awt.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.BufferedImageOp;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.ImagingOpException;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.RasterOp;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.awt.image.*;
|
||||
|
||||
/**
|
||||
* This is a drop-in replacement for {@link java.awt.image.AffineTransformOp}.
|
||||
@@ -76,7 +70,6 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
delegate = new java.awt.image.AffineTransformOp(xform, interpolationType);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
public BufferedImage filter(final BufferedImage src, BufferedImage dst) {
|
||||
try {
|
||||
@@ -87,9 +80,10 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
dst = createCompatibleDestImage(src, src.getColorModel());
|
||||
}
|
||||
|
||||
Graphics2D g2d = dst.createGraphics();
|
||||
Graphics2D g2d = null;
|
||||
|
||||
try {
|
||||
g2d = dst.createGraphics();
|
||||
int interpolationType = delegate.getInterpolationType();
|
||||
|
||||
if (interpolationType > 0) {
|
||||
@@ -115,7 +109,9 @@ public class AffineTransformOp implements BufferedImageOp, RasterOp {
|
||||
return dst;
|
||||
}
|
||||
finally {
|
||||
g2d.dispose();
|
||||
if (g2d != null) {
|
||||
g2d.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+87
-97
@@ -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 source the source image
|
||||
* @throws IllegalArgumentException if {@code source == null}
|
||||
* @param pSource the source image
|
||||
* @throws IllegalArgumentException if {@code pSource == null}
|
||||
*/
|
||||
public BufferedImageFactory(final Image source) {
|
||||
this(source != null ? source.getSource() : null);
|
||||
public BufferedImageFactory(final Image pSource) {
|
||||
this(pSource != null ? pSource.getSource() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code BufferedImageFactory}.
|
||||
* @param source the source image producer
|
||||
* @throws IllegalArgumentException if {@code source == null}
|
||||
* @param pSource the source image producer
|
||||
* @throws IllegalArgumentException if {@code pSource == null}
|
||||
*/
|
||||
public BufferedImageFactory(final ImageProducer source) {
|
||||
Validate.notNull(source, "source");
|
||||
producer = source;
|
||||
public BufferedImageFactory(final ImageProducer pSource) {
|
||||
Validate.notNull(pSource, "source");
|
||||
producer = pSource;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,44 +155,44 @@ public final class BufferedImageFactory {
|
||||
/**
|
||||
* Sets the source region (AOI) for the new image.
|
||||
*
|
||||
* @param region the source region
|
||||
* @param pRegion the source region
|
||||
*/
|
||||
public void setSourceRegion(final Rectangle region) {
|
||||
public void setSourceRegion(final Rectangle pRegion) {
|
||||
// Re-fetch everything, if region changed
|
||||
if (x != region.x || y != region.y || width != region.width || height != region.height) {
|
||||
if (x != pRegion.x || y != pRegion.y || width != pRegion.width || height != pRegion.height) {
|
||||
dispose();
|
||||
}
|
||||
|
||||
x = region.x;
|
||||
y = region.y;
|
||||
width = region.width;
|
||||
height = region.height;
|
||||
x = pRegion.x;
|
||||
y = pRegion.y;
|
||||
width = pRegion.width;
|
||||
height = pRegion.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source subsampling for the new image.
|
||||
*
|
||||
* @param xSubsampling horizontal subsampling factor
|
||||
* @param ySubsampling vertical subsampling factor
|
||||
* @param pXSub horizontal subsampling factor
|
||||
* @param pYSub vertical subsampling factor
|
||||
*/
|
||||
public void setSourceSubsampling(int xSubsampling, int ySubsampling) {
|
||||
public void setSourceSubsampling(int pXSub, int pYSub) {
|
||||
// Re-fetch everything, if subsampling changed
|
||||
if (xSub != xSubsampling || ySub != ySubsampling) {
|
||||
if (xSub != pXSub || ySub != pYSub) {
|
||||
dispose();
|
||||
}
|
||||
|
||||
if (xSubsampling > 1) {
|
||||
xSub = xSubsampling;
|
||||
if (pXSub > 1) {
|
||||
xSub = pXSub;
|
||||
}
|
||||
if (ySubsampling > 1) {
|
||||
ySub = ySubsampling;
|
||||
if (pYSub > 1) {
|
||||
ySub = pYSub;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void doFetch(final boolean colorModelOnly) throws ImageConversionException {
|
||||
if (!fetching && (!colorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
||||
private synchronized void doFetch(boolean pColorModelOnly) throws ImageConversionException {
|
||||
if (!fetching && (!pColorModelOnly && buffered == null || buffered == null && sourceColorModel == null)) {
|
||||
// NOTE: Subsampling is only applied if extracting full image
|
||||
if (!colorModelOnly && (xSub > 1 || ySub > 1)) {
|
||||
if (!pColorModelOnly && (xSub > 1 || ySub > 1)) {
|
||||
// If only sampling a region, the region must be scaled too
|
||||
if (width > 0 && height > 0) {
|
||||
width = (width + xSub - 1) / xSub;
|
||||
@@ -207,41 +207,38 @@ public final class BufferedImageFactory {
|
||||
|
||||
// Start fetching
|
||||
fetching = true;
|
||||
readColorModelOnly = colorModelOnly;
|
||||
readColorModelOnly = pColorModelOnly;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (consumerException != null) {
|
||||
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
||||
}
|
||||
|
||||
if (colorModelOnly) {
|
||||
createColorModel();
|
||||
}
|
||||
else {
|
||||
createBuffered();
|
||||
}
|
||||
if (consumerException != null) {
|
||||
throw new ImageConversionException("Image conversion failed: " + consumerException.getMessage(), consumerException);
|
||||
}
|
||||
finally {
|
||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||
freeResources();
|
||||
|
||||
if (pColorModelOnly) {
|
||||
createColorModel();
|
||||
}
|
||||
else {
|
||||
createBuffered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createColorModel() {
|
||||
colorModel = sourceColorModel;
|
||||
|
||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||
freeResources();
|
||||
}
|
||||
|
||||
private void createBuffered() {
|
||||
@@ -256,9 +253,8 @@ public final class BufferedImageFactory {
|
||||
}
|
||||
}
|
||||
|
||||
if (buffered == null) {
|
||||
throw new ImageConversionException("Could not create BufferedImage");
|
||||
}
|
||||
// Clean up, in case any objects are copied/cloned, so we can free resources
|
||||
freeResources();
|
||||
}
|
||||
|
||||
private void freeResources() {
|
||||
@@ -284,27 +280,27 @@ public final class BufferedImageFactory {
|
||||
/**
|
||||
* Adds a progress listener to this factory.
|
||||
*
|
||||
* @param listener the progress listener
|
||||
* @param pListener the progress listener
|
||||
*/
|
||||
public void addProgressListener(ProgressListener listener) {
|
||||
if (listener == null) {
|
||||
public void addProgressListener(ProgressListener pListener) {
|
||||
if (pListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (listeners == null) {
|
||||
listeners = new CopyOnWriteArrayList<>();
|
||||
listeners = new CopyOnWriteArrayList<ProgressListener>();
|
||||
}
|
||||
|
||||
listeners.add(listener);
|
||||
listeners.add(pListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a progress listener from this factory.
|
||||
*
|
||||
* @param listener the progress listener
|
||||
* @param pListener the progress listener
|
||||
*/
|
||||
public void removeProgressListener(ProgressListener listener) {
|
||||
if (listener == null) {
|
||||
public void removeProgressListener(ProgressListener pListener) {
|
||||
if (pListener == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,7 +308,7 @@ public final class BufferedImageFactory {
|
||||
return;
|
||||
}
|
||||
|
||||
listeners.remove(listener);
|
||||
listeners.remove(pListener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,22 +324,21 @@ public final class BufferedImageFactory {
|
||||
* Converts an array of {@code int} pixels to an array of {@code short}
|
||||
* 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 inputPixels the pixel data to convert
|
||||
* @return an array of {@code short}s, same length as {@code inputPixels}
|
||||
* @param pPixels the pixel data to convert
|
||||
* @return an array of {@code short}s, same lenght as {@code pPixels}
|
||||
*/
|
||||
private static short[] toShortPixels(int[] inputPixels) {
|
||||
short[] pixels = new short[inputPixels.length];
|
||||
private static short[] toShortPixels(int[] pPixels) {
|
||||
short[] pixels = new short[pPixels.length];
|
||||
|
||||
for (int i = 0; i < pixels.length; i++) {
|
||||
pixels[i] = (short) (inputPixels[i] & 0xffff);
|
||||
pixels[i] = (short) (pPixels[i] & 0xffff);
|
||||
}
|
||||
|
||||
return pixels;
|
||||
@@ -356,17 +351,17 @@ public final class BufferedImageFactory {
|
||||
* @see BufferedImageFactory#addProgressListener
|
||||
* @see BufferedImageFactory#removeProgressListener
|
||||
*/
|
||||
public interface ProgressListener extends EventListener {
|
||||
public static interface ProgressListener extends EventListener {
|
||||
|
||||
/**
|
||||
* Reports progress to this listener.
|
||||
* Invoked by the {@code BufferedImageFactory} to report progress in
|
||||
* the image decoding.
|
||||
*
|
||||
* @param factory the factory reporting the progress
|
||||
* @param percentage the percentage of progress
|
||||
* @param pFactory the factory reporting the progress
|
||||
* @param pPercentage the percentage of progress
|
||||
*/
|
||||
void progress(BufferedImageFactory factory, float percentage);
|
||||
void progress(BufferedImageFactory pFactory, float pPercentage);
|
||||
}
|
||||
|
||||
private class Consumer implements ImageConsumer {
|
||||
@@ -451,18 +446,18 @@ public final class BufferedImageFactory {
|
||||
processProgress(pY + pHeight);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
private void setColorModelOnce(final ColorModel colorModel) {
|
||||
private void setColorModelOnce(final ColorModel pModel) {
|
||||
// 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 != colorModel) {
|
||||
if (sourcePixels == null) {
|
||||
sourceColorModel = colorModel;
|
||||
if (sourceColorModel != pModel) {
|
||||
if (/*sourceColorModel == null ||*/ sourcePixels == null) {
|
||||
sourceColorModel = pModel;
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Change of ColorModel after pixel delivery not supported");
|
||||
@@ -475,16 +470,17 @@ public final class BufferedImageFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void imageComplete(int status) {
|
||||
public void imageComplete(int pStatus) {
|
||||
fetching = false;
|
||||
|
||||
if (producer != null) {
|
||||
producer.removeConsumer(this);
|
||||
}
|
||||
|
||||
if (status == ImageConsumer.IMAGEERROR) {
|
||||
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
||||
switch (pStatus) {
|
||||
case ImageConsumer.IMAGEERROR:
|
||||
consumerException = new ImageConversionException("ImageConsumer.IMAGEERROR");
|
||||
break;
|
||||
}
|
||||
|
||||
synchronized (BufferedImageFactory.this) {
|
||||
@@ -492,18 +488,16 @@ public final class BufferedImageFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorModel(ColorModel colorModel) {
|
||||
setColorModelOnce(colorModel);
|
||||
public void setColorModel(ColorModel pModel) {
|
||||
setColorModelOnce(pModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDimensions(int w, int h) {
|
||||
public void setDimensions(int pWidth, int pHeight) {
|
||||
if (width < 0) {
|
||||
width = w - x;
|
||||
width = pWidth - x;
|
||||
}
|
||||
if (height < 0) {
|
||||
height = h - y;
|
||||
height = pHeight - y;
|
||||
}
|
||||
|
||||
// Hmm.. Special case, but is it a good idea?
|
||||
@@ -512,31 +506,27 @@ public final class BufferedImageFactory {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setHints(int hintFlags) {
|
||||
public void setHints(int pHintflags) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@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 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, int[] pixels, int offset, int scanSize) {
|
||||
if (colorModel.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||
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) {
|
||||
// NOTE: Workaround for limitation in ImageConsumer API
|
||||
// Convert int[] to short[], to be compatible with the ColorModel
|
||||
setPixelsImpl(x, y, width, height, colorModel, toShortPixels(pixels), offset, scanSize);
|
||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, toShortPixels(pPixels), pOffset, pScanSize);
|
||||
}
|
||||
else {
|
||||
setPixelsImpl(x, y, width, height, colorModel, pixels, offset, scanSize);
|
||||
setPixelsImpl(pX, pY, pWeigth, pHeight, pModel, pPixels, pOffset, pScanSize);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setProperties(Hashtable properties) {
|
||||
sourceProperties = properties;
|
||||
public void setProperties(Hashtable pProperties) {
|
||||
sourceProperties = pProperties;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -45,8 +45,8 @@ import java.awt.image.BufferedImage;
|
||||
*/
|
||||
public class BufferedImageIcon implements Icon {
|
||||
private final BufferedImage image;
|
||||
private final int width;
|
||||
private final int height;
|
||||
private int width;
|
||||
private int height;
|
||||
private final boolean fast;
|
||||
|
||||
public BufferedImageIcon(BufferedImage pImage) {
|
||||
@@ -81,10 +81,11 @@ public class BufferedImageIcon implements Icon {
|
||||
else {
|
||||
//System.out.println("Scaling using interpolation");
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
AffineTransform transform = AffineTransform.getTranslateInstance(x, y);
|
||||
transform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(image, transform, null);
|
||||
AffineTransform xform = AffineTransform.getTranslateInstance(x, y);
|
||||
xform.scale(width / (double) image.getWidth(), height / (double) image.getHeight());
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
|
||||
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(image, xform, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,7 +587,6 @@ class IndexImage {
|
||||
* @deprecated Use {@link #getIndexColorModel(Image,int,int)} instead!
|
||||
* This version will be removed in a later version of the API.
|
||||
*/
|
||||
@Deprecated
|
||||
public static IndexColorModel getIndexColorModel(Image pImage, int pNumberOfColors, boolean pFast) {
|
||||
return getIndexColorModel(pImage, pNumberOfColors, pFast ? COLOR_SELECTION_FAST : COLOR_SELECTION_QUALITY);
|
||||
}
|
||||
|
||||
+16
-22
@@ -30,26 +30,17 @@
|
||||
|
||||
package com.twelvemonkeys.image;
|
||||
|
||||
import static java.lang.Math.min;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.BufferedImageOp;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.ImagingOpException;
|
||||
import java.awt.image.Raster;
|
||||
import java.awt.image.RasterOp;
|
||||
import java.awt.image.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* AffineTransformOpTest.
|
||||
@@ -110,7 +101,6 @@ public class AffineTransformOpTest {
|
||||
|
||||
private final int width = 30;
|
||||
private final int height = 20;
|
||||
private final double anchor = min(width, height) / 2.0;
|
||||
|
||||
@Test
|
||||
public void testGetPoint2D() {
|
||||
@@ -138,8 +128,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
public void testFilterRotateBIStandard() {
|
||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
|
||||
for (Integer type : TYPES) {
|
||||
BufferedImage image = new BufferedImage(width, height, type);
|
||||
@@ -157,8 +147,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
public void testFilterRotateBICustom() {
|
||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
BufferedImageOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
BufferedImageOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
|
||||
for (ImageTypeSpecifier spec : SPECS) {
|
||||
BufferedImage image = spec.createBufferedImage(width, height);
|
||||
@@ -207,8 +197,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
public void testFilterRotateRasterStandard() {
|
||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
|
||||
for (Integer type : TYPES) {
|
||||
Raster raster = new BufferedImage(width, height, type).getRaster();
|
||||
@@ -231,6 +221,8 @@ public class AffineTransformOpTest {
|
||||
fail("No result!");
|
||||
}
|
||||
else {
|
||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterStandard");
|
||||
System.err.println("type: " + type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -248,8 +240,8 @@ public class AffineTransformOpTest {
|
||||
|
||||
@Test
|
||||
public void testFilterRotateRasterCustom() {
|
||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, anchor, anchor), null);
|
||||
RasterOp jreOp = new java.awt.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
RasterOp tmOp = new com.twelvemonkeys.image.AffineTransformOp(AffineTransform.getQuadrantRotateInstance(1, Math.min(width, height) / 2, Math.min(width, height) / 2), null);
|
||||
|
||||
for (ImageTypeSpecifier spec : SPECS) {
|
||||
Raster raster = spec.createBufferedImage(width, height).getRaster();
|
||||
@@ -272,6 +264,8 @@ public class AffineTransformOpTest {
|
||||
fail("No result!");
|
||||
}
|
||||
else {
|
||||
System.err.println("AffineTransformOpTest.testFilterRotateRasterCustom");
|
||||
System.err.println("spec: " + spec);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
+14
-13
@@ -34,13 +34,14 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
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.net.URL;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* BufferedImageFactoryTestCase
|
||||
@@ -259,9 +260,9 @@ public class BufferedImageFactoryTest {
|
||||
|
||||
// Listener should abort ASAP
|
||||
factory.addProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||
public void progress(BufferedImageFactory factory, float percentage) {
|
||||
if (percentage > 5) {
|
||||
factory.abort();
|
||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
||||
if (pPercentage > 5) {
|
||||
pFactory.abort();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -342,7 +343,7 @@ public class BufferedImageFactoryTest {
|
||||
VerifyingListener listener = new VerifyingListener(factory);
|
||||
factory.addProgressListener(listener);
|
||||
factory.removeProgressListener(new BufferedImageFactory.ProgressListener() {
|
||||
public void progress(BufferedImageFactory factory, float percentage) {
|
||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
||||
}
|
||||
});
|
||||
factory.getBufferedImage();
|
||||
@@ -379,11 +380,11 @@ public class BufferedImageFactoryTest {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public void progress(BufferedImageFactory factory, float percentage) {
|
||||
assertEquals(this.factory, factory);
|
||||
assertTrue(percentage >= progress && percentage <= 100f);
|
||||
public void progress(BufferedImageFactory pFactory, float pPercentage) {
|
||||
assertEquals(factory, pFactory);
|
||||
assertTrue(pPercentage >= progress && pPercentage <= 100f);
|
||||
|
||||
progress = percentage;
|
||||
progress = pPercentage;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -13,10 +13,6 @@
|
||||
The TwelveMonkeys Common IO support
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.common.io</project.jpms.module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
|
||||
@@ -43,8 +42,11 @@ import java.util.Arrays;
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: FastByteArrayOutputStream.java#2 $
|
||||
*/
|
||||
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
||||
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
|
||||
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
/** Max grow size (unless if writing more than this amount of bytes) */
|
||||
protected int maxGrowSize = 1024 * 1024; // 1 MB
|
||||
|
||||
/**
|
||||
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
||||
* size.
|
||||
@@ -95,8 +97,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
private void growIfNeeded(int pNewCount) {
|
||||
if (pNewCount > buf.length) {
|
||||
int newSize = Math.max(buf.length << 1, pNewCount);
|
||||
buf = Arrays.copyOf(buf, newSize);
|
||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
|
||||
byte[] newBuf = new byte[newSize];
|
||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||
buf = newBuf;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +113,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
// Non-synchronized version of toByteArray
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
return Arrays.copyOf(buf, count);
|
||||
byte[] newBuf = new byte[count];
|
||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||
|
||||
return newBuf;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -346,7 +346,7 @@ public final class FileUtil {
|
||||
|
||||
/**
|
||||
* Gets the file (type) extension of the given file.
|
||||
* A file extension is the part of the filename, after the last occurrence
|
||||
* A file extension is the part of the filename, after the last occurence
|
||||
* of a period {@code '.'}.
|
||||
* If the filename contains no period, {@code null} is returned.
|
||||
*
|
||||
|
||||
@@ -65,7 +65,6 @@ import java.io.FilenameFilter;
|
||||
* @see WildcardStringParser
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public class FilenameMaskFilter implements FilenameFilter {
|
||||
|
||||
// TODO: Rewrite to use regexp, or create new class
|
||||
|
||||
@@ -442,7 +442,6 @@ public class LittleEndianDataInputStream extends FilterInputStream implements Da
|
||||
* @see java.io.BufferedReader#readLine()
|
||||
* @see java.io.DataInputStream#readLine()
|
||||
*/
|
||||
@Deprecated
|
||||
public String readLine() throws IOException {
|
||||
DataInputStream ds = new DataInputStream(in);
|
||||
return ds.readLine();
|
||||
|
||||
@@ -41,20 +41,21 @@ 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 stream}.
|
||||
* Creates a {@code SubStream} of the given {@code pStream}.
|
||||
*
|
||||
* @param stream the underlying input stream
|
||||
* @param length maximum number of bytes to read from this stream
|
||||
* @param pStream the underlying input stream
|
||||
* @param pLength maximum number of bytes to read drom this stream
|
||||
*/
|
||||
public SubStream(final InputStream stream, final long length) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
bytesLeft = length;
|
||||
public SubStream(final InputStream pStream, final long pLength) {
|
||||
super(Validate.notNull(pStream, "stream"));
|
||||
bytesLeft = pLength;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,13 +73,13 @@ public final class SubStream extends FilterInputStream {
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (int) findMaxLen(super.available());
|
||||
return (int) Math.min(super.available(), bytesLeft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int readLimit) {
|
||||
super.mark(readLimit);// This either succeeds or does nothing...
|
||||
markLimit = readLimit;
|
||||
public void mark(int pReadLimit) {
|
||||
super.mark(pReadLimit);// This either succeeds or does nothing...
|
||||
markLimit = pReadLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -92,42 +93,44 @@ public final class SubStream extends FilterInputStream {
|
||||
if (bytesLeft-- <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] bytes) throws IOException {
|
||||
return read(bytes, 0, bytes.length);
|
||||
public final int read(byte[] pBytes) throws IOException {
|
||||
return read(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] bytes, final int off, final int len) throws IOException {
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (bytesLeft <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = super.read(bytes, off, (int) findMaxLen(len));
|
||||
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
||||
bytesLeft = read < 0 ? 0 : bytesLeft - read;
|
||||
|
||||
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 length the requested length
|
||||
* @param pLength the requested length
|
||||
* @return the maximum number of bytes to read
|
||||
*/
|
||||
private long findMaxLen(long length) {
|
||||
return bytesLeft < length ? Math.max(bytesLeft, 0) : length;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,39 +45,39 @@ import java.nio.ByteBuffer;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
||||
*/
|
||||
public final class DecoderStream extends FilterInputStream {
|
||||
private final ByteBuffer buffer;
|
||||
private final Decoder decoder;
|
||||
protected final ByteBuffer buffer;
|
||||
protected final Decoder decoder;
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code stream} argument.
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
* The stream will use a default decode buffer size.
|
||||
*
|
||||
* @param stream the underlying input stream.
|
||||
* @param decoder the decoder that will be used to decode the underlying stream
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream stream, final Decoder decoder) {
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
||||
// TODO: Let the decoder decide preferred buffer size
|
||||
this(stream, decoder, 1024);
|
||||
this(pStream, pDecoder, 1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code stream} argument.
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
*
|
||||
* @param stream the underlying input stream.
|
||||
* @param decoder the decoder that will be used to decode the underlying stream
|
||||
* @param bufferSize the size of the decode buffer
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
* @param pBufferSize the size of the decode buffer
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) {
|
||||
super(stream);
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
||||
super(pStream);
|
||||
|
||||
this.decoder = decoder;
|
||||
buffer = ByteBuffer.allocate(bufferSize);
|
||||
decoder = pDecoder;
|
||||
buffer = ByteBuffer.allocate(pBufferSize);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
@@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream {
|
||||
return buffer.get() & 0xff;
|
||||
}
|
||||
|
||||
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
if (bytes == null) {
|
||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
||||
if (pBytes == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
else if ((offset < 0) || (offset > bytes.length) || (length < 0) ||
|
||||
((offset + length) > bytes.length) || ((offset + length) < 0)) {
|
||||
throw new IndexOutOfBoundsException("bytes.length=" + bytes.length + " offset=" + offset + " length=" + length);
|
||||
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength);
|
||||
}
|
||||
else if (length == 0) {
|
||||
else if (pLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -114,11 +114,11 @@ public final class DecoderStream extends FilterInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
// Read until we have read length bytes, or have reached EOF
|
||||
// Read until we have read pLength bytes, or have reached EOF
|
||||
int count = 0;
|
||||
int off = offset;
|
||||
int off = pOffset;
|
||||
|
||||
while (length > count) {
|
||||
while (pLength > count) {
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
break;
|
||||
@@ -126,8 +126,8 @@ public final class DecoderStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
// Copy as many bytes as possible
|
||||
int dstLen = Math.min(length - count, buffer.remaining());
|
||||
buffer.get(bytes, off, dstLen);
|
||||
int dstLen = Math.min(pLength - count, buffer.remaining());
|
||||
buffer.get(pBytes, off, dstLen);
|
||||
|
||||
// Update offset (rest)
|
||||
off += dstLen;
|
||||
@@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
return count;
|
||||
}
|
||||
|
||||
public long skip(final long length) throws IOException {
|
||||
public long skip(final long pLength) throws IOException {
|
||||
// End of file?
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
@@ -147,10 +147,10 @@ public final class DecoderStream extends FilterInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
// Skip until we have skipped length bytes, or have reached EOF
|
||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
||||
long total = 0;
|
||||
|
||||
while (total < length) {
|
||||
while (total < pLength) {
|
||||
if (!buffer.hasRemaining()) {
|
||||
if (fill() < 0) {
|
||||
break;
|
||||
@@ -158,7 +158,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
}
|
||||
|
||||
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
|
||||
int skipped = (int) Math.min(length - total, buffer.remaining());
|
||||
int skipped = (int) Math.min(pLength - total, buffer.remaining());
|
||||
buffer.position(buffer.position() + skipped);
|
||||
total += skipped;
|
||||
}
|
||||
@@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
private int fill() throws IOException {
|
||||
protected int fill() throws IOException {
|
||||
buffer.clear();
|
||||
int read = decoder.decode(in, buffer);
|
||||
|
||||
|
||||
@@ -45,39 +45,41 @@ import java.nio.ByteBuffer;
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
||||
*/
|
||||
public final class EncoderStream extends FilterOutputStream {
|
||||
// TODO: This class need a test case ASAP!!!
|
||||
|
||||
private final Encoder encoder;
|
||||
protected final Encoder encoder;
|
||||
private final boolean flushOnWrite;
|
||||
|
||||
private final ByteBuffer buffer;
|
||||
protected final ByteBuffer buffer;
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param stream the underlying output stream
|
||||
* @param encoder the encoder to use
|
||||
* @param pStream the underlying output stream
|
||||
* @param pEncoder the encoder to use
|
||||
*/
|
||||
public EncoderStream(final OutputStream stream, final Encoder encoder) {
|
||||
this(stream, encoder, false);
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
|
||||
this(pStream, pEncoder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param stream the underlying output stream
|
||||
* @param encoder the encoder to use
|
||||
* @param flushOnWrite if {@code true}, calls to the byte-array
|
||||
* @param pStream the underlying output stream
|
||||
* @param pEncoder the encoder to use
|
||||
* @param pFlushOnWrite if {@code true}, calls to the byte-array
|
||||
* {@code write} methods will automatically flush the buffer.
|
||||
*/
|
||||
public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) {
|
||||
super(stream);
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
||||
super(pStream);
|
||||
|
||||
this.encoder = encoder;
|
||||
this.flushOnWrite = flushOnWrite;
|
||||
encoder = pEncoder;
|
||||
flushOnWrite = pFlushOnWrite;
|
||||
|
||||
buffer = ByteBuffer.allocate(1024);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
@@ -102,33 +104,33 @@ public final class EncoderStream extends FilterOutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public void write(final byte[] bytes) throws IOException {
|
||||
write(bytes, 0, bytes.length);
|
||||
public final void write(final byte[] pBytes) throws IOException {
|
||||
write(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
// TODO: Verify that this works for the general case (it probably won't)...
|
||||
// TODO: We might need a way to explicitly flush the encoder, or specify
|
||||
// that the encoder can't buffer. In that case, the encoder should probably
|
||||
// tell the EncoderStream how large buffer it prefers...
|
||||
public void write(final byte[] values, final int offset, final int length) throws IOException {
|
||||
if (!flushOnWrite && length < buffer.remaining()) {
|
||||
// tell the EncoderStream how large buffer it prefers...
|
||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (!flushOnWrite && pLength < buffer.remaining()) {
|
||||
// Buffer data
|
||||
buffer.put(values, offset, length);
|
||||
buffer.put(pBytes, pOffset, pLength);
|
||||
}
|
||||
else {
|
||||
// Encode data already in the buffer
|
||||
encodeBuffer();
|
||||
|
||||
// Encode rest without buffering
|
||||
encoder.encode(out, ByteBuffer.wrap(values, offset, length));
|
||||
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
|
||||
}
|
||||
}
|
||||
|
||||
public void write(final int value) throws IOException {
|
||||
public void write(final int pByte) throws IOException {
|
||||
if (!buffer.hasRemaining()) {
|
||||
encodeBuffer(); // Resets bufferPos to 0
|
||||
}
|
||||
|
||||
buffer.put((byte) value);
|
||||
buffer.put((byte) pByte);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,6 @@
|
||||
|
||||
package com.twelvemonkeys.xml;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.w3c.dom.DOMConfiguration;
|
||||
import org.w3c.dom.DOMImplementationList;
|
||||
import org.w3c.dom.Document;
|
||||
@@ -41,6 +38,9 @@ import org.w3c.dom.ls.DOMImplementationLS;
|
||||
import org.w3c.dom.ls.LSOutput;
|
||||
import org.w3c.dom.ls.LSSerializer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* {@code DOMImplementationLS} backed implementation.
|
||||
*
|
||||
@@ -88,6 +88,17 @@ public final class DOMSerializer {
|
||||
output.setCharacterStream(pStream);
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: Is it useful?
|
||||
public void setNewLine(final String pNewLine) {
|
||||
serializer.setNewLine(pNewLine);
|
||||
}
|
||||
|
||||
public String getNewLine() {
|
||||
return serializer.getNewLine();
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specifies wether the serializer should use indentation and optimize for
|
||||
* readability.
|
||||
@@ -158,7 +169,13 @@ public final class DOMSerializer {
|
||||
try {
|
||||
return DOMImplementationRegistry.newInstance();
|
||||
}
|
||||
catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,23 +30,16 @@
|
||||
|
||||
package com.twelvemonkeys.xml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* XMLSerializer
|
||||
@@ -297,7 +290,7 @@ public class XMLSerializer {
|
||||
}
|
||||
|
||||
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
||||
pBuilder.append(pString, pStart, pEnd);
|
||||
pBuilder.append(pString.substring(pStart, pEnd));
|
||||
pBuilder.append(pEntity);
|
||||
return pEnd + 1;
|
||||
}
|
||||
@@ -534,7 +527,8 @@ public class XMLSerializer {
|
||||
builder = factory.newDocumentBuilder();
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
throw new IOException(e);
|
||||
//noinspection ThrowableInstanceNeverThrown BOGUS
|
||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
||||
}
|
||||
|
||||
DOMImplementation dom = builder.getDOMImplementation();
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DecoderStreamTest {
|
||||
|
||||
private final Random rng = new Random(5467809876546L);
|
||||
|
||||
private byte[] createData(final int length) {
|
||||
byte[] data = new byte[length];
|
||||
rng.nextBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeSingleBytes() throws IOException {
|
||||
byte[] data = createData(1327);
|
||||
|
||||
InputStream source = new ByteArrayInputStream(data);
|
||||
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||
for (byte datum : data) {
|
||||
int read = stream.read();
|
||||
assertNotEquals(-1, read);
|
||||
assertEquals(datum, (byte) read);
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeArray() throws IOException {
|
||||
int length = 793;
|
||||
byte[] data = createData(length * 10);
|
||||
|
||||
InputStream source = new ByteArrayInputStream(data);
|
||||
byte[] result = new byte[477];
|
||||
|
||||
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||
int dataOffset = 0;
|
||||
while (dataOffset < data.length) {
|
||||
int count = stream.read(result);
|
||||
|
||||
assertFalse(count <= 0);
|
||||
assertArrayEquals(Arrays.copyOfRange(data, dataOffset, dataOffset + count), Arrays.copyOfRange(result, 0, count));
|
||||
|
||||
dataOffset += count;
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeArrayOffset() throws IOException {
|
||||
int length = 793;
|
||||
byte[] data = createData(length * 10);
|
||||
|
||||
InputStream source = new ByteArrayInputStream(data);
|
||||
byte[] result = new byte[477];
|
||||
|
||||
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
|
||||
int dataOffset = 0;
|
||||
while (dataOffset < data.length) {
|
||||
int resultOffset = dataOffset % result.length;
|
||||
int count = stream.read(result, resultOffset, result.length - resultOffset);
|
||||
|
||||
assertFalse(count <= 0);
|
||||
assertArrayEquals(Arrays.copyOfRange(data, dataOffset + resultOffset, dataOffset + count), Arrays.copyOfRange(result, resultOffset, count));
|
||||
|
||||
dataOffset += count;
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NullDecoder implements Decoder {
|
||||
@Override
|
||||
public int decode(InputStream stream, ByteBuffer buffer) throws IOException {
|
||||
int read = stream.read(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||
|
||||
if (read > 0) {
|
||||
// Set position, should be equivalent to using buffer.put(stream.read()) until EOF or buffer full
|
||||
buffer.position(read);
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,13 +32,13 @@ package com.twelvemonkeys.io.enc;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.ObjectAbstractTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
@@ -73,7 +73,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] createData(final int pLength) {
|
||||
private byte[] createData(final int pLength) throws Exception {
|
||||
byte[] bytes = new byte[pLength];
|
||||
RANDOM.nextBytes(bytes);
|
||||
return bytes;
|
||||
@@ -82,8 +82,9 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
private void runStreamTest(final int pLength) throws Exception {
|
||||
byte[] data = createData(pLength);
|
||||
ByteArrayOutputStream outBytes = new ByteArrayOutputStream();
|
||||
OutputStream out = new EncoderStream(outBytes, createEncoder(), true);
|
||||
|
||||
try (OutputStream out = new EncoderStream(outBytes, createEncoder(), true)) {
|
||||
try {
|
||||
// Provoke failure for encoders that doesn't take array offset properly into account
|
||||
int off = (data.length + 1) / 2;
|
||||
out.write(data, 0, off);
|
||||
@@ -91,6 +92,9 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
out.write(data, off, data.length - off);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
|
||||
byte[] encoded = outBytes.toByteArray();
|
||||
|
||||
@@ -98,7 +102,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
// System.err.println("encoded: " + Arrays.toString(encoded));
|
||||
|
||||
byte[] decoded = FileUtil.read(new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder()));
|
||||
assertArrayEquals(data, decoded);
|
||||
assertTrue(Arrays.equals(data, decoded));
|
||||
|
||||
InputStream in = new DecoderStream(new ByteArrayInputStream(encoded), createCompatibleDecoder());
|
||||
outBytes = new ByteArrayOutputStream();
|
||||
@@ -112,7 +116,7 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
}
|
||||
|
||||
decoded = outBytes.toByteArray();
|
||||
assertArrayEquals(data, decoded);
|
||||
assertTrue(Arrays.equals(data, decoded));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -125,6 +129,10 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage() + ": " + i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage() + ": " + i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 100; i < 2000; i += 250) {
|
||||
@@ -135,6 +143,10 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage() + ": " + i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage() + ": " + i);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 2000; i < 80000; i += 1000) {
|
||||
@@ -145,8 +157,14 @@ public abstract class EncoderAbstractTest extends ObjectAbstractTest {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage() + ": " + i);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(e.getMessage() + ": " + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Test that the transition from byte[] to ByteBuffer didn't introduce bugs when writing to a wrapped array with offset.
|
||||
// TODO: Test that the transition from byte[] to ByteBuffer didn't introduce bugs when writing to a wrapped array with offset.
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class EncoderStreamTest {
|
||||
|
||||
private final Random rng = new Random(5467809876546L);
|
||||
|
||||
private byte[] createData(final int length) {
|
||||
byte[] data = new byte[length];
|
||||
rng.nextBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSingleBytes() throws IOException {
|
||||
byte[] data = createData(1327);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||
for (byte datum : data) {
|
||||
stream.write(datum);
|
||||
}
|
||||
}
|
||||
|
||||
assertArrayEquals(data, result.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeArray() throws IOException {
|
||||
byte[] data = createData(1793);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
stream.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] encoded = result.toByteArray();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertArrayEquals(data, Arrays.copyOfRange(encoded, i * data.length, (i + 1) * data.length));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeArrayOffset() throws IOException {
|
||||
byte[] data = createData(87);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
stream.write(data, 13, 59);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] original = Arrays.copyOfRange(data, 13, 13 + 59);
|
||||
byte[] encoded = result.toByteArray();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertArrayEquals(original, Arrays.copyOfRange(encoded, i * original.length, (i + 1) * original.length));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NullEncoder implements Encoder {
|
||||
@Override
|
||||
public void encode(OutputStream stream, ByteBuffer buffer) throws IOException {
|
||||
stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -13,8 +13,4 @@
|
||||
The TwelveMonkeys Common Language support
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.common.lang</project.jpms.module.name>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -770,7 +770,6 @@ public final class StringUtil {
|
||||
*/
|
||||
|
||||
/*public*/
|
||||
@Deprecated
|
||||
static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
|
||||
StringBuilder result = new StringBuilder();
|
||||
|
||||
@@ -904,7 +903,7 @@ public final class StringUtil {
|
||||
}
|
||||
catch (ParseException pe) {
|
||||
// Wrap in RuntimeException
|
||||
throw new IllegalArgumentException(pe.getMessage() + " at pos " + pe.getErrorOffset());
|
||||
throw new IllegalArgumentException(pe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1465,7 +1464,6 @@ public final class StringUtil {
|
||||
*/
|
||||
|
||||
/*public*/
|
||||
@Deprecated
|
||||
static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
|
||||
StringBuilder filteredString = new StringBuilder();
|
||||
boolean insideDemarcatedArea = false;
|
||||
|
||||
@@ -330,7 +330,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple Map.Entry implementation.
|
||||
* A simple Map.Entry implementaton.
|
||||
*/
|
||||
static class BasicEntry<K, V> implements Entry<K, V>, Serializable {
|
||||
K mKey;
|
||||
|
||||
@@ -157,7 +157,6 @@ public class Time {
|
||||
* @see #parseTime(String)
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public String toString(String pFormatStr) {
|
||||
TimeFormat tf = new TimeFormat(pFormatStr);
|
||||
|
||||
@@ -175,7 +174,6 @@ public class Time {
|
||||
* @see #toString(String)
|
||||
* @deprecated
|
||||
*/
|
||||
@Deprecated
|
||||
public static Time parseTime(String pStr) {
|
||||
TimeFormat tf = TimeFormat.getInstance();
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* A {@code Map} implementation that removes (expires) its elements after
|
||||
* A {@code Map} implementation that removes (exipres) its elements after
|
||||
* a given period. The map is by default backed by a {@link java.util.HashMap},
|
||||
* or can be instantiated with any given {@code Map} as backing.
|
||||
* <p>
|
||||
@@ -67,7 +67,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
protected long expiryTime = 60000L; // 1 minute
|
||||
|
||||
//////////////////////
|
||||
private volatile long nextExpiryTime = Long.MAX_VALUE;
|
||||
private volatile long nextExpiryTime;
|
||||
//////////////////////
|
||||
|
||||
/**
|
||||
@@ -178,7 +178,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
* @return {@code true} if this map contains no key-value mappings.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return size() <= 0;
|
||||
return (size() <= 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +208,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
* @see #containsKey(java.lang.Object)
|
||||
*/
|
||||
public V get(Object pKey) {
|
||||
TimedEntry entry = (TimedEntry) entries.get(pKey);
|
||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey);
|
||||
|
||||
if (entry == null) {
|
||||
return null;
|
||||
@@ -236,7 +236,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
* {@code null} values.
|
||||
*/
|
||||
public V put(K pKey, V pValue) {
|
||||
TimedEntry entry = (TimedEntry) entries.get(pKey);
|
||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.get(pKey);
|
||||
V oldValue;
|
||||
|
||||
if (entry == null) {
|
||||
@@ -272,7 +272,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
* {@code null} values.
|
||||
*/
|
||||
public V remove(Object pKey) {
|
||||
TimedEntry entry = (TimedEntry) entries.remove(pKey);
|
||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) entries.remove(pKey);
|
||||
return (entry != null) ? entry.getValue() : null;
|
||||
}
|
||||
|
||||
@@ -284,12 +284,13 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
init();
|
||||
}
|
||||
|
||||
/*protected*/ TimedEntry createEntry(K pKey, V pValue) {
|
||||
return new TimedEntry(pKey, pValue);
|
||||
/*protected*/ TimedEntry<K, V> createEntry(K pKey, V pValue) {
|
||||
return new TimedEntry<K, V>(pKey, pValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any expired mappings.
|
||||
*
|
||||
*/
|
||||
protected void removeExpiredEntries() {
|
||||
// Remove any expired elements
|
||||
@@ -311,7 +312,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
long next = Long.MAX_VALUE;
|
||||
nextExpiryTime = next; // Avoid multiple runs...
|
||||
for (Iterator<Entry<K, V>> iterator = new EntryIterator(); iterator.hasNext();) {
|
||||
TimedEntry entry = (TimedEntry) iterator.next();
|
||||
TimedEntry<K, V> entry = (TimedEntry<K, V>) iterator.next();
|
||||
////
|
||||
long expires = entry.expires();
|
||||
if (expires < next) {
|
||||
@@ -375,7 +376,7 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
|
||||
while (mNext == null && mIterator.hasNext()) {
|
||||
Entry<K, Entry<K, V>> entry = mIterator.next();
|
||||
TimedEntry timed = (TimedEntry) entry.getValue();
|
||||
TimedEntry<K, V> timed = (TimedEntry<K, V>) entry.getValue();
|
||||
|
||||
if (timed.isExpiredBy(mNow)) {
|
||||
// Remove from map, and continue
|
||||
@@ -424,26 +425,17 @@ public class TimeoutMap<K, V> extends AbstractDecoratedMap<K, V> implements Expi
|
||||
/**
|
||||
* Keeps track of timed objects
|
||||
*/
|
||||
private class TimedEntry extends BasicEntry<K, V> {
|
||||
private class TimedEntry<K, V> extends BasicEntry<K, V> {
|
||||
private long mTimestamp;
|
||||
|
||||
TimedEntry(K pKey, V pValue) {
|
||||
super(pKey, pValue);
|
||||
updateTimestamp();
|
||||
mTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public V setValue(V pValue) {
|
||||
updateTimestamp();
|
||||
return super.setValue(pValue);
|
||||
}
|
||||
|
||||
private void updateTimestamp() {
|
||||
mTimestamp = System.currentTimeMillis();
|
||||
|
||||
long expires = expires();
|
||||
if (expires < nextExpiryTime) {
|
||||
nextExpiryTime = expires;
|
||||
}
|
||||
return super.setValue(pValue);
|
||||
}
|
||||
|
||||
final boolean isExpired() {
|
||||
|
||||
-1
@@ -111,7 +111,6 @@ import java.io.PrintStream;
|
||||
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||
* @deprecated Will probably be removed in the near future
|
||||
*/
|
||||
@Deprecated
|
||||
public class WildcardStringParser {
|
||||
// TODO: Get rid of this class
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
package com.twelvemonkeys.lang;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.*;
|
||||
import java.sql.Timestamp;
|
||||
@@ -41,7 +41,7 @@ import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* StringUtilTestCase
|
||||
@@ -76,24 +76,24 @@ public class StringUtilTest {
|
||||
assertNull(StringUtil.valueOf(null));
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testToUpperCase() {
|
||||
String str = StringUtil.toUpperCase(TEST_STRING);
|
||||
assertNotNull(str);
|
||||
assertEquals(TEST_STRING.toUpperCase(), str);
|
||||
|
||||
assertNull(StringUtil.toUpperCase(null));
|
||||
str = StringUtil.toUpperCase(null);
|
||||
assertNull(str);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testToLowerCase() {
|
||||
String str = StringUtil.toLowerCase(TEST_STRING);
|
||||
assertNotNull(str);
|
||||
assertEquals(TEST_STRING.toLowerCase(), str);
|
||||
|
||||
assertNull(StringUtil.toLowerCase(null));
|
||||
str = StringUtil.toLowerCase(null);
|
||||
assertNull(str);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -113,7 +113,6 @@ public class StringUtilTest {
|
||||
assertFalse(StringUtil.isEmpty(new String[]{WHITESPACE_STRING, TEST_STRING}));
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testContains() {
|
||||
assertTrue(StringUtil.contains(TEST_STRING, TEST_STRING));
|
||||
@@ -146,7 +145,6 @@ public class StringUtilTest {
|
||||
assertFalse(StringUtil.containsIgnoreCase(null, null));
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Test
|
||||
public void testContainsChar() {
|
||||
for (int i = 0; i < TEST_STRING.length(); i++) {
|
||||
@@ -468,7 +466,7 @@ public class StringUtilTest {
|
||||
assertEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING));
|
||||
assertEquals(TEST_STRING, StringUtil.ltrim(" " + TEST_STRING));
|
||||
assertEquals(TEST_STRING, StringUtil.ltrim(WHITESPACE_STRING + TEST_STRING));
|
||||
assertNotEquals(TEST_STRING, StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING));
|
||||
assertFalse(TEST_STRING.equals(StringUtil.ltrim(TEST_STRING + WHITESPACE_STRING)));
|
||||
// TODO: Test is not complete
|
||||
}
|
||||
|
||||
@@ -477,7 +475,7 @@ public class StringUtilTest {
|
||||
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING));
|
||||
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + " "));
|
||||
assertEquals(TEST_STRING, StringUtil.rtrim(TEST_STRING + WHITESPACE_STRING));
|
||||
assertNotEquals(TEST_STRING, StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING));
|
||||
assertFalse(TEST_STRING.equals(StringUtil.rtrim(WHITESPACE_STRING + TEST_STRING)));
|
||||
// TODO: Test is not complete
|
||||
}
|
||||
|
||||
@@ -518,7 +516,7 @@ public class StringUtilTest {
|
||||
public void testCaptialize() {
|
||||
assertNull(StringUtil.capitalize(null));
|
||||
assertEquals(TEST_STRING.toUpperCase(), StringUtil.capitalize(TEST_STRING.toUpperCase()));
|
||||
assertEquals('A', StringUtil.capitalize("abc").charAt(0));
|
||||
assertTrue(StringUtil.capitalize("abc").charAt(0) == 'A');
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -554,13 +552,13 @@ public class StringUtilTest {
|
||||
public void testToDateWithFormatString() {
|
||||
Calendar cal = new GregorianCalendar();
|
||||
cal.clear();
|
||||
cal.set(1976, Calendar.MARCH, 16); // Month is 0-based
|
||||
cal.set(1976, 2, 16); // Month is 0-based
|
||||
Date date = StringUtil.toDate("16.03.1976", "dd.MM.yyyy");
|
||||
assertNotNull(date);
|
||||
assertEquals(cal.getTime(), date);
|
||||
|
||||
cal.clear();
|
||||
cal.set(2004, Calendar.MAY, 13, 23, 51, 3);
|
||||
cal.set(2004, 4, 13, 23, 51, 3);
|
||||
date = StringUtil.toDate("2004-5-13 23:51 (03)", "yyyy-MM-dd hh:mm (ss)");
|
||||
assertNotNull(date);
|
||||
assertEquals(cal.getTime(), date);
|
||||
@@ -578,23 +576,23 @@ public class StringUtilTest {
|
||||
public void testToDateWithFormat() {
|
||||
Calendar cal = new GregorianCalendar();
|
||||
cal.clear();
|
||||
cal.set(1976, Calendar.MARCH, 16); // Month is 0-based
|
||||
cal.set(1976, 2, 16); // Month is 0-based
|
||||
Date date = StringUtil.toDate("16.03.1976", new SimpleDateFormat("dd.MM.yyyy"));
|
||||
assertNotNull(date);
|
||||
assertEquals(cal.getTime(), date);
|
||||
|
||||
cal.clear();
|
||||
cal.set(2004, Calendar.MAY, 13, 23, 51);
|
||||
DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO"));
|
||||
date = StringUtil.toDate(format.format(cal.getTime()), format);
|
||||
cal.set(2004, 4, 13, 23, 51);
|
||||
date = StringUtil.toDate("13.5.04 23:51",
|
||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, new Locale("no", "NO")));
|
||||
assertNotNull(date);
|
||||
assertEquals(cal.getTime(), date);
|
||||
|
||||
cal.clear();
|
||||
cal.set(Calendar.HOUR, 1);
|
||||
cal.set(Calendar.MINUTE, 2);
|
||||
format = new SimpleDateFormat("HH:mm");
|
||||
date = StringUtil.toDate("1:02", format);
|
||||
date = StringUtil.toDate("1:02 am",
|
||||
DateFormat.getTimeInstance(DateFormat.SHORT, Locale.US));
|
||||
assertNotNull(date);
|
||||
assertEquals(cal.getTime(), date);
|
||||
}
|
||||
@@ -603,9 +601,10 @@ public class StringUtilTest {
|
||||
public void testToTimestamp() {
|
||||
Calendar cal = new GregorianCalendar();
|
||||
cal.clear();
|
||||
cal.set(1976, Calendar.MARCH, 16, 21, 28, 4); // Month is 0-based
|
||||
Timestamp date = StringUtil.toTimestamp("1976-03-16 21:28:04");
|
||||
cal.set(1976, 2, 16, 21, 28, 4); // Month is 0-based
|
||||
Date date = StringUtil.toTimestamp("1976-03-16 21:28:04");
|
||||
assertNotNull(date);
|
||||
assertTrue(date instanceof Timestamp);
|
||||
assertEquals(cal.getTime(), date);
|
||||
}
|
||||
|
||||
@@ -822,7 +821,7 @@ public class StringUtilTest {
|
||||
assertTrue(StringUtil.isNumber("12345"));
|
||||
assertTrue(StringUtil.isNumber(TEST_INTEGER.toString()));
|
||||
assertTrue(StringUtil.isNumber("1234567890123456789012345678901234567890"));
|
||||
assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + Long.MAX_VALUE));
|
||||
assertTrue(StringUtil.isNumber(String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE)));
|
||||
assertFalse(StringUtil.isNumber("abc"));
|
||||
assertFalse(StringUtil.isNumber(TEST_STRING));
|
||||
}
|
||||
@@ -832,7 +831,7 @@ public class StringUtilTest {
|
||||
assertTrue(StringUtil.isNumber("-12345"));
|
||||
assertTrue(StringUtil.isNumber('-' + TEST_INTEGER.toString()));
|
||||
assertTrue(StringUtil.isNumber("-1234567890123456789012345678901234567890"));
|
||||
assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + Long.MAX_VALUE));
|
||||
assertTrue(StringUtil.isNumber('-' + String.valueOf(Long.MAX_VALUE) + String.valueOf(Long.MAX_VALUE)));
|
||||
assertFalse(StringUtil.isNumber("-abc"));
|
||||
assertFalse(StringUtil.isNumber('-' + TEST_STRING));
|
||||
}
|
||||
|
||||
@@ -557,7 +557,7 @@ public class TimeoutMapTest extends MapAbstractTest {
|
||||
// NOTE: Only wait fist time, to avoid slooow tests
|
||||
synchronized (this) {
|
||||
try {
|
||||
wait(60L);
|
||||
wait(60l);
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
@@ -591,7 +591,7 @@ public class TimeoutMapTest extends MapAbstractTest {
|
||||
try {
|
||||
wait(60l);
|
||||
}
|
||||
catch (InterruptedException ignore) {
|
||||
catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -651,24 +651,5 @@ public class TimeoutMapTest extends MapAbstractTest {
|
||||
assertTrue("Wrong entry removed, keySet().iterator() is broken.", !map.containsKey(removedKey));
|
||||
assertTrue("Wrong entry removed, keySet().iterator() is broken.", map.containsKey(otherKey));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testContainsKeyOnEmptyMap() {
|
||||
// See #600
|
||||
Map<String, String> timeoutMap = new TimeoutMap<>(30);
|
||||
assertFalse(timeoutMap.containsKey("xyz"));
|
||||
timeoutMap.put("xyz", "xyz");
|
||||
assertTrue(timeoutMap.containsKey("xyz"));
|
||||
|
||||
try {
|
||||
Thread.sleep(50); // Let the item expire
|
||||
}
|
||||
catch (InterruptedException ignore) {
|
||||
}
|
||||
|
||||
assertFalse(timeoutMap.containsKey("xyz"));
|
||||
assertNull(timeoutMap.get("xyz"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
@@ -47,7 +47,7 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||
<artifactId>contrib</artifactId>
|
||||
@@ -65,7 +65,7 @@
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.13.2</version>
|
||||
<version>4.13.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
@@ -2,7 +2,6 @@ package com.twelvemonkeys.contrib.exif;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.IIOImage;
|
||||
@@ -30,14 +29,9 @@ import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation;
|
||||
public class EXIFUtilities {
|
||||
/**
|
||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||
*
|
||||
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||
*
|
||||
* @param input a {@code URL}
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||
* {@code null}.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
||||
@@ -48,14 +42,9 @@ public class EXIFUtilities {
|
||||
|
||||
/**
|
||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||
*
|
||||
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||
*
|
||||
* @param input an {@code InputStream}
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||
* {@code null}.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
||||
@@ -66,14 +55,9 @@ public class EXIFUtilities {
|
||||
|
||||
/**
|
||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||
*
|
||||
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||
*
|
||||
* @param input a {@code File}
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info or
|
||||
* {@code null}.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final File input) throws IOException {
|
||||
@@ -84,14 +68,9 @@ public class EXIFUtilities {
|
||||
|
||||
/**
|
||||
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
|
||||
* The returned {@code IIOImage} will always contain an image and no raster, and
|
||||
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
|
||||
*
|
||||
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
|
||||
*
|
||||
* @param input an {@code ImageInputStream}
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||
* {@code null}.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
||||
@@ -103,11 +82,12 @@ public class EXIFUtilities {
|
||||
ImageReader reader = readers.next();
|
||||
try {
|
||||
reader.setInput(input, true, false);
|
||||
IIOImage image = reader.readAll(0, reader.getDefaultReadParam());
|
||||
|
||||
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||
BufferedImage bufferedImage = applyOrientation(reader.read(0), findImageOrientation(metadata).value());
|
||||
BufferedImage bufferedImage = ImageUtil.toBuffered(image.getRenderedImage());
|
||||
image.setRenderedImage(applyOrientation(bufferedImage, findImageOrientation(image.getMetadata()).value()));
|
||||
|
||||
return new IIOImage(bufferedImage, null, metadata);
|
||||
return image;
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
@@ -143,15 +123,9 @@ public class EXIFUtilities {
|
||||
for (String arg : args) {
|
||||
File input = new File(arg);
|
||||
|
||||
// Read everything but thumbnails (similar to ImageReader.readAll(0, null)),
|
||||
// and applies the correct image orientation
|
||||
// Read everything (similar to ImageReader.readAll(0, null)), but applies the correct image orientation
|
||||
IIOImage image = readWithOrientation(input);
|
||||
|
||||
if (image == null) {
|
||||
System.err.printf("No reader for %s%n", input);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Finds the orientation as defined by the javax_imageio_1.0 format
|
||||
Orientation orientation = findImageOrientation(image.getMetadata());
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@
|
||||
package com.twelvemonkeys.contrib.tiff;
|
||||
|
||||
import com.twelvemonkeys.contrib.tiff.TIFFUtilities.TIFFExtension;
|
||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFImageMetadataFormat;
|
||||
import com.twelvemonkeys.imageio.plugins.tiff.TIFFMedataFormat;
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.Node;
|
||||
@@ -155,7 +154,7 @@ public class TIFFUtilitiesTest {
|
||||
reader.setInput(checkTest1);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Node metaData = reader.getImageMetadata(i)
|
||||
.getAsTree(TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||
Assert.assertEquals(orientation, TIFFExtension.ORIENTATION_RIGHTTOP);
|
||||
}
|
||||
@@ -172,7 +171,7 @@ public class TIFFUtilitiesTest {
|
||||
reader.setInput(checkTest2);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Node metaData = reader.getImageMetadata(i)
|
||||
.getAsTree(TIFFImageMetadataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
.getAsTree(TIFFMedataFormat.SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME);
|
||||
short orientation = ((Number) expression.evaluate(metaData, XPathConstants.NUMBER)).shortValue();
|
||||
Assert.assertEquals(orientation, i == 1
|
||||
? TIFFExtension.ORIENTATION_BOTRIGHT
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -14,12 +14,6 @@
|
||||
See the <a href="http://xmlgraphics.apache.org/batik/">Batik Home page</a>
|
||||
for more information.]]>
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
|
||||
<batik.version>1.16</batik.version>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
@@ -27,9 +21,9 @@
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
||||
true
|
||||
</com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
@@ -48,13 +42,6 @@
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>commons-io</groupId>
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>2.13.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-rasterizer-ext</artifactId>
|
||||
@@ -75,6 +62,13 @@
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>xmlgraphics-commons</artifactId>
|
||||
<version>2.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.xmlgraphics</groupId>
|
||||
<artifactId>batik-anim</artifactId>
|
||||
@@ -98,7 +92,7 @@
|
||||
<!--
|
||||
There seems to be some weirdness in the
|
||||
Batik/FOP poms (Batik depends on FOP 0.20-5) that screws things up,
|
||||
making everything end up depending on Batik 1.5, not the specified version
|
||||
making everything end up depending on Batik 1.5, not 1.6
|
||||
-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -108,4 +102,8 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<batik.version>1.12</batik.version>
|
||||
</properties>
|
||||
</project>
|
||||
|
||||
+96
-104
@@ -34,10 +34,10 @@ import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||
import org.apache.batik.bridge.*;
|
||||
import org.apache.batik.css.parser.CSSLexicalUnit;
|
||||
import org.apache.batik.dom.util.DOMUtilities;
|
||||
import org.apache.batik.ext.awt.image.GraphicsUtil;
|
||||
import org.apache.batik.gvt.CanvasGraphicsNode;
|
||||
@@ -45,14 +45,11 @@ import org.apache.batik.gvt.GraphicsNode;
|
||||
import org.apache.batik.gvt.renderer.ConcreteImageRendererFactory;
|
||||
import org.apache.batik.gvt.renderer.ImageRenderer;
|
||||
import org.apache.batik.gvt.renderer.ImageRendererFactory;
|
||||
import org.apache.batik.transcoder.SVGAbstractTranscoder;
|
||||
import org.apache.batik.transcoder.TranscoderException;
|
||||
import org.apache.batik.transcoder.TranscoderInput;
|
||||
import org.apache.batik.transcoder.TranscoderOutput;
|
||||
import org.apache.batik.transcoder.TranscodingHints;
|
||||
import org.apache.batik.transcoder.*;
|
||||
import org.apache.batik.transcoder.image.ImageTranscoder;
|
||||
import org.apache.batik.util.ParsedURL;
|
||||
import org.apache.batik.util.SVGConstants;
|
||||
import org.apache.batik.xml.LexicalUnits;
|
||||
import org.w3c.dom.DOMImplementation;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.svg.SVGSVGElement;
|
||||
@@ -62,8 +59,10 @@ import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.geom.*;
|
||||
import java.awt.image.*;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Dimension2D;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -77,13 +76,12 @@ import java.util.Map;
|
||||
* @author Harald Kuhr
|
||||
* @author Inpspired by code from the Batik Team
|
||||
* @version $Id: $
|
||||
* @see <a href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</a>
|
||||
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
|
||||
*/
|
||||
public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
|
||||
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowExternalResources",
|
||||
System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources")));
|
||||
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources"));
|
||||
|
||||
private Rasterizer rasterizer;
|
||||
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
|
||||
@@ -134,7 +132,6 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
// Set ImageReadParams as hints
|
||||
// Note: The cast to Map invokes a different method that preserves
|
||||
// unset defaults, DO NOT REMOVE!
|
||||
//noinspection rawtypes
|
||||
rasterizer.setTranscodingHints((Map) paramsToHints(svgParam));
|
||||
}
|
||||
|
||||
@@ -149,23 +146,29 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height);
|
||||
|
||||
// Read in the image, using the Batik Transcoder
|
||||
processImageStarted(pIndex);
|
||||
|
||||
BufferedImage image = rasterizer.getImage();
|
||||
|
||||
Graphics2D g = destination.createGraphics();
|
||||
try {
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
processImageStarted(pIndex);
|
||||
|
||||
processImageComplete();
|
||||
BufferedImage image = rasterizer.getImage();
|
||||
|
||||
return destination;
|
||||
Graphics2D g = destination.createGraphics();
|
||||
try {
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
||||
}
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
Throwable cause = unwrapException(e);
|
||||
throw new IIOException(cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
|
||||
private static Throwable unwrapException(TranscoderException ex) {
|
||||
@@ -180,11 +183,11 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
// Set dimensions
|
||||
Dimension size = pParam.getSourceRenderSize();
|
||||
Rectangle viewBox = rasterizer.getViewBox();
|
||||
Dimension origSize = new Dimension(getWidth(0), getHeight(0));
|
||||
if (size == null) {
|
||||
// SVG is not a pixel based format, but we'll scale it, according to
|
||||
// the subsampling for compatibility
|
||||
size = getSourceRenderSizeFromSubsamping(pParam, viewBox.getSize());
|
||||
size = getSourceRenderSizeFromSubsamping(pParam, origSize);
|
||||
}
|
||||
|
||||
if (size != null) {
|
||||
@@ -204,8 +207,8 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
// Need to resize here...
|
||||
double xScale = size.getWidth() / viewBox.getWidth();
|
||||
double yScale = size.getHeight() / viewBox.getHeight();
|
||||
double xScale = size.getWidth() / origSize.getWidth();
|
||||
double yScale = size.getHeight() / origSize.getHeight();
|
||||
|
||||
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
|
||||
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
|
||||
@@ -213,7 +216,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else if (size != null) {
|
||||
// Allow non-uniform scaling
|
||||
hints.put(ImageTranscoder.KEY_AOI, viewBox);
|
||||
hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize));
|
||||
}
|
||||
|
||||
// Background color
|
||||
@@ -228,7 +231,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) {
|
||||
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
|
||||
return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()),
|
||||
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
|
||||
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -239,16 +242,25 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
|
||||
return rasterizer.getDefaultWidth();
|
||||
try {
|
||||
return rasterizer.getDefaultWidth();
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
return rasterizer.getDefaultHeight();
|
||||
try {
|
||||
return rasterizer.getDefaultHeight();
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
|
||||
return Collections.singleton(ImageTypeSpecifier.createFromRenderedImage(rasterizer.createImage(1, 1))).iterator();
|
||||
}
|
||||
|
||||
@@ -259,11 +271,12 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
* and needs major refactoring!
|
||||
* </p>
|
||||
*/
|
||||
private class Rasterizer extends SVGAbstractTranscoder {
|
||||
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
|
||||
|
||||
private BufferedImage image;
|
||||
private TranscoderInput transcoderInput;
|
||||
private final Rectangle2D viewBox = new Rectangle2D.Float();
|
||||
private final Dimension defaultSize = new Dimension();
|
||||
private float defaultWidth;
|
||||
private float defaultHeight;
|
||||
private boolean initialized = false;
|
||||
private SVGOMDocument document;
|
||||
private String uri;
|
||||
@@ -276,7 +289,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// This is cheating... We don't fully transcode after all
|
||||
protected void transcode(Document document, final String uri, final TranscoderOutput output) {
|
||||
protected void transcode(Document document, final String uri, final TranscoderOutput output) throws TranscoderException {
|
||||
// Sets up root, curTxf & curAoi
|
||||
// ----
|
||||
if (document != null) {
|
||||
@@ -324,66 +337,54 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
// ----
|
||||
SVGSVGElement rootElement = svgDoc.getRootElement();
|
||||
|
||||
// Get the viewBox
|
||||
String viewBoxStr = rootElement.getAttributeNS(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
|
||||
if (viewBoxStr.length() != 0) {
|
||||
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
|
||||
viewBox.setFrame(rect[0], rect[1], rect[2], rect[3]);
|
||||
}
|
||||
|
||||
// Get the 'width' and 'height' attributes of the SVG document
|
||||
double width = 0;
|
||||
double height = 0;
|
||||
UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, rootElement);
|
||||
// get the 'width' and 'height' attributes of the SVG document
|
||||
UnitProcessor.Context uctx
|
||||
= UnitProcessor.createContext(ctx, rootElement);
|
||||
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
|
||||
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
|
||||
if (!StringUtil.isEmpty(widthStr)) {
|
||||
width = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
||||
defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
||||
}
|
||||
if (!StringUtil.isEmpty(heightStr)) {
|
||||
height = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
||||
if(!StringUtil.isEmpty(heightStr)){
|
||||
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
||||
}
|
||||
|
||||
boolean hasWidth = width > 0.0;
|
||||
boolean hasHeight = height > 0.0;
|
||||
boolean hasWidth = defaultWidth > 0.0;
|
||||
boolean hasHeight = defaultHeight > 0.0;
|
||||
|
||||
if (!hasWidth || !hasHeight) {
|
||||
if (!viewBox.isEmpty()) {
|
||||
// If one dimension is given, calculate other by aspect ratio in viewBox
|
||||
String viewBoxStr = rootElement.getAttributeNS
|
||||
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
|
||||
if (viewBoxStr.length() != 0) {
|
||||
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
|
||||
// if one dimension is given, calculate other by aspect ratio in viewBox
|
||||
// or use viewBox if no dimension is given
|
||||
if (hasWidth) {
|
||||
height = width * viewBox.getHeight() / viewBox.getWidth();
|
||||
defaultHeight = defaultWidth * rect[3] / rect[2];
|
||||
}
|
||||
else if (hasHeight) {
|
||||
width = height * viewBox.getWidth() / viewBox.getHeight();
|
||||
defaultWidth = defaultHeight * rect[2] / rect[3];
|
||||
}
|
||||
else {
|
||||
// ...or use viewBox if no dimension is given
|
||||
width = viewBox.getWidth();
|
||||
height = viewBox.getHeight();
|
||||
defaultWidth = rect[2];
|
||||
defaultHeight = rect[3];
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No viewBox, just assume square size
|
||||
if (hasHeight) {
|
||||
width = height;
|
||||
defaultWidth = defaultHeight;
|
||||
}
|
||||
else if (hasWidth) {
|
||||
height = width;
|
||||
defaultHeight = defaultWidth;
|
||||
}
|
||||
else {
|
||||
// ...or finally fall back to Batik default sizes
|
||||
width = 400;
|
||||
height = 400;
|
||||
// fallback to batik default sizes
|
||||
defaultWidth = 400;
|
||||
defaultHeight = 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We now have a size, in the rare case we don't have a viewBox; set it to this size
|
||||
defaultSize.setSize(width, height);
|
||||
if (viewBox.isEmpty()) {
|
||||
viewBox.setRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
// Hack to work around exception above
|
||||
if (root != null) {
|
||||
gvtRoot = root;
|
||||
@@ -396,7 +397,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
ctx = null;
|
||||
}
|
||||
|
||||
private BufferedImage readImage() throws IOException {
|
||||
private BufferedImage readImage() throws TranscoderException {
|
||||
init();
|
||||
|
||||
if (abortRequested()) {
|
||||
@@ -421,8 +422,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
if (gvtRoot == null) {
|
||||
Throwable cause = unwrapException(exception);
|
||||
throw new IIOException(cause.getMessage(), cause);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
ctx = context;
|
||||
@@ -440,7 +440,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
|
||||
// ----
|
||||
setImageSize(defaultSize.width, defaultSize.height);
|
||||
setImageSize(defaultWidth, defaultHeight);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -454,17 +454,18 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
try {
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
throw new IIOException(ex.getMessage(), ex);
|
||||
throw new TranscoderException(ex);
|
||||
}
|
||||
|
||||
if (Px.isIdentity() && (width != defaultSize.width || height != defaultSize.height)) {
|
||||
if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) {
|
||||
// The document has no viewBox, we need to resize it by hand.
|
||||
// we want to keep the document size ratio
|
||||
float xscale, yscale;
|
||||
xscale = width / defaultSize.width;
|
||||
yscale = height / defaultSize.height;
|
||||
xscale = width / defaultWidth;
|
||||
yscale = height / defaultHeight;
|
||||
float scale = Math.min(xscale, yscale);
|
||||
Px = AffineTransform.getScaleInstance(scale, scale);
|
||||
}
|
||||
@@ -514,7 +515,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
throw new IIOException(ex.getMessage(), ex);
|
||||
throw new TranscoderException(ex);
|
||||
}
|
||||
|
||||
this.root = gvtRoot;
|
||||
@@ -583,7 +584,9 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
return dest;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IIOException(ex.getMessage(), ex);
|
||||
TranscoderException exception = new TranscoderException(ex.getMessage());
|
||||
exception.initCause(ex);
|
||||
throw exception;
|
||||
}
|
||||
finally {
|
||||
if (context != null) {
|
||||
@@ -592,7 +595,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void init() throws IIOException {
|
||||
private synchronized void init() throws TranscoderException {
|
||||
if (!initialized) {
|
||||
if (transcoderInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
@@ -600,17 +603,11 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
initialized = true;
|
||||
|
||||
try {
|
||||
super.transcode(transcoderInput, null);
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
Throwable cause = unwrapException(e);
|
||||
throw new IIOException(cause.getMessage(), cause);
|
||||
}
|
||||
super.transcode(transcoderInput, null);
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage getImage() throws IOException {
|
||||
private BufferedImage getImage() throws TranscoderException {
|
||||
if (image == null) {
|
||||
image = readImage();
|
||||
}
|
||||
@@ -618,19 +615,14 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
return image;
|
||||
}
|
||||
|
||||
int getDefaultWidth() throws IOException {
|
||||
int getDefaultWidth() throws TranscoderException {
|
||||
init();
|
||||
return defaultSize.width;
|
||||
return (int) Math.ceil(defaultWidth);
|
||||
}
|
||||
|
||||
int getDefaultHeight() throws IOException {
|
||||
int getDefaultHeight() throws TranscoderException {
|
||||
init();
|
||||
return defaultSize.height;
|
||||
}
|
||||
|
||||
Rectangle getViewBox() throws IOException {
|
||||
init();
|
||||
return viewBox.getBounds();
|
||||
return (int) Math.ceil(defaultHeight);
|
||||
}
|
||||
|
||||
void setInput(final TranscoderInput pInput) {
|
||||
@@ -663,7 +655,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
if (allowExternalResources) {
|
||||
return super.getExternalResourceSecurity(resourceURL, docURL);
|
||||
}
|
||||
return new EmbededExternalResourceSecurity(resourceURL);
|
||||
return new NoLoadExternalResourceSecurity();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+141
-143
@@ -1,143 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
|
||||
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import org.apache.batik.transcoder.TranscoderException;
|
||||
import org.apache.batik.transcoder.TranscoderInput;
|
||||
import org.apache.batik.transcoder.TranscoderOutput;
|
||||
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.image.*;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* WMFImageReader class description.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
|
||||
*/
|
||||
// TODO: Probably possible to do less wrapping/unwrapping of data...
|
||||
public final class WMFImageReader extends ImageReaderBase {
|
||||
|
||||
private SVGImageReader reader = null;
|
||||
|
||||
public WMFImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
if (reader != null) {
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
reader = null;
|
||||
}
|
||||
|
||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
||||
init();
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
BufferedImage image = reader.read(pIndex, pParam);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
return image;
|
||||
}
|
||||
processImageProgress(100f);
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private void init() throws IOException {
|
||||
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
||||
if (imageInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
}
|
||||
|
||||
if (reader == null) {
|
||||
WMFTranscoder transcoder = new WMFTranscoder();
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
|
||||
|
||||
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
|
||||
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||
TranscoderOutput out = new TranscoderOutput(writer);
|
||||
|
||||
// TODO: Transcodinghints?
|
||||
|
||||
transcoder.transcode(in, out);
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
reader = new SVGImageReader(getOriginatingProvider());
|
||||
reader.setInput(new ByteArrayImageInputStream(output.toByteArray()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReadParam getDefaultReadParam() {
|
||||
return new SVGReadParam();
|
||||
}
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
init();
|
||||
return reader.getWidth(pIndex);
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
init();
|
||||
return reader.getHeight(pIndex);
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
||||
init();
|
||||
return reader.getImageTypes(pImageIndex);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
|
||||
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import org.apache.batik.transcoder.TranscoderException;
|
||||
import org.apache.batik.transcoder.TranscoderInput;
|
||||
import org.apache.batik.transcoder.TranscoderOutput;
|
||||
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.*;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* WMFImageReader class description.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
|
||||
*/
|
||||
// TODO: Probably possible to do less wrapping/unwrapping of data...
|
||||
// TODO: Consider using temp file instead of in-memory stream
|
||||
public final class WMFImageReader extends ImageReaderBase {
|
||||
|
||||
private SVGImageReader reader = null;
|
||||
|
||||
public WMFImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
protected void resetMembers() {
|
||||
if (reader != null) {
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
reader = null;
|
||||
}
|
||||
|
||||
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
|
||||
init();
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
BufferedImage image = reader.read(pIndex, pParam);
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
return image;
|
||||
}
|
||||
processImageProgress(100f);
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
private synchronized void init() throws IOException {
|
||||
// Need the extra test, to avoid throwing an IOException from the Transcoder
|
||||
if (imageInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
}
|
||||
|
||||
if (reader == null) {
|
||||
WMFTranscoder transcoder = new WMFTranscoder();
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
Writer writer = new OutputStreamWriter(output, "UTF8");
|
||||
try {
|
||||
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
|
||||
TranscoderOutput out = new TranscoderOutput(writer);
|
||||
|
||||
// TODO: Transcodinghints?
|
||||
|
||||
transcoder.transcode(in, out);
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
reader = new SVGImageReader(getOriginatingProvider());
|
||||
reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageReadParam getDefaultReadParam() {
|
||||
return new SVGReadParam();
|
||||
}
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
init();
|
||||
return reader.getWidth(pIndex);
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
init();
|
||||
return reader.getHeight(pIndex);
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
|
||||
init();
|
||||
return reader.getImageTypes(pImageIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-93
@@ -43,7 +43,8 @@ import javax.imageio.event.IIOReadWarningListener;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ImagingOpException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -52,10 +53,9 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@@ -194,12 +194,12 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
||||
reader.setInput(redSquare.getInputStream());
|
||||
BufferedImage imageRed = reader.read(0, param);
|
||||
assertRGBEquals("Expected all red", 0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||
assertEquals(0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF);
|
||||
|
||||
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
||||
reader.setInput(blueSquare.getInputStream());
|
||||
BufferedImage imageBlue = reader.read(0, param);
|
||||
assertRGBEquals("Expected all blue", 0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||
assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -225,7 +225,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
assertEquals(500, image.getHeight());
|
||||
|
||||
// CSS and embedded resources all go!
|
||||
verifyNoInteractions(listener);
|
||||
verifyZeroInteractions(listener);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
@@ -266,7 +266,7 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
assertEquals(500, image.getHeight());
|
||||
|
||||
// No more warnings now that the base URI is set
|
||||
verifyNoInteractions(listener);
|
||||
verifyZeroInteractions(listener);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
@@ -297,25 +297,6 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadEmbeddedWithDisallowExternalResources() throws IOException{
|
||||
// File using "data:" URLs for embedded resources
|
||||
URL resource = getClassLoaderResource("/svg/embedded-data-resource.svg");
|
||||
SVGImageReader reader = createReader();
|
||||
|
||||
TestData data = new TestData(resource, (Dimension) null);
|
||||
try (ImageInputStream stream = data.getInputStream()) {
|
||||
reader.setInput(stream);
|
||||
|
||||
SVGReadParam param = reader.getDefaultReadParam();
|
||||
param.setAllowExternalResources(false);
|
||||
reader.read(0, param);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected = SecurityException.class)
|
||||
public void testDisallowedExternalResources() throws URISyntaxException, IOException {
|
||||
// system-property set to true in surefire-plugin-settings in the pom
|
||||
@@ -339,69 +320,4 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWitSourceRenderSize() throws IOException {
|
||||
URL resource = getClassLoaderResource("/svg/circle.svg");
|
||||
|
||||
SVGImageReader reader = createReader();
|
||||
|
||||
TestData data = new TestData(resource, (Dimension) null);
|
||||
try (ImageInputStream stream = data.getInputStream()) {
|
||||
reader.setInput(stream);
|
||||
|
||||
SVGReadParam param = reader.getDefaultReadParam();
|
||||
param.setSourceRenderSize(new Dimension(100, 100));
|
||||
BufferedImage image = reader.read(0, param);
|
||||
|
||||
assertNotNull(image);
|
||||
assertEquals(100, image.getWidth());
|
||||
assertEquals(100, image.getHeight());
|
||||
|
||||
// Some quick samples
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 0), 0);
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 99), 0);
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 99), 0);
|
||||
assertRGBEquals("Expected red center", 0xffff0000, image.getRGB(50, 50), 0);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadWitSourceRenderSizeViewBoxNegativeXY() throws IOException {
|
||||
URL resource = getClassLoaderResource("/svg/Android_robot.svg");
|
||||
|
||||
SVGImageReader reader = createReader();
|
||||
|
||||
TestData data = new TestData(resource, (Dimension) null);
|
||||
try (ImageInputStream stream = data.getInputStream()) {
|
||||
reader.setInput(stream);
|
||||
|
||||
SVGReadParam param = reader.getDefaultReadParam();
|
||||
param.setSourceRenderSize(new Dimension(219, 256)); // Aspect scaled to 256 boxed
|
||||
BufferedImage image = reader.read(0, param);
|
||||
|
||||
assertNotNull(image);
|
||||
assertEquals(219, image.getWidth());
|
||||
assertEquals(256, image.getHeight());
|
||||
|
||||
// Some quick samples
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 0), 0);
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 255), 0);
|
||||
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 255), 0);
|
||||
assertRGBEquals("Expected green head", 0xffa4c639, image.getRGB(109, 20), 25);
|
||||
assertRGBEquals("Expected green center", 0xffa4c639, image.getRGB(109, 128), 25);
|
||||
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(80, 246), 25);
|
||||
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(130, 246), 25);
|
||||
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(0, 128), 0);
|
||||
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(218, 128), 0);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
|
||||
<circle cx="25" cy="25" r="25" fill="red"/></svg>
|
||||
|
Before Width: | Height: | Size: 436 B |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 92 KiB |
@@ -4,16 +4,12 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
<description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.imageio.bmp</project.jpms.module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
+58
-56
@@ -30,14 +30,15 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
@@ -50,15 +51,15 @@ import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
|
||||
/**
|
||||
* ImageReader for Microsoft Windows Bitmap (BMP) format.
|
||||
@@ -81,7 +82,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
super(new BMPImageReaderSpi());
|
||||
}
|
||||
|
||||
BMPImageReader(final ImageReaderSpi pProvider) {
|
||||
protected BMPImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
@@ -204,7 +205,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
checkBounds(pImageIndex);
|
||||
|
||||
// TODO: Better implementation, include INT_RGB types for 3BYTE_BGR and 4BYTE_ABGR for INT_ARGB
|
||||
return Collections.singletonList(getRawImageType(pImageIndex)).iterator();
|
||||
return Arrays.asList(getRawImageType(pImageIndex)).iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -362,18 +363,14 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
for (int y = 0; y < height; y++) {
|
||||
int bitCount = header.getBitCount();
|
||||
switch (bitCount) {
|
||||
switch (header.getBitCount()) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
case 24:
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
int bitsPerSample = bitCount == 24 ? 8 : bitCount;
|
||||
int samplesPerPixel = bitCount == 24 ? 3 : 1;
|
||||
|
||||
readRowByte(input, height, srcRegion, xSub, ySub, bitsPerSample, samplesPerPixel, rowDataByte, destRaster, clippedRow, y);
|
||||
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
@@ -387,7 +384,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + bitCount);
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height);
|
||||
@@ -413,13 +410,6 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
|
||||
private ImageReader initReaderDelegate(int compression) throws IOException {
|
||||
ImageReader reader = getImageReaderDelegate(compression);
|
||||
reader.reset();
|
||||
|
||||
// Install listener
|
||||
ListenerDelegator listenerDelegator = new ListenerDelegator();
|
||||
reader.addIIOReadWarningListener(listenerDelegator);
|
||||
reader.addIIOReadProgressListener(listenerDelegator);
|
||||
reader.addIIOReadUpdateListener(listenerDelegator);
|
||||
|
||||
imageInput.seek(pixelOffset);
|
||||
reader.setInput(new SubImageInputStream(imageInput, header.getImageSize()));
|
||||
@@ -460,6 +450,12 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
|
||||
ImageReader reader = readers.next();
|
||||
|
||||
// Install listener
|
||||
ListenerDelegator listenerDelegator = new ListenerDelegator();
|
||||
reader.addIIOReadWarningListener(listenerDelegator);
|
||||
reader.addIIOReadProgressListener(listenerDelegator);
|
||||
reader.addIIOReadUpdateListener(listenerDelegator);
|
||||
|
||||
// Cache for later use
|
||||
switch (compression) {
|
||||
case DIB.COMPRESSION_JPEG:
|
||||
@@ -484,14 +480,9 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||
int bitsPerSample, int samplesPerPixel,
|
||||
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||
// Flip into position?
|
||||
int srcY = !header.topDown ? height - 1 - y : y;
|
||||
int dstY = (srcY - srcRegion.y) / ySub;
|
||||
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
||||
input.skipBytes(rowDataByte.length);
|
||||
|
||||
return;
|
||||
@@ -501,20 +492,24 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
|
||||
// Subsample horizontal
|
||||
if (xSub != 1) {
|
||||
IIOUtil.subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
|
||||
for (int x = 0; x < srcRegion.width / xSub; x++) {
|
||||
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
|
||||
}
|
||||
}
|
||||
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
if (header.topDown) {
|
||||
destChannel.setDataElements(0, y, srcChannel);
|
||||
} else {
|
||||
// Flip into position
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private void readRowUShort(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||
final short[] rowDataUShort, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||
// Flip into position?
|
||||
int srcY = !header.topDown ? height - 1 - y : y;
|
||||
int dstY = (srcY - srcRegion.y) / ySub;
|
||||
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
||||
input.skipBytes(rowDataUShort.length * 2 + (rowDataUShort.length % 2) * 2);
|
||||
|
||||
return;
|
||||
@@ -534,17 +529,19 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
if (header.topDown) {
|
||||
destChannel.setDataElements(0, y, srcChannel);
|
||||
} else {
|
||||
// Flip into position
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
}
|
||||
|
||||
private void readRowInt(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||
final int[] rowDataInt, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
|
||||
// Flip into position?
|
||||
int srcY = !header.topDown ? height - 1 - y : y;
|
||||
int dstY = (srcY - srcRegion.y) / ySub;
|
||||
|
||||
// If subsampled or outside source region, skip entire row
|
||||
if (srcY % ySub != 0 || srcY < srcRegion.y || srcY >= srcRegion.y + srcRegion.height) {
|
||||
if (y % ySub != 0 || height - 1 - y < srcRegion.y || height - 1 - y >= srcRegion.y + srcRegion.height) {
|
||||
input.skipBytes(rowDataInt.length * 4);
|
||||
|
||||
return;
|
||||
@@ -559,7 +556,13 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
if (header.topDown) {
|
||||
destChannel.setDataElements(0, y, srcChannel);
|
||||
} else {
|
||||
// Flip into position
|
||||
int dstY = (height - 1 - y - srcRegion.y) / ySub;
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Candidate util method
|
||||
@@ -630,8 +633,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
return new BMPMetadata(header, colors);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void main(String[] args) {
|
||||
public static void main(String[] args) throws IOException {
|
||||
BMPImageReaderSpi provider = new BMPImageReaderSpi();
|
||||
BMPImageReader reader = new BMPImageReader(provider);
|
||||
|
||||
@@ -684,7 +686,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "UnusedDeclaration", "SameParameterValue" })
|
||||
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
|
||||
static <T extends Throwable> void throwAs(final Class<T> pType, final Throwable pThrowable) throws T {
|
||||
throw (T) pThrowable;
|
||||
}
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
|
||||
}
|
||||
}
|
||||
|
||||
public ImageReader createReaderInstance(final Object pExtension) {
|
||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
||||
return new BMPImageReader(this);
|
||||
}
|
||||
|
||||
|
||||
+1
-5
@@ -364,11 +364,7 @@ abstract class DIBHeader {
|
||||
|
||||
public String getBMPVersion() {
|
||||
// This is to be compatible with the native metadata of the original com.sun....BMPMetadata
|
||||
return size > DIB.BITMAP_INFO_HEADER_SIZE
|
||||
? "BMP V2/V3 INFO"
|
||||
: compression == DIB.COMPRESSION_BITFIELDS || compression == DIB.COMPRESSION_ALPHA_BITFIELDS
|
||||
? "BMP v. 3.x NT"
|
||||
: "BMP v. 3.x";
|
||||
return compression == DIB.COMPRESSION_BITFIELDS ? "BMP v. 3.x NT" : "BMP v. 3.x";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -32,8 +32,8 @@ package com.twelvemonkeys.imageio.plugins.bmp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assume.assumeNoException;
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Matchers.anyInt;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.atLeastOnce;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -295,7 +295,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
InOrder ordered = inOrder(listener);
|
||||
ordered.verify(listener).imageStarted(reader, 0);
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||
ordered.verify(listener).imageComplete(reader);
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
// At least imageStarted and imageComplete, plus any number of imageProgress
|
||||
InOrder ordered = inOrder(listener);
|
||||
ordered.verify(listener).imageStarted(reader, 0);
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyFloat());
|
||||
ordered.verify(listener, atLeastOnce()).imageProgress(eq(reader), anyInt());
|
||||
ordered.verify(listener).imageComplete(reader);
|
||||
}
|
||||
|
||||
@@ -342,7 +342,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
|
||||
for (TestData data : getTestData()) {
|
||||
if (data.getInput().toString().contains("pal8offs")) {
|
||||
// Skip: Contains extra bogus PaletteEntry nodes
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -359,7 +358,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
System.err.println("WARNING: Reading " + data + " caused exception: " + e.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
IIOMetadata jreMetadata = jreReader.getImageMetadata(0);
|
||||
|
||||
assertTrue(metadata.isStandardMetadataFormatSupported());
|
||||
@@ -372,7 +370,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
String absolutePath = data.toString();
|
||||
String localPath = absolutePath.substring(absolutePath.lastIndexOf("test-classes") + 12);
|
||||
|
||||
// TODO: blauesglas_16_bitmask444 fails BMP Version for 11+
|
||||
Node expectedTree = jreMetadata.getAsTree(format);
|
||||
Node actualTree = metadata.getAsTree(format);
|
||||
|
||||
@@ -431,7 +428,6 @@ public class BMPImageReaderTest extends ImageReaderAbstractTest<BMPImageReader>
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantIfStatement")
|
||||
private boolean excludeEqualValueTest(final Node expected) {
|
||||
if (expected.getLocalName().equals("ImageSize")) {
|
||||
// JRE metadata returns 0, even if known in reader...
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 257 KiB |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||
@@ -12,10 +12,6 @@
|
||||
Photoshop Clipping Path Support.
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.imageio.clippath</project.jpms.module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
-1
@@ -39,7 +39,6 @@ import java.io.IOException;
|
||||
*
|
||||
* @deprecated Use {@link AdobePathReader} instead. This class will be removed in a future release.
|
||||
*/
|
||||
@Deprecated
|
||||
public final class AdobePathBuilder {
|
||||
|
||||
private final AdobePathReader delegate;
|
||||
|
||||
@@ -56,8 +56,6 @@ import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -133,13 +131,7 @@ public final class Paths {
|
||||
List<JPEGSegment> photoshop = JPEGSegmentUtil.readSegments(stream, segmentIdentifiers);
|
||||
|
||||
if (!photoshop.isEmpty()) {
|
||||
InputStream data = null;
|
||||
|
||||
for (JPEGSegment ps : photoshop) {
|
||||
data = data == null ? ps.data() : new SequenceInputStream(data, ps.data());
|
||||
}
|
||||
|
||||
return readPathFromPhotoshopResources(new MemoryCacheImageInputStream(data));
|
||||
return readPathFromPhotoshopResources(new MemoryCacheImageInputStream(photoshop.get(0).data()));
|
||||
}
|
||||
}
|
||||
else if (magic >>> 16 == TIFF.BYTE_ORDER_MARK_BIG_ENDIAN && (magic & 0xffff) == TIFF.TIFF_MAGIC
|
||||
@@ -358,10 +350,10 @@ public final class Paths {
|
||||
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
|
||||
unknown.setAttribute("MarkerTag", Integer.toString(JPEG.APP13 & 0xFF));
|
||||
|
||||
byte[] identifier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] data = new byte[identifier.length + 1 + pathResource.length];
|
||||
System.arraycopy(identifier, 0, data, 0, identifier.length);
|
||||
System.arraycopy(pathResource, 0, data, identifier.length + 1, pathResource.length);
|
||||
byte[] identfier = "Photoshop 3.0".getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] data = new byte[identfier.length + 1 + pathResource.length];
|
||||
System.arraycopy(identfier, 0, data, 0, identfier.length);
|
||||
System.arraycopy(pathResource, 0, data, identfier.length + 1, pathResource.length);
|
||||
|
||||
unknown.setUserObject(data);
|
||||
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.9.5-SNAPSHOT</version>
|
||||
<version>3.6.2</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
<properties>
|
||||
<project.jpms.module.name>com.twelvemonkeys.imageio.core</project.jpms.module.name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
|
||||
@@ -30,20 +30,16 @@
|
||||
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import com.twelvemonkeys.image.BufferedImageIcon;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.datatransfer.DataFlavor;
|
||||
import java.awt.datatransfer.Transferable;
|
||||
import java.awt.datatransfer.UnsupportedFlavorException;
|
||||
import java.awt.event.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.File;
|
||||
@@ -52,6 +48,20 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.swing.*;
|
||||
|
||||
import com.twelvemonkeys.image.BufferedImageIcon;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
/**
|
||||
* Abstract base class for image readers.
|
||||
*
|
||||
@@ -265,9 +275,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
// - transferType is ok
|
||||
// - bands are ok
|
||||
// TODO: Test if color model is ok?
|
||||
if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType()
|
||||
&& Arrays.equals(specifier.getSampleModel().getSampleSize(), dest.getSampleModel().getSampleSize())
|
||||
&& specifier.getNumBands() <= dest.getSampleModel().getNumBands()) {
|
||||
if (specifier.getSampleModel().getTransferType() == dest.getSampleModel().getTransferType() &&
|
||||
specifier.getNumBands() <= dest.getSampleModel().getNumBands()) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
@@ -441,7 +450,6 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
static final String ZOOM_IN = "zoom-in";
|
||||
static final String ZOOM_OUT = "zoom-out";
|
||||
static final String ZOOM_ACTUAL = "zoom-actual";
|
||||
static final String ZOOM_FIT = "zoom-fit";
|
||||
|
||||
private BufferedImage image;
|
||||
|
||||
@@ -517,20 +525,9 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
|
||||
private void setupActions() {
|
||||
// Mac weirdness... VK_MINUS/VK_PLUS seems to map to english key map always...
|
||||
bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN,
|
||||
KeyStroke.getKeyStroke('+'),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_ADD, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT,
|
||||
KeyStroke.getKeyStroke('-'),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL,
|
||||
KeyStroke.getKeyStroke('0'),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
bindAction(new ZoomToFitAction("Zoom fit"), ZOOM_FIT,
|
||||
KeyStroke.getKeyStroke('9'),
|
||||
KeyStroke.getKeyStroke(KeyEvent.VK_9, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
bindAction(new ZoomAction("Zoom in", 2), ZOOM_IN, KeyStroke.getKeyStroke('+'), KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0));
|
||||
bindAction(new ZoomAction("Zoom out", .5), ZOOM_OUT, KeyStroke.getKeyStroke('-'), KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0));
|
||||
bindAction(new ZoomAction("Zoom actual"), ZOOM_ACTUAL, KeyStroke.getKeyStroke('0'), KeyStroke.getKeyStroke(KeyEvent.VK_0, 0));
|
||||
|
||||
bindAction(TransferHandler.getCopyAction(), (String) TransferHandler.getCopyAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
bindAction(TransferHandler.getPasteAction(), (String) TransferHandler.getPasteAction().getValue(Action.NAME), KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()));
|
||||
@@ -547,7 +544,6 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
private JPopupMenu createPopupMenu() {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
|
||||
popup.add(getActionMap().get(ZOOM_FIT));
|
||||
popup.add(getActionMap().get(ZOOM_ACTUAL));
|
||||
popup.add(getActionMap().get(ZOOM_IN));
|
||||
popup.add(getActionMap().get(ZOOM_OUT));
|
||||
@@ -568,7 +564,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), background, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), background, group);
|
||||
background.addSeparator();
|
||||
ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : new Color(0xFF6600));
|
||||
ChooseBackgroundAction chooseBackgroundAction = new ChooseBackgroundAction("Choose...", defaultBG != null ? defaultBG : Color.BLUE);
|
||||
chooseBackgroundAction.putValue(Action.SELECTED_KEY, backgroundPaint == defaultBG);
|
||||
addCheckBoxItem(chooseBackgroundAction, background, group);
|
||||
|
||||
@@ -682,41 +678,14 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
else {
|
||||
Icon current = getIcon();
|
||||
int w = Math.max(Math.min((int) (current.getIconWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 16);
|
||||
int h = Math.max(Math.min((int) (current.getIconHeight() * zoomFactor), image.getHeight() * 16), image.getHeight() / 16);
|
||||
int w = (int) Math.max(Math.min(current.getIconWidth() * zoomFactor, image.getWidth() * 16), image.getWidth() / 16);
|
||||
int h = (int) Math.max(Math.min(current.getIconHeight() * zoomFactor, image.getHeight() * 16), image.getHeight() / 16);
|
||||
|
||||
setIcon(new BufferedImageIcon(image, Math.max(w, 2), Math.max(h, 2), w > image.getWidth() || h > image.getHeight()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ZoomToFitAction extends ZoomAction {
|
||||
public ZoomToFitAction(final String name) {
|
||||
super(name, -1);
|
||||
}
|
||||
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
JComponent source = (JComponent) e.getSource();
|
||||
|
||||
if (source instanceof JMenuItem) {
|
||||
JPopupMenu menu = (JPopupMenu) SwingUtilities.getAncestorOfClass(JPopupMenu.class, source);
|
||||
source = (JComponent) menu.getInvoker();
|
||||
}
|
||||
|
||||
Container container = SwingUtilities.getAncestorOfClass(JViewport.class, source);
|
||||
|
||||
double ratioX = container.getWidth() / (double) image.getWidth();
|
||||
double ratioY = container.getHeight() / (double) image.getHeight();
|
||||
|
||||
double zoomFactor = Math.min(ratioX, ratioY);
|
||||
|
||||
int w = Math.max(Math.min((int) (image.getWidth() * zoomFactor), image.getWidth() * 16), image.getWidth() / 16);
|
||||
int h = Math.max(Math.min((int) (image.getHeight() * zoomFactor), image.getHeight() * 16), image.getHeight() / 16);
|
||||
|
||||
setIcon(new BufferedImageIcon(image, w, h, zoomFactor > 1));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImageTransferable implements Transferable {
|
||||
private final BufferedImage image;
|
||||
|
||||
@@ -735,7 +704,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException {
|
||||
public Object getTransferData(final DataFlavor flavor) throws UnsupportedFlavorException, IOException {
|
||||
if (isDataFlavorSupported(flavor)) {
|
||||
return image;
|
||||
}
|
||||
|
||||
-627
@@ -1,627 +0,0 @@
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*;
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* Base class for easy read-only implementation of the standard image metadata format.
|
||||
* Chroma, Data and Transparency nodes values are based on the required
|
||||
* {@link ImageTypeSpecifier}.
|
||||
* Other values or overrides may be specified using the builder.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
public class StandardImageMetadataSupport extends AbstractMetadata {
|
||||
|
||||
// The only required field, most standard metadata can be extracted from the type
|
||||
private final ImageTypeSpecifier type;
|
||||
protected final ColorSpaceType colorSpaceType;
|
||||
protected final boolean blackIsZero;
|
||||
private final IndexColorModel palette;
|
||||
protected final String compressionName;
|
||||
protected final boolean compressionLossless;
|
||||
protected final PlanarConfiguration planarConfiguration;
|
||||
private final int[] bitsPerSample;
|
||||
private final int[] significantBits;
|
||||
private final int[] sampleMSB;
|
||||
protected final Double pixelAspectRatio;
|
||||
protected final ImageOrientation orientation;
|
||||
protected final String formatVersion;
|
||||
protected final SubimageInterpretation subimageInterpretation;
|
||||
private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API...
|
||||
private final Collection<TextEntry> textEntries;
|
||||
|
||||
protected StandardImageMetadataSupport(Builder builder) {
|
||||
notNull(builder, "builder");
|
||||
|
||||
// Baseline
|
||||
type = builder.type;
|
||||
|
||||
// Chroma
|
||||
colorSpaceType = builder.colorSpaceType;
|
||||
blackIsZero = builder.blackIsZero;
|
||||
palette = builder.palette;
|
||||
|
||||
// Compression
|
||||
compressionName = builder.compressionName;
|
||||
compressionLossless = builder.compressionLossless;
|
||||
|
||||
// Data
|
||||
planarConfiguration = builder.planarConfiguration;
|
||||
bitsPerSample = builder.bitsPerSample;
|
||||
significantBits = builder.significantBits;
|
||||
sampleMSB = builder.sampleMSB;
|
||||
|
||||
// Dimension
|
||||
orientation = builder.orientation;
|
||||
pixelAspectRatio = builder.pixelAspectRatio;
|
||||
|
||||
// Document
|
||||
formatVersion = builder.formatVersion;
|
||||
documentCreationTime = builder.documentCreationTime;
|
||||
subimageInterpretation = builder.subimageInterpretation;
|
||||
|
||||
// Text
|
||||
textEntries = builder.textEntries;
|
||||
}
|
||||
|
||||
public static Builder builder(ImageTypeSpecifier type) {
|
||||
return new Builder(type);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final ImageTypeSpecifier type;
|
||||
private ColorSpaceType colorSpaceType;
|
||||
private boolean blackIsZero = true;
|
||||
private IndexColorModel palette;
|
||||
private String compressionName;
|
||||
private boolean compressionLossless = true;
|
||||
private PlanarConfiguration planarConfiguration;
|
||||
public int[] bitsPerSample;
|
||||
private int[] significantBits;
|
||||
private int[] sampleMSB;
|
||||
private Double pixelAspectRatio;
|
||||
private ImageOrientation orientation = ImageOrientation.Normal;
|
||||
private String formatVersion;
|
||||
private SubimageInterpretation subimageInterpretation;
|
||||
private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type
|
||||
private final Collection<TextEntry> textEntries = new ArrayList<>();
|
||||
|
||||
protected Builder(ImageTypeSpecifier type) {
|
||||
this.type = notNull(type, "type");
|
||||
}
|
||||
|
||||
public Builder withColorSpaceType(ColorSpaceType colorSpaceType) {
|
||||
this.colorSpaceType = colorSpaceType;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBlackIsZero(boolean blackIsZero) {
|
||||
this.blackIsZero = blackIsZero;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPalette(IndexColorModel palette) {
|
||||
this.palette = palette;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCompressionTypeName(String compressionName) {
|
||||
this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withCompressionLossless(boolean lossless) {
|
||||
this.compressionLossless = isTrue(lossless || compressionName != null, lossless, "Lossy compression requires compression name");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) {
|
||||
this.planarConfiguration = planarConfiguration;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBitsPerSample(int... bitsPerSample) {
|
||||
this.bitsPerSample = bitsPerSample;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSignificantBitsPerSample(int... significantBits) {
|
||||
this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(),
|
||||
significantBits,
|
||||
String.format("single value or %d values expected", type.getNumBands()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSampleMSB(int... sampleMSB) {
|
||||
this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(),
|
||||
sampleMSB,
|
||||
String.format("single value or %d values expected", type.getNumBands()));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withPixelAspectRatio(Double pixelAspectRatio) {
|
||||
this.pixelAspectRatio = pixelAspectRatio;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withOrientation(ImageOrientation orientation) {
|
||||
this.orientation = notNull(orientation, "orientation");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withFormatVersion(String formatVersion) {
|
||||
this.formatVersion = notNull(formatVersion, "formatVersion");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withSubimageInterpretation(SubimageInterpretation interpretation) {
|
||||
this.subimageInterpretation = interpretation;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withDocumentCreationTime(Calendar creationTime) {
|
||||
this.documentCreationTime = creationTime;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTextEntries(Map<String, String> entries) {
|
||||
return withTextEntries(toTextEntries(notNull(entries, "entries").entrySet()));
|
||||
}
|
||||
|
||||
private Collection<TextEntry> toTextEntries(Collection<Map.Entry<String, String>> entries) {
|
||||
TextEntry[] result = new TextEntry[entries.size()];
|
||||
|
||||
int i = 0;
|
||||
for (Map.Entry<String, String> entry : entries) {
|
||||
result[i++] = new TextEntry(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return Arrays.asList(result);
|
||||
}
|
||||
|
||||
public Builder withTextEntries(Collection<TextEntry> entries) {
|
||||
this.textEntries.addAll(notNull(entries, "entries"));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withTextEntry(String keyword, String value) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
this.textEntries.add(new TextEntry(notNull(keyword, "keyword"), value));
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public IIOMetadata build() {
|
||||
return new StandardImageMetadataSupport(this);
|
||||
}
|
||||
}
|
||||
|
||||
protected enum ColorSpaceType {
|
||||
XYZ(3),
|
||||
Lab(3),
|
||||
Luv(3),
|
||||
YCbCr(3),
|
||||
Yxy(3),
|
||||
YCCK(4),
|
||||
PhotoYCC(3),
|
||||
RGB(3),
|
||||
GRAY(1),
|
||||
HSV(3),
|
||||
HLS(3),
|
||||
CMYK(3),
|
||||
CMY(3),
|
||||
|
||||
// Generic types (so much extra work, because Java names can't start with a number, phew...)
|
||||
GENERIC_2CLR(2, "2CLR"),
|
||||
GENERIC_3CLR(3, "3CLR"),
|
||||
GENERIC_4CLR(4, "4CLR"),
|
||||
GENERIC_5CLR(5, "5CLR"),
|
||||
GENERIC_6CLR(6, "6CLR"),
|
||||
GENERIC_7CLR(7, "7CLR"),
|
||||
GENERIC_8CLR(8, "8CLR"),
|
||||
GENERIC_9CLR(9, "9CLR"),
|
||||
GENERIC_ACLR(0xA, "ACLR"),
|
||||
GENERIC_BCLR(0xB, "BCLR"),
|
||||
GENERIC_CCLR(0xC, "CCLR"),
|
||||
GENERIC_DCLR(0xD, "DCLR"),
|
||||
GENERIC_ECLR(0xE, "ECLR"),
|
||||
GENERIC_FCLR(0xF, "FCLR");
|
||||
|
||||
final int numChannels;
|
||||
private final String nameOverride;
|
||||
|
||||
ColorSpaceType(int numChannels) {
|
||||
this(numChannels, null);
|
||||
}
|
||||
ColorSpaceType(int numChannels, String nameOverride) {
|
||||
this.numChannels = numChannels;
|
||||
this.nameOverride = nameOverride;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return nameOverride != null ? nameOverride : super.toString();
|
||||
}
|
||||
}
|
||||
|
||||
protected enum PlanarConfiguration {
|
||||
PixelInterleaved,
|
||||
PlaneInterleaved,
|
||||
LineInterleaved,
|
||||
TileInterleaved
|
||||
}
|
||||
|
||||
protected enum ImageOrientation {
|
||||
Normal,
|
||||
Rotate90,
|
||||
Rotate180,
|
||||
Rotate270,
|
||||
FlipH,
|
||||
FlipV,
|
||||
FlipHRotate90,
|
||||
FlipVRotate90
|
||||
}
|
||||
|
||||
protected enum SubimageInterpretation {
|
||||
Standalone,
|
||||
SinglePage,
|
||||
FullResolution,
|
||||
ReducedResolution,
|
||||
PyramidLayer,
|
||||
Preview,
|
||||
VolumeSlice,
|
||||
ObjectView,
|
||||
Panorama,
|
||||
AnimationFrame,
|
||||
TransparencyMask,
|
||||
CompositingLayer,
|
||||
SpectralSlice,
|
||||
Unknown
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma");
|
||||
|
||||
ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel();
|
||||
ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace());
|
||||
int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents();
|
||||
|
||||
IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType");
|
||||
chromaNode.appendChild(colorSpaceTypeNode);
|
||||
colorSpaceTypeNode.setAttribute("name", csType.toString());
|
||||
|
||||
IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels");
|
||||
numChannelsNode.setAttribute("value", String.valueOf(numComponents));
|
||||
chromaNode.appendChild(numChannelsNode);
|
||||
|
||||
IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero");
|
||||
blackIsZeroNode.setAttribute("value", booleanString(blackIsZero));
|
||||
chromaNode.appendChild(blackIsZeroNode);
|
||||
|
||||
if (colorModel instanceof IndexColorModel || palette != null) {
|
||||
IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel;
|
||||
|
||||
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
|
||||
chromaNode.appendChild(paletteNode);
|
||||
|
||||
for (int i = 0; i < colorMap.getMapSize(); i++) {
|
||||
IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry");
|
||||
paletteNode.appendChild(paletteEntryNode);
|
||||
|
||||
paletteEntryNode.setAttribute("index", Integer.toString(i));
|
||||
paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i)));
|
||||
paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
|
||||
paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
|
||||
|
||||
// Assumption: BITMASK transparency will use single transparent pixel
|
||||
if (colorMap.getTransparency() == Transparency.TRANSLUCENT) {
|
||||
paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i)));
|
||||
}
|
||||
}
|
||||
|
||||
if (colorMap.getTransparentPixel() != -1) {
|
||||
IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex");
|
||||
chromaNode.appendChild(backgroundIndexNode);
|
||||
backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: BackgroundColor?
|
||||
|
||||
return chromaNode;
|
||||
}
|
||||
|
||||
private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) {
|
||||
switch (colorSpace.getType()) {
|
||||
case ColorSpace.TYPE_XYZ:
|
||||
return XYZ;
|
||||
case ColorSpace.TYPE_Lab:
|
||||
return Lab;
|
||||
case ColorSpace.TYPE_Luv:
|
||||
return Luv;
|
||||
case ColorSpace.TYPE_YCbCr:
|
||||
return YCbCr;
|
||||
case ColorSpace.TYPE_Yxy:
|
||||
return Yxy;
|
||||
// Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace
|
||||
case ColorSpace.TYPE_RGB:
|
||||
return RGB;
|
||||
case ColorSpace.TYPE_GRAY:
|
||||
return GRAY;
|
||||
case ColorSpace.TYPE_HSV:
|
||||
return HSV;
|
||||
case ColorSpace.TYPE_HLS:
|
||||
return HLS;
|
||||
case ColorSpace.TYPE_CMYK:
|
||||
return CMYK;
|
||||
case ColorSpace.TYPE_CMY:
|
||||
return CMY;
|
||||
default:
|
||||
int numComponents = colorSpace.getNumComponents();
|
||||
if (numComponents == 1) {
|
||||
return GRAY;
|
||||
}
|
||||
else if (numComponents < 16) {
|
||||
return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR");
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace);
|
||||
}
|
||||
|
||||
protected static final class TextEntry {
|
||||
static final List<String> COMPRESSIONS = Arrays.asList("none", "lzw", "zip", "bzip", "other");
|
||||
|
||||
final String keyword;
|
||||
final String value;
|
||||
final String language;
|
||||
final String encoding;
|
||||
final String compression;
|
||||
|
||||
public TextEntry(final String keyword, final String value) {
|
||||
this(keyword, value, null, null, null);
|
||||
}
|
||||
|
||||
public TextEntry(final String keyword, final String value, final String language, final String encoding, final String compression) {
|
||||
this.keyword = keyword;
|
||||
this.value = notNull(value, "value");
|
||||
this.language = language;
|
||||
this.encoding = encoding;
|
||||
this.compression = isTrue(compression == null || COMPRESSIONS.contains(compression), compression, String.format("Unknown compression: %s (expected: %s)", compression, COMPRESSIONS));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
if (compressionName == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode node = new IIOMetadataNode("Compression");
|
||||
|
||||
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
|
||||
compressionTypeName.setAttribute("value", compressionName);
|
||||
node.appendChild(compressionTypeName);
|
||||
|
||||
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
|
||||
lossless.setAttribute("value", booleanString(compressionLossless));
|
||||
node.appendChild(lossless);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
protected static String booleanString(boolean booleanValue) {
|
||||
return booleanValue ? "TRUE" : "FALSE";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||
|
||||
IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration");
|
||||
dataNode.appendChild(planarConfigurationNode);
|
||||
planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() :
|
||||
(type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved"));
|
||||
|
||||
String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel
|
||||
? "Index"
|
||||
: sampleFormat(type.getSampleModel());
|
||||
|
||||
if (sampleFormatValue != null) {
|
||||
IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat");
|
||||
sampleFormatNode.setAttribute("value", sampleFormatValue);
|
||||
dataNode.appendChild(sampleFormatNode);
|
||||
}
|
||||
|
||||
int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize();
|
||||
IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample");
|
||||
bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample));
|
||||
dataNode.appendChild(bitsPerSampleNode);
|
||||
|
||||
if (significantBits != null) {
|
||||
String significantBitsValue = createListValue(type.getNumBands(), significantBits);
|
||||
if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) {
|
||||
IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample");
|
||||
significantBitsPerSampleNode.setAttribute("value", significantBitsValue);
|
||||
dataNode.appendChild(significantBitsPerSampleNode);
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleMSB != null) {
|
||||
// TODO: Only if different from default!
|
||||
IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB");
|
||||
sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB));
|
||||
dataNode.appendChild(sampleMSBNode);
|
||||
}
|
||||
|
||||
return dataNode;
|
||||
}
|
||||
|
||||
private static String createListValue(final int itemCount, final int... values) {
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(' ');
|
||||
}
|
||||
|
||||
buffer.append(values[i % values.length]);
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private static String sampleFormat(SampleModel sampleModel) {
|
||||
switch (sampleModel.getDataType()) {
|
||||
case DataBuffer.TYPE_SHORT:
|
||||
case DataBuffer.TYPE_INT:
|
||||
if (sampleModel instanceof ComponentSampleModel) {
|
||||
return "SignedIntegral";
|
||||
}
|
||||
// Otherwise fall-through, most likely a *PixelPackedSampleModel
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
return "UnsignedIntegral";
|
||||
case DataBuffer.TYPE_FLOAT:
|
||||
case DataBuffer.TYPE_DOUBLE:
|
||||
return "Real";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||
|
||||
if (pixelAspectRatio != null) {
|
||||
IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio");
|
||||
pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio));
|
||||
dimensionNode.appendChild(pixelAspectRatioNode);
|
||||
}
|
||||
|
||||
IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation");
|
||||
imageOrientationNode.setAttribute("value", orientation.toString());
|
||||
dimensionNode.appendChild(imageOrientationNode);
|
||||
|
||||
return dimensionNode.hasChildNodes() ? dimensionNode : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode documentNode = new IIOMetadataNode("Document");
|
||||
|
||||
if (formatVersion != null) {
|
||||
IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion");
|
||||
documentNode.appendChild(formatVersionNode);
|
||||
formatVersionNode.setAttribute("value", formatVersion);
|
||||
}
|
||||
|
||||
if (subimageInterpretation != null) {
|
||||
IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation");
|
||||
documentNode.appendChild(subImageInterpretationNode);
|
||||
subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString());
|
||||
}
|
||||
|
||||
if (documentCreationTime != null) {
|
||||
IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime");
|
||||
documentNode.appendChild(imageCreationTimeNode);
|
||||
|
||||
imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR)));
|
||||
imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1));
|
||||
imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH)));
|
||||
imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY)));
|
||||
imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE)));
|
||||
imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND)));
|
||||
}
|
||||
|
||||
return documentNode.hasChildNodes() ? documentNode : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
if (textEntries.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode textNode = new IIOMetadataNode("Text");
|
||||
|
||||
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||
|
||||
for (TextEntry entry : textEntries) {
|
||||
IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry");
|
||||
textNode.appendChild(textEntryNode);
|
||||
if (entry.keyword != null) {
|
||||
textEntryNode.setAttribute("keyword", entry.keyword);
|
||||
}
|
||||
textEntryNode.setAttribute("value", entry.value);
|
||||
if (entry.language != null) {
|
||||
textEntryNode.setAttribute("language", entry.language);
|
||||
}
|
||||
if (entry.encoding != null) {
|
||||
textEntryNode.setAttribute("encoding", entry.encoding);
|
||||
}
|
||||
if (entry.compression != null) {
|
||||
textEntryNode.setAttribute("compression", entry.compression);
|
||||
}
|
||||
}
|
||||
|
||||
return textNode;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency");
|
||||
|
||||
ColorModel colorModel = type.getColorModel();
|
||||
|
||||
IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha");
|
||||
transparencyNode.appendChild(alphaNode);
|
||||
alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none");
|
||||
|
||||
if (colorModel instanceof IndexColorModel) {
|
||||
IndexColorModel icm = (IndexColorModel) colorModel;
|
||||
if (icm.getTransparentPixel() != -1) {
|
||||
IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex");
|
||||
transparencyNode.appendChild(transparentIndexNode);
|
||||
transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel()));
|
||||
}
|
||||
}
|
||||
|
||||
return transparencyNode;
|
||||
}
|
||||
}
|
||||
-563
@@ -1,563 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.color;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.Platform;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
|
||||
import static com.twelvemonkeys.imageio.color.ColorSpaces.DEBUG;
|
||||
|
||||
/**
|
||||
* A helper class for working with ICC color profiles.
|
||||
* <p>
|
||||
* Standard ICC color profiles are read from system-specific locations
|
||||
* for known operating systems.
|
||||
* </p>
|
||||
* <p>
|
||||
* Color profiles may be configured by placing a property-file
|
||||
* {@code com/twelvemonkeys/imageio/color/icc_profiles.properties}
|
||||
* on the classpath, specifying the full path to the profiles.
|
||||
* ICC color profiles are probably already present on your system, or
|
||||
* can be downloaded from
|
||||
* <a href="http://www.color.org/profiles2.xalter">ICC</a>,
|
||||
* <a href="http://www.adobe.com/downloads/">Adobe</a> or other places.
|
||||
* * </p>
|
||||
* <p>
|
||||
* Example property file:
|
||||
* </p>
|
||||
* <pre>
|
||||
* # icc_profiles.properties
|
||||
* ADOBE_RGB_1998=/path/to/Adobe RGB 1998.icc
|
||||
* GENERIC_CMYK=/path/to/Generic CMYK.icc
|
||||
* </pre>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ColorSpaces.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
public final class ColorProfiles {
|
||||
/**
|
||||
* We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy.
|
||||
*/
|
||||
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
||||
|
||||
static final int ICC_PROFILE_MAGIC = 'a' << 24 | 'c' << 16 | 's' << 8 | 'p';
|
||||
static final int ICC_PROFILE_HEADER_SIZE = 128;
|
||||
|
||||
static {
|
||||
// In case we didn't activate through SPI already
|
||||
ProfileDeferralActivator.activateProfiles();
|
||||
}
|
||||
|
||||
private ColorProfiles() {
|
||||
}
|
||||
|
||||
static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||
// Get *entire profile data*... :-/
|
||||
return getProfileHeaderWithProfileId(profile.getData());
|
||||
}
|
||||
|
||||
static byte[] getProfileHeaderWithProfileId(byte[] data) {
|
||||
// ICC profile header is the first 128 bytes
|
||||
byte[] header = Arrays.copyOf(data, ICC_PROFILE_HEADER_SIZE);
|
||||
|
||||
// Clear out preferred CMM, platform & creator, as these don't affect the profile in any way
|
||||
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
||||
// - KCMS keeps the values in the file...
|
||||
Arrays.fill(header, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
||||
Arrays.fill(header, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
||||
// + Clear out rendering intent, as this may be updated by application
|
||||
Arrays.fill(header, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
||||
Arrays.fill(header, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
||||
|
||||
// Clear out any existing MD5, as it is no longer correct
|
||||
Arrays.fill(header, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
||||
|
||||
// Generate new MD5 and store in header
|
||||
byte[] md5 = computeMD5(header, data);
|
||||
System.arraycopy(md5, 0, header, ICC_Profile.icHdrProfileID, md5.length);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private static byte[] computeMD5(byte[] header, byte[] data) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("MD5");
|
||||
digest.update(header, 0, ICC_PROFILE_HEADER_SIZE);
|
||||
digest.update(data, ICC_PROFILE_HEADER_SIZE, data.length - ICC_PROFILE_HEADER_SIZE);
|
||||
return digest.digest();
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Missing MD5 MessageDigest");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @see java.awt.color.ColorSpace#CS_sRGB
|
||||
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||
*/
|
||||
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is equal to the default GRAY profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default GRAY profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @see java.awt.color.ColorSpace#CS_GRAY
|
||||
*/
|
||||
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||
// (that is 1 single bit of difference right there.. ;-)
|
||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
||||
|
||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||
// except the getRenderingIntent method is package protected in java.awt.color
|
||||
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
||||
|
||||
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
||||
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an ICC color profile is valid.
|
||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code profile} if valid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
// Fix profile before validation
|
||||
profileCleaner.fixProfile(profile);
|
||||
ColorSpaces.validateColorSpace(new ICC_ColorSpace(profile)); // TODO: Should use createColorSpace and cache if good?
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ICC Profile from the given input stream, as-is, with no validation.
|
||||
*
|
||||
* This method behaves exactly like {@code ICC_Profile.getInstance(input)}.
|
||||
*
|
||||
* @param input the input stream to read from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||
* @throws IOException If an I/O error occurs while reading the stream.
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the stream does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(InputStream)
|
||||
* @see #readProfile(InputStream)
|
||||
*/
|
||||
public static ICC_Profile readProfileRaw(final InputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
return ICC_Profile.getInstance(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ICC Profile from the given input stream, with extra validation.
|
||||
*
|
||||
* If a matching profile already exists in cache, the cached instance is returned.
|
||||
*
|
||||
* @param input the input stream to read from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object as read from the input stream.
|
||||
* @throws IOException If an I/O error occurs while reading the stream.
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the stream does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(InputStream)
|
||||
*/
|
||||
public static ICC_Profile readProfile(final InputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
DataInputStream dataInput = new DataInputStream(input);
|
||||
byte[] header = new byte[ICC_PROFILE_HEADER_SIZE];
|
||||
try {
|
||||
dataInput.readFully(header);
|
||||
|
||||
int size = validateHeaderAndGetSize(header);
|
||||
byte[] data = Arrays.copyOf(header, size);
|
||||
dataInput.readFully(data, header.length, size - header.length);
|
||||
|
||||
return createProfile(data);
|
||||
}
|
||||
catch (EOFException e) {
|
||||
throw new IllegalArgumentException("Truncated ICC Profile data", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an ICC Profile from the given byte array, as-is, with no validation.
|
||||
*
|
||||
* This method behaves exactly like {@code ICC_Profile.getInstance(input)},
|
||||
* except that extraneous bytes at the end of the array is ignored.
|
||||
*
|
||||
* @param input the byte array to create a profile from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object created from the byte array
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the byte array does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(byte[])
|
||||
* @see #createProfile(byte[])
|
||||
*/
|
||||
public static ICC_Profile createProfileRaw(final byte[] input) {
|
||||
int size = validateHeaderAndGetSize(input);
|
||||
|
||||
// Unlike the InputStream version, the byte version of ICC_Profile.getInstance()
|
||||
// does not discard extra bytes at the end. We'll chop them off here for convenience
|
||||
return ICC_Profile.getInstance(limit(input, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an ICC Profile from the given byte array, with extra validation.
|
||||
* Extraneous bytes at the end of the array are ignored.
|
||||
*
|
||||
* If a matching profile already exists in cache, the cached instance is returned.
|
||||
*
|
||||
* @param input the byte array to create a profile from, may not be {@code null}
|
||||
* @return an {@code ICC_Profile} object created from the byte array
|
||||
* @throws IllegalArgumentException If {@code input} is {@code null}
|
||||
* or the byte array does not contain valid ICC Profile data.
|
||||
* @see ICC_Profile#getInstance(byte[])
|
||||
*/
|
||||
public static ICC_Profile createProfile(final byte[] input) {
|
||||
int size = validateAndGetSize(input);
|
||||
|
||||
// Look up in cache before returning, these are already validated
|
||||
byte[] profileHeader = getProfileHeaderWithProfileId(input);
|
||||
ICC_Profile internal = getInternalProfile(profileHeader);
|
||||
if (internal != null) {
|
||||
return internal;
|
||||
}
|
||||
|
||||
ICC_ColorSpace cached = ColorSpaces.getCachedCS(profileHeader);
|
||||
if (cached != null) {
|
||||
return cached.getProfile();
|
||||
}
|
||||
|
||||
ICC_Profile profile = ICC_Profile.getInstance(limit(input, size));
|
||||
|
||||
// We'll validate & cache by creating a color space and returning its profile...
|
||||
// TODO: Rewrite with separate cache for profiles...
|
||||
return ColorSpaces.createColorSpace(profile).getProfile();
|
||||
}
|
||||
|
||||
private static byte[] limit(byte[] input, int size) {
|
||||
return input.length == size ? input : Arrays.copyOf(input, size);
|
||||
}
|
||||
|
||||
private static int validateAndGetSize(byte[] input) {
|
||||
int size = validateHeaderAndGetSize(input);
|
||||
|
||||
if (size < 0 || size > input.length) {
|
||||
throw new IllegalArgumentException("Truncated ICC profile data, length < " + size + ": " + input.length);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static int validateHeaderAndGetSize(byte[] input) {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
if (input.length < ICC_PROFILE_HEADER_SIZE) { // Can't be less than size of ICC header
|
||||
throw new IllegalArgumentException("Truncated ICC profile data, length < 128: " + input.length);
|
||||
}
|
||||
|
||||
int size = intBigEndian(input, ICC_Profile.icHdrSize);
|
||||
|
||||
if (intBigEndian(input, ICC_Profile.icHdrMagic) != ICC_PROFILE_MAGIC) {
|
||||
throw new IllegalArgumentException("Not an ICC profile, missing file signature");
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private static ICC_Profile getInternalProfile(final byte[] profileHeader) {
|
||||
int profileCSType = getCsType(profileHeader);
|
||||
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_PYCC);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||
return ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int intBigEndian(byte[] data, int index) {
|
||||
return (data[index] & 0xff) << 24 | (data[index + 1] & 0xff) << 16 | (data[index + 2] & 0xff) << 8 | (data[index + 3] & 0xff);
|
||||
}
|
||||
|
||||
private static int getCsType(byte[] profileHeader) {
|
||||
int csSig = intBigEndian(profileHeader, ICC_Profile.icHdrColorSpace);
|
||||
|
||||
switch (csSig) {
|
||||
case ICC_Profile.icSigXYZData:
|
||||
return ColorSpace.TYPE_XYZ;
|
||||
case ICC_Profile.icSigLabData:
|
||||
return ColorSpace.TYPE_Lab;
|
||||
case ICC_Profile.icSigLuvData:
|
||||
return ColorSpace.TYPE_Luv;
|
||||
case ICC_Profile.icSigYCbCrData:
|
||||
return ColorSpace.TYPE_YCbCr;
|
||||
case ICC_Profile.icSigYxyData:
|
||||
return ColorSpace.TYPE_Yxy;
|
||||
case ICC_Profile.icSigRgbData:
|
||||
return ColorSpace.TYPE_RGB;
|
||||
case ICC_Profile.icSigGrayData:
|
||||
return ColorSpace.TYPE_GRAY;
|
||||
case ICC_Profile.icSigHsvData:
|
||||
return ColorSpace.TYPE_HSV;
|
||||
case ICC_Profile.icSigHlsData:
|
||||
return ColorSpace.TYPE_HLS;
|
||||
case ICC_Profile.icSigCmykData:
|
||||
return ColorSpace.TYPE_CMYK;
|
||||
// Note: There is no TYPE_* 10...
|
||||
case ICC_Profile.icSigCmyData:
|
||||
return ColorSpace.TYPE_CMY;
|
||||
case ICC_Profile.icSigSpace2CLR:
|
||||
return ColorSpace.TYPE_2CLR;
|
||||
case ICC_Profile.icSigSpace3CLR:
|
||||
return ColorSpace.TYPE_3CLR;
|
||||
case ICC_Profile.icSigSpace4CLR:
|
||||
return ColorSpace.TYPE_4CLR;
|
||||
case ICC_Profile.icSigSpace5CLR:
|
||||
return ColorSpace.TYPE_5CLR;
|
||||
case ICC_Profile.icSigSpace6CLR:
|
||||
return ColorSpace.TYPE_6CLR;
|
||||
case ICC_Profile.icSigSpace7CLR:
|
||||
return ColorSpace.TYPE_7CLR;
|
||||
case ICC_Profile.icSigSpace8CLR:
|
||||
return ColorSpace.TYPE_8CLR;
|
||||
case ICC_Profile.icSigSpace9CLR:
|
||||
return ColorSpace.TYPE_9CLR;
|
||||
case ICC_Profile.icSigSpaceACLR:
|
||||
return ColorSpace.TYPE_ACLR;
|
||||
case ICC_Profile.icSigSpaceBCLR:
|
||||
return ColorSpace.TYPE_BCLR;
|
||||
case ICC_Profile.icSigSpaceCCLR:
|
||||
return ColorSpace.TYPE_CCLR;
|
||||
case ICC_Profile.icSigSpaceDCLR:
|
||||
return ColorSpace.TYPE_DCLR;
|
||||
case ICC_Profile.icSigSpaceECLR:
|
||||
return ColorSpace.TYPE_ECLR;
|
||||
case ICC_Profile.icSigSpaceFCLR:
|
||||
return ColorSpace.TYPE_FCLR;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid ICC color space signature: " + csSig); // TODO: fourCC?
|
||||
}
|
||||
}
|
||||
|
||||
static ICC_Profile readProfileFromClasspathResource(@SuppressWarnings("SameParameterValue") final String profilePath) {
|
||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||
|
||||
if (stream != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(stream);
|
||||
}
|
||||
catch (@SuppressWarnings("CatchMayIgnoreException") IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||
if (profilePath != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static void fixProfile(ICC_Profile profile) {
|
||||
profileCleaner.fixProfile(profile);
|
||||
}
|
||||
|
||||
static boolean validationAltersProfileHeader() {
|
||||
return profileCleaner.validationAltersProfileHeader();
|
||||
}
|
||||
|
||||
// Cache header profile data to avoid excessive array creation/copying. Use static inner class for on-demand lazy init
|
||||
static class sRGB {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
||||
}
|
||||
|
||||
static class CIEXYZ {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
||||
}
|
||||
|
||||
static class PYCC {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
||||
}
|
||||
|
||||
static class GRAY {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
}
|
||||
|
||||
static class LINEAR_RGB {
|
||||
static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
||||
}
|
||||
|
||||
static class Profiles {
|
||||
// TODO: Honour java.iccprofile.path property?
|
||||
private static final Properties PROFILES = loadProfiles();
|
||||
|
||||
private static Properties loadProfiles() {
|
||||
Properties systemDefaults;
|
||||
|
||||
try {
|
||||
systemDefaults = SystemUtil.loadProperties(ColorSpaces.class, "com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id());
|
||||
}
|
||||
catch (@SuppressWarnings("CatchMayIgnoreException") SecurityException | IOException ignore) {
|
||||
System.err.printf(
|
||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||
ignore.getMessage()
|
||||
);
|
||||
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
systemDefaults = null;
|
||||
}
|
||||
|
||||
// Create map with defaults and add user overrides if any
|
||||
Properties profiles = new Properties(systemDefaults);
|
||||
|
||||
try {
|
||||
Properties userOverrides = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||
);
|
||||
profiles.putAll(userOverrides);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
// Most likely, this file won't be there
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("User ICC profiles: " + profiles);
|
||||
System.out.println("System ICC profiles : " + systemDefaults);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static String getPath(final String profileName) {
|
||||
return PROFILES.getProperty(profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
+243
-40
@@ -30,17 +30,23 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.lang.Platform;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
import com.twelvemonkeys.util.LRUHashMap;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.twelvemonkeys.imageio.color.ColorProfiles.*;
|
||||
import java.util.Properties;
|
||||
|
||||
/**
|
||||
* A helper class for working with ICC color profiles and color spaces.
|
||||
@@ -77,6 +83,9 @@ public final class ColorSpaces {
|
||||
|
||||
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.color.debug"));
|
||||
|
||||
/** We need special ICC profile handling for KCMS vs LCMS. Delegate to specific strategy. */
|
||||
private final static ICCProfileSanitizer profileCleaner = ICCProfileSanitizer.Factory.get();
|
||||
|
||||
// NOTE: java.awt.color.ColorSpace.CS_* uses 1000-1004, we'll use 5000+ to not interfere with future additions
|
||||
|
||||
/** The Adobe RGB 1998 (or compatible) color space. Either read from disk or built-in. */
|
||||
@@ -87,17 +96,24 @@ public final class ColorSpaces {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public static final int CS_GENERIC_CMYK = 5001;
|
||||
|
||||
// TODO: Move to ColorProfiles OR cache ICC_ColorSpace instead?
|
||||
// Weak references to hold the color spaces while cached
|
||||
private static WeakReference<ICC_Profile> adobeRGB1998 = new WeakReference<>(null);
|
||||
private static WeakReference<ICC_Profile> genericCMYK = new WeakReference<>(null);
|
||||
|
||||
// Cache for the latest used color spaces
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(16);
|
||||
private static final Map<Key, ICC_ColorSpace> cache = new LRUHashMap<>(10);
|
||||
|
||||
static {
|
||||
// In case we didn't activate through SPI already
|
||||
ProfileDeferralActivator.activateProfiles();
|
||||
try {
|
||||
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863
|
||||
ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData();
|
||||
}
|
||||
catch (Throwable disasters) {
|
||||
System.err.println("ICC Color Profile not properly activated due to the exception below.");
|
||||
System.err.println("Expect to see JDK-6986863 in action, and consider filing a bug report to your JRE provider.");
|
||||
|
||||
disasters.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private ColorSpaces() {}
|
||||
@@ -118,7 +134,7 @@ public final class ColorSpaces {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// Fix profile before lookup/create
|
||||
fixProfile(profile);
|
||||
profileCleaner.fixProfile(profile);
|
||||
|
||||
byte[] profileHeader = getProfileHeaderWithProfileId(profile);
|
||||
|
||||
@@ -130,20 +146,53 @@ public final class ColorSpaces {
|
||||
return getCachedOrCreateCS(profile, profileHeader);
|
||||
}
|
||||
|
||||
static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.sRGB.header)) {
|
||||
private static byte[] getProfileHeaderWithProfileId(final ICC_Profile profile) {
|
||||
// Get *entire profile data*... :-/
|
||||
byte[] data = profile.getData();
|
||||
|
||||
// Clear out preferred CMM, platform & creator, as these does not affect the profile in any way
|
||||
// - LCMS updates CMM + creator to "lcms" and platform to current platform
|
||||
// - KCMS keeps the values in the file...
|
||||
Arrays.fill(data, ICC_Profile.icHdrCmmId, ICC_Profile.icHdrCmmId + 4, (byte) 0);
|
||||
Arrays.fill(data, ICC_Profile.icHdrPlatform, ICC_Profile.icHdrPlatform + 4, (byte) 0);
|
||||
// + Clear out rendering intent, as this may be updated by application
|
||||
Arrays.fill(data, ICC_Profile.icHdrRenderingIntent, ICC_Profile.icHdrRenderingIntent + 4, (byte) 0);
|
||||
Arrays.fill(data, ICC_Profile.icHdrCreator, ICC_Profile.icHdrCreator + 4, (byte) 0);
|
||||
|
||||
// Clear out any existing MD5, as it is no longer correct
|
||||
Arrays.fill(data, ICC_Profile.icHdrProfileID, ICC_Profile.icHdrProfileID + 16, (byte) 0);
|
||||
|
||||
// Generate new MD5 and store in header
|
||||
byte[] md5 = computeMD5(data);
|
||||
System.arraycopy(md5, 0, data, ICC_Profile.icHdrProfileID, md5.length);
|
||||
|
||||
// ICC profile header is the first 128 bytes
|
||||
return Arrays.copyOf(data, 128);
|
||||
}
|
||||
|
||||
private static byte[] computeMD5(byte[] data) {
|
||||
try {
|
||||
return MessageDigest.getInstance("MD5").digest(data);
|
||||
}
|
||||
catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Missing MD5 MessageDigest");
|
||||
}
|
||||
}
|
||||
|
||||
private static ICC_ColorSpace getInternalCS(final int profileCSType, final byte[] profileHeader) {
|
||||
if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, sRGB.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, ColorProfiles.GRAY.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_GRAY && Arrays.equals(profileHeader, GRAY.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_GRAY);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, ColorProfiles.PYCC.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_3CLR && Arrays.equals(profileHeader, PYCC.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_PYCC);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, ColorProfiles.LINEAR_RGB.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_RGB && Arrays.equals(profileHeader, LINEAR_RGB.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_LINEAR_RGB);
|
||||
}
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, ColorProfiles.CIEXYZ.header)) {
|
||||
else if (profileCSType == ColorSpace.TYPE_XYZ && Arrays.equals(profileHeader, CIEXYZ.header)) {
|
||||
return (ICC_ColorSpace) ColorSpace.getInstance(ColorSpace.CS_CIEXYZ);
|
||||
}
|
||||
|
||||
@@ -154,36 +203,26 @@ public final class ColorSpaces {
|
||||
Key key = new Key(profileHeader);
|
||||
|
||||
synchronized (cache) {
|
||||
ICC_ColorSpace cs = getCachedCS(key);
|
||||
ICC_ColorSpace cs = cache.get(key);
|
||||
|
||||
if (cs == null) {
|
||||
cs = new ICC_ColorSpace(profile);
|
||||
|
||||
validateColorSpace(cs);
|
||||
|
||||
cache.put(key, cs);
|
||||
|
||||
// On LCMS, validation *alters* the profile header, need to re-generate key
|
||||
if (ColorProfiles.validationAltersProfileHeader()) {
|
||||
cache.put(new Key(getProfileHeaderWithProfileId(cs.getProfile())), cs);
|
||||
}
|
||||
key = profileCleaner.validationAltersProfileHeader()
|
||||
? new Key(getProfileHeaderWithProfileId(cs.getProfile()))
|
||||
: key;
|
||||
|
||||
cache.put(key, cs);
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
}
|
||||
|
||||
private static ICC_ColorSpace getCachedCS(Key profileKey) {
|
||||
synchronized (cache) {
|
||||
return cache.get(profileKey);
|
||||
}
|
||||
}
|
||||
|
||||
static ICC_ColorSpace getCachedCS(final byte[] profileHeader) {
|
||||
return getCachedCS(new Key(profileHeader));
|
||||
}
|
||||
|
||||
static void validateColorSpace(final ICC_ColorSpace cs) {
|
||||
private static void validateColorSpace(final ICC_ColorSpace cs) {
|
||||
// Validate the color space, to avoid caching bad profiles/color spaces
|
||||
// Will throw IllegalArgumentException or CMMException if the profile is bad
|
||||
cs.fromRGB(new float[] {0.999f, 0.5f, 0.001f});
|
||||
@@ -194,27 +233,73 @@ public final class ColorSpaces {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link ColorProfiles#isCS_sRGB(ICC_Profile)} instead.
|
||||
* Tests whether an ICC color profile is equal to the default sRGB profile.
|
||||
*
|
||||
* @param profile the ICC profile to test. May not be {@code null}.
|
||||
* @return {@code true} if {@code profile} is equal to the default sRGB profile.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*
|
||||
* @see java.awt.color.ColorSpace#isCS_sRGB()
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isCS_sRGB(final ICC_Profile profile) {
|
||||
return ColorProfiles.isCS_sRGB(profile);
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
return profile.getColorSpaceType() == ColorSpace.TYPE_RGB && Arrays.equals(getProfileHeaderWithProfileId(profile), sRGB.header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link ColorProfiles#isCS_GRAY(ICC_Profile)} instead.
|
||||
* Tests whether an ICC color profile is known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code true} if known to be offending, {@code false} otherwise
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean isCS_GRAY(final ICC_Profile profile) {
|
||||
return ColorProfiles.isCS_GRAY(profile);
|
||||
static boolean isOffendingColorProfile(final ICC_Profile profile) {
|
||||
Validate.notNull(profile, "profile");
|
||||
|
||||
// NOTE:
|
||||
// Several embedded ICC color profiles are non-compliant with Java pre JDK7 and throws CMMException
|
||||
// The problem with these embedded ICC profiles seems to be the rendering intent
|
||||
// being 1 (01000000) - "Media Relative Colormetric" in the offending profiles,
|
||||
// and 0 (00000000) - "Perceptual" in the good profiles
|
||||
// (that is 1 single bit of difference right there.. ;-)
|
||||
// See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7064516
|
||||
|
||||
// This is particularly annoying, as the byte copying isn't really necessary,
|
||||
// except the getRenderingIntent method is package protected in java.awt.color
|
||||
byte[] header = profile.getData(ICC_Profile.icSigHead);
|
||||
|
||||
return header[ICC_Profile.icHdrRenderingIntent] != 0 || header[ICC_Profile.icHdrRenderingIntent + 1] != 0
|
||||
|| header[ICC_Profile.icHdrRenderingIntent + 2] != 0 || header[ICC_Profile.icHdrRenderingIntent + 3] > 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link ColorProfiles#validateProfile(ICC_Profile)} instead.
|
||||
* Tests whether an ICC color profile is valid.
|
||||
* Invalid profiles are known to cause problems for {@link java.awt.image.ColorConvertOp}.
|
||||
* <p>
|
||||
* <em>
|
||||
* Note that this method only tests if a color conversion using this profile is known to fail.
|
||||
* There's no guarantee that the color conversion will succeed even if this method returns {@code false}.
|
||||
* </em>
|
||||
* </p>
|
||||
*
|
||||
* @param profile the ICC color profile. May not be {@code null}.
|
||||
* @return {@code profile} if valid.
|
||||
* @throws IllegalArgumentException if {@code profile} is {@code null}
|
||||
* @throws java.awt.color.CMMException if {@code profile} is invalid.
|
||||
*/
|
||||
@Deprecated
|
||||
public static ICC_Profile validateProfile(final ICC_Profile profile) {
|
||||
return ColorProfiles.validateProfile(profile);
|
||||
// Fix profile before validation
|
||||
profileCleaner.fixProfile(profile);
|
||||
validateColorSpace(new ICC_ColorSpace(profile));
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,6 +382,50 @@ public final class ColorSpaces {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static ICC_Profile readProfileFromClasspathResource(final String profilePath) {
|
||||
InputStream stream = ColorSpaces.class.getResourceAsStream(profilePath);
|
||||
|
||||
if (stream != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from classpath resource: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(stream);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(stream);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ICC_Profile readProfileFromPath(final String profilePath) {
|
||||
if (profilePath != null) {
|
||||
if (DEBUG) {
|
||||
System.out.println("Loading profile from: " + profilePath);
|
||||
}
|
||||
|
||||
try {
|
||||
return ICC_Profile.getInstance(profilePath);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static final class Key {
|
||||
private final byte[] data;
|
||||
|
||||
@@ -319,4 +448,78 @@ public final class ColorSpaces {
|
||||
return getClass().getSimpleName() + "@" + Integer.toHexString(hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
// Cache header profile data to avoid excessive array creation/copying in static inner class for on-demand lazy init
|
||||
private static class sRGB {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_sRGB));
|
||||
}
|
||||
|
||||
private static class CIEXYZ {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ));
|
||||
}
|
||||
|
||||
private static class PYCC {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_PYCC));
|
||||
}
|
||||
|
||||
private static class GRAY {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
}
|
||||
|
||||
private static class LINEAR_RGB {
|
||||
private static final byte[] header = getProfileHeaderWithProfileId(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB));
|
||||
}
|
||||
|
||||
private static class Profiles {
|
||||
// TODO: Honour java.iccprofile.path property?
|
||||
private static final Properties PROFILES = loadProfiles();
|
||||
|
||||
private static Properties loadProfiles() {
|
||||
Properties systemDefaults;
|
||||
|
||||
try {
|
||||
systemDefaults = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles_" + Platform.os().id()
|
||||
);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
System.err.printf(
|
||||
"Warning: Could not load system default ICC profile locations from %s, will use bundled fallback profiles.\n",
|
||||
ignore.getMessage()
|
||||
);
|
||||
|
||||
if (DEBUG) {
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
|
||||
systemDefaults = null;
|
||||
}
|
||||
|
||||
// Create map with defaults and add user overrides if any
|
||||
Properties profiles = new Properties(systemDefaults);
|
||||
|
||||
try {
|
||||
Properties userOverrides = SystemUtil.loadProperties(
|
||||
ColorSpaces.class,
|
||||
"com/twelvemonkeys/imageio/color/icc_profiles"
|
||||
);
|
||||
profiles.putAll(userOverrides);
|
||||
}
|
||||
catch (SecurityException | IOException ignore) {
|
||||
// Most likely, this file won't be there
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("User ICC profiles: " + profiles);
|
||||
System.out.println("System ICC profiles : " + systemDefaults);
|
||||
}
|
||||
|
||||
return profiles;
|
||||
}
|
||||
|
||||
static String getPath(final String profileName) {
|
||||
return PROFILES.getProperty(profileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-20
@@ -54,8 +54,8 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
// Our IndexColorModel delegate
|
||||
private final IndexColorModel icm;
|
||||
|
||||
private final int extraSamples;
|
||||
private final int samples;
|
||||
private final boolean hasAlpha;
|
||||
|
||||
/**
|
||||
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
|
||||
@@ -86,33 +86,33 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
);
|
||||
|
||||
this.icm = icm;
|
||||
this.extraSamples = extraSamples;
|
||||
this.samples = 1 + extraSamples;
|
||||
this.hasAlpha = hasAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumComponents() {
|
||||
return getNumColorComponents() + extraSamples;
|
||||
return samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(final int pixel) {
|
||||
public final int getRed(final int pixel) {
|
||||
return icm.getRed(pixel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(final int pixel) {
|
||||
public final int getGreen(final int pixel) {
|
||||
return icm.getGreen(pixel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(final int pixel) {
|
||||
public final int getBlue(final int pixel) {
|
||||
return icm.getBlue(pixel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha(final int pixel) {
|
||||
return hasAlpha() ? (int) ((((float) pixel) / ((1 << getComponentSize(3)) - 1)) * 255.0f + 0.5f) : 0xff;
|
||||
public final int getAlpha(final int pixel) {
|
||||
return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
|
||||
}
|
||||
|
||||
private int getSample(final Object inData, final int index) {
|
||||
@@ -120,15 +120,15 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
|
||||
switch (transferType) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
byte[] bdata = (byte[]) inData;
|
||||
byte bdata[] = (byte[]) inData;
|
||||
pixel = bdata[index] & 0xff;
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
short[] sdata = (short[]) inData;
|
||||
short sdata[] = (short[]) inData;
|
||||
pixel = sdata[index] & 0xffff;
|
||||
break;
|
||||
case DataBuffer.TYPE_INT:
|
||||
int[] idata = (int[]) inData;
|
||||
int idata[] = (int[]) inData;
|
||||
pixel = idata[index];
|
||||
break;
|
||||
default:
|
||||
@@ -139,27 +139,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRed(final Object inData) {
|
||||
public final int getRed(final Object inData) {
|
||||
return getRed(getSample(inData, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGreen(final Object inData) {
|
||||
public final int getGreen(final Object inData) {
|
||||
return getGreen(getSample(inData, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlue(final Object inData) {
|
||||
public final int getBlue(final Object inData) {
|
||||
return getBlue(getSample(inData, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAlpha(final Object inData) {
|
||||
return hasAlpha() ? getAlpha(getSample(inData, 1)) : 0xff;
|
||||
public final int getAlpha(final Object inData) {
|
||||
return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleModel createCompatibleSampleModel(final int w, final int h) {
|
||||
public final SampleModel createCompatibleSampleModel(final int w, final int h) {
|
||||
return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
|
||||
}
|
||||
|
||||
@@ -174,17 +174,17 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatibleSampleModel(final SampleModel sm) {
|
||||
public final boolean isCompatibleSampleModel(final SampleModel sm) {
|
||||
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
||||
public final WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
||||
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCompatibleRaster(final Raster raster) {
|
||||
public final boolean isCompatibleRaster(final Raster raster) {
|
||||
int size = raster.getSampleModel().getSampleSize(0);
|
||||
return ((raster.getTransferType() == transferType) &&
|
||||
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
|
||||
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.color;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
|
||||
import static com.twelvemonkeys.imageio.util.IIOUtil.deregisterProvider;
|
||||
|
||||
/**
|
||||
* This class exists to force early invocation of {@code ProfileDeferralMgr.activateProfiles()},
|
||||
* in an attempt to avoid JDK-6986863 and related bugs in Java < 17.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @see <a href="https://bugs.openjdk.java.net/browse/JDK-6986863">JDK-6986863</a>
|
||||
*/
|
||||
final class ProfileDeferralActivator {
|
||||
|
||||
static {
|
||||
activateProfilesInternal();
|
||||
}
|
||||
|
||||
private static void activateProfilesInternal() {
|
||||
try {
|
||||
// Force invocation of ProfileDeferralMgr.activateProfiles() to avoid JDK-6986863 and friends.
|
||||
// Relies on static initializer in ColorConvertOp to actually invoke ProfileDeferralMgr.activateProfiles()
|
||||
Class.forName("java.awt.image.ColorConvertOp");
|
||||
}
|
||||
catch (Throwable disasters) {
|
||||
System.err.println("ProfileDeferralMgr.activateProfiles() failed. ICC Color Profiles may not work properly, see stack trace below.");
|
||||
System.err.println("For more information, see https://bugs.openjdk.java.net/browse/JDK-6986863");
|
||||
System.err.println("Please upgrade to Java 17 or later where this bug is fixed, or ask your JRE provider to backport the fix.");
|
||||
System.err.println();
|
||||
System.err.println("If you can't update to Java 17, a possible workaround is to add");
|
||||
System.err.println("\tClass.forName(\"java.awt.image.ColorConvertOp\");");
|
||||
System.err.println("*early* in your application startup code, to force profile activation before profiles are accessed.");
|
||||
System.err.println();
|
||||
|
||||
disasters.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
static void activateProfiles() {
|
||||
// This method exists for other classes in the package to
|
||||
// ensure this class' static initializer is run.
|
||||
}
|
||||
|
||||
/**
|
||||
* This is not a service provider, but exploits the SPI mechanism as a hook to force early profile activation.
|
||||
*/
|
||||
public static final class Spi extends ImageInputStreamSpi {
|
||||
@Override public void onRegistration(ServiceRegistry registry, Class<?> category) {
|
||||
activateProfiles();
|
||||
|
||||
deregisterProvider(registry, this, category);
|
||||
}
|
||||
|
||||
@Override public String getDescription(Locale locale) {
|
||||
return getClass().getName();
|
||||
}
|
||||
|
||||
@Override public ImageInputStream createInputStreamInstance(Object input, boolean useCache, File cacheDir) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -61,7 +61,7 @@ public final class UInt32ColorModel extends ComponentColorModel {
|
||||
// This class only supports DataBuffer.TYPE_INT, cast is safe
|
||||
int[] ipixel = (int[]) pixel;
|
||||
for (int c = 0, nc = normOffset; c < numComponents; c++, nc++) {
|
||||
normComponents[nc] = ((float) (ipixel[c] & 0xFFFFFFFFL)) / ((float) ((1L << getComponentSize(c)) - 1));
|
||||
normComponents[nc] = ((float) (ipixel[c] & 0xffffffffl)) / ((float) ((1l << getComponentSize(c)) - 1));
|
||||
}
|
||||
|
||||
int numColorComponents = getNumColorComponents();
|
||||
|
||||
+31
-87
@@ -45,82 +45,36 @@ public final class YCbCrConverter {
|
||||
private final static int CENTERJSAMPLE = 128;
|
||||
private final static int ONE_HALF = 1 << (SCALEBITS - 1);
|
||||
|
||||
private final static class JPEG {
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (ColorSpaces.DEBUG) {
|
||||
System.err.println("Building JPEG YCbCr conversion table");
|
||||
}
|
||||
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
// Cr=>R value is nearest int to 1.40200 * x
|
||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 1.77200 * x
|
||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.71414 * x
|
||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.34414 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
}
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (ColorSpaces.DEBUG) {
|
||||
System.err.println("Building YCC conversion table");
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
// Cr=>R value is nearest int to 1.40200 * x
|
||||
Cr_R_LUT[i] = (int) ((1.40200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 1.77200 * x
|
||||
Cb_B_LUT[i] = (int) ((1.77200 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.71414 * x
|
||||
Cr_G_LUT[i] = -(int) (0.71414 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.34414 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.34414) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
}
|
||||
}
|
||||
|
||||
private final static class ITU_R_601 {
|
||||
private final static int[] Cr_R_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_B_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cr_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Cb_G_LUT = new int[MAXJSAMPLE + 1];
|
||||
private final static int[] Y_LUT = new int[MAXJSAMPLE + 1];
|
||||
|
||||
/**
|
||||
* Initializes tables for YCC->RGB color space conversion.
|
||||
*/
|
||||
private static void buildYCCtoRGBtable() {
|
||||
if (ColorSpaces.DEBUG) {
|
||||
System.err.println("Building ITU-R REC.601 YCbCr conversion table");
|
||||
}
|
||||
|
||||
for (int i = 0, x = -CENTERJSAMPLE; i <= MAXJSAMPLE; i++, x++) {
|
||||
// i is the actual input pixel value, in the range 0..MAXJSAMPLE
|
||||
// The Cb or Cr value we are thinking of is x = i - CENTERJSAMPLE
|
||||
|
||||
// Y'CbCr to RGB conversion, using values from BT.601 specification:
|
||||
// R = 1.16438 * (Y'-16) + 1.59603 * (Cr-128)
|
||||
// G = 1.16438 * (Y'-16) - 0.39176 * (Cb-128) - 0.81297 * (Cr-128)
|
||||
// B = 1.16438 * (Y'-16) + 2.01723 * (Cb-128)
|
||||
|
||||
// Cr=>R value is nearest int to 1.59603 * x
|
||||
Cr_R_LUT[i] = ((int) (1.59603 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cb=>B value is nearest int to 2.01723 * x
|
||||
Cb_B_LUT[i] = ((int) (2.01723 * (1 << SCALEBITS) + 0.5) * x + ONE_HALF) >> SCALEBITS;
|
||||
// Cr=>G value is scaled-up -0.81297 * x
|
||||
Cr_G_LUT[i] = -(int) (0.81297 * (1 << SCALEBITS) + 0.5) * x;
|
||||
// Cb=>G value is scaled-up -0.39176 * x
|
||||
// We also add in ONE_HALF so that need not do it in inner loop
|
||||
Cb_G_LUT[i] = -(int) ((0.39176) * (1 << SCALEBITS) + 0.5) * x + ONE_HALF;
|
||||
|
||||
// Y`=>RGB
|
||||
Y_LUT[i] = ((int) (1.16438 * (1 << SCALEBITS) + 0.5) * (i - 16) + ONE_HALF) >> SCALEBITS;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
static {
|
||||
buildYCCtoRGBtable();
|
||||
}
|
||||
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final double[] coefficients, double[] referenceBW, final int offset) {
|
||||
@@ -154,27 +108,17 @@ public final class YCbCrConverter {
|
||||
rgb[offset + 1] = clamp(green);
|
||||
}
|
||||
|
||||
public static void convertJPEGYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset ] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
public static void convertYCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
|
||||
rgb[offset ] = clamp(y + JPEG.Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (JPEG.Cb_G_LUT[cb] + JPEG.Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + JPEG.Cb_B_LUT[cb]);
|
||||
rgb[offset] = clamp(y + Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(y + (Cb_G_LUT[cb] + Cr_G_LUT[cr] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(y + Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
public static void convertRec601YCbCr2RGB(final byte[] yCbCr, final byte[] rgb, final int offset) {
|
||||
int y = yCbCr[offset ] & 0xff;
|
||||
int cb = yCbCr[offset + 1] & 0xff;
|
||||
int cr = yCbCr[offset + 2] & 0xff;
|
||||
|
||||
rgb[offset ] = clamp(ITU_R_601.Y_LUT[y] + ITU_R_601.Cr_R_LUT[cr]);
|
||||
rgb[offset + 1] = clamp(ITU_R_601.Y_LUT[y] + (ITU_R_601.Cr_G_LUT[cr] + ITU_R_601.Cb_G_LUT[cb] >> SCALEBITS));
|
||||
rgb[offset + 2] = clamp(ITU_R_601.Y_LUT[y] + ITU_R_601.Cb_B_LUT[cb]);
|
||||
}
|
||||
|
||||
private static byte clamp(final int val) {
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -49,10 +49,10 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
private final String[] mimeTypes;
|
||||
private final String readerClassName;
|
||||
private final String[] readerSpiClassNames;
|
||||
private final Class<?>[] inputTypes = new Class<?>[] {ImageInputStream.class};
|
||||
private final Class[] inputTypes = new Class[] {ImageInputStream.class};
|
||||
private final String writerClassName;
|
||||
private final String[] writerSpiClassNames;
|
||||
private final Class<?>[] outputTypes = new Class<?>[] {ImageOutputStream.class};
|
||||
private final Class[] outputTypes = new Class[] {ImageOutputStream.class};
|
||||
private final boolean supportsStandardStreamMetadata;
|
||||
private final String nativeStreamMetadataFormatName;
|
||||
private final String nativeStreamMetadataFormatClassName;
|
||||
@@ -80,7 +80,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
final String writerClassName,
|
||||
final String[] writerSpiClassNames,
|
||||
final boolean supportsStandardStreamMetadata,
|
||||
final String nativeStreamMetadataFormatName,
|
||||
final String nativeStreameMetadataFormatName,
|
||||
final String nativeStreamMetadataFormatClassName,
|
||||
final String[] extraStreamMetadataFormatNames,
|
||||
final String[] extraStreamMetadataFormatClassNames,
|
||||
@@ -99,7 +99,7 @@ public abstract class ReaderWriterProviderInfo extends ProviderInfo {
|
||||
this.writerClassName = writerClassName;
|
||||
this.writerSpiClassNames = writerSpiClassNames;
|
||||
this.supportsStandardStreamMetadata = supportsStandardStreamMetadata;
|
||||
this.nativeStreamMetadataFormatName = nativeStreamMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatName = nativeStreameMetadataFormatName;
|
||||
this.nativeStreamMetadataFormatClassName = nativeStreamMetadataFormatClassName;
|
||||
this.extraStreamMetadataFormatNames = extraStreamMetadataFormatNames;
|
||||
this.extraStreamMetadataFormatClassNames = extraStreamMetadataFormatClassNames;
|
||||
|
||||
-348
@@ -1,348 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
/**
|
||||
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
|
||||
* and provides greatly improved performance
|
||||
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
|
||||
* for shorter reads, like single byte or bit reads.
|
||||
*/
|
||||
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
|
||||
private static final Closeable CLOSEABLE_STUB = new Closeable() {
|
||||
@Override public void close() {}
|
||||
};
|
||||
|
||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
|
||||
private byte[] buffer = byteBuffer.array();
|
||||
private int bufferPos;
|
||||
private int bufferLimit;
|
||||
|
||||
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
|
||||
private final byte[] integralCacheArray = integralCache.array();
|
||||
|
||||
private SeekableByteChannel channel;
|
||||
private Closeable closeable;
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
|
||||
*
|
||||
* @param file a {@code File} to read from.
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||
* @throws IOException if an I/O error occurs while opening the file.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final File file) throws IOException {
|
||||
this(notNull(file, "file").toPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
|
||||
*
|
||||
* @param file a {@code Path} to read from.
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
|
||||
* @throws IOException if an I/O error occurs while opening the file.
|
||||
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final Path file) throws IOException {
|
||||
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
|
||||
*
|
||||
* @param file a {@code RandomAccessFile} to read from.
|
||||
* @throws IllegalArgumentException if {@code file} is {@code null}.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final RandomAccessFile file) {
|
||||
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
|
||||
this(notNull(file, "file").getChannel(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
|
||||
* <p>
|
||||
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
|
||||
* </p>
|
||||
*
|
||||
* @param inputStream a {@code FileInputStream} to read from.
|
||||
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
|
||||
this(notNull(inputStream, "inputStream").getChannel(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
|
||||
* <p>
|
||||
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
|
||||
* </p>
|
||||
*
|
||||
* @param channel a {@code SeekableByteChannel} to read from.
|
||||
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||
*/
|
||||
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
|
||||
this(notNull(channel, "channel"), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
|
||||
* <p>
|
||||
* Closing this stream will close the {@code Cache}.
|
||||
* </p>
|
||||
*
|
||||
* @param cache a {@code SeekableByteChannel} to read from.
|
||||
* @throws IllegalArgumentException if {@code channel} is {@code null}.
|
||||
*/
|
||||
BufferedChannelImageInputStream(final Cache cache) {
|
||||
this(notNull(cache, "cache"), true);
|
||||
}
|
||||
|
||||
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
|
||||
this.channel = notNull(channel, "channel");
|
||||
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean fillBuffer() throws IOException {
|
||||
byteBuffer.rewind();
|
||||
int length = channel.read(byteBuffer);
|
||||
bufferPos = 0;
|
||||
bufferLimit = max(length, 0);
|
||||
|
||||
return bufferLimit > 0;
|
||||
}
|
||||
|
||||
private boolean bufferEmpty() {
|
||||
return bufferPos >= bufferLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByteOrder(ByteOrder byteOrder) {
|
||||
super.setByteOrder(byteOrder);
|
||||
integralCache.order(byteOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (bufferEmpty() && !fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
streamPos++;
|
||||
|
||||
return buffer[bufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
checkClosed();
|
||||
bitOffset = 0;
|
||||
|
||||
if (bufferEmpty()) {
|
||||
// Bypass buffer if buffer is empty for reads longer than buffer
|
||||
if (length >= buffer.length) {
|
||||
return readDirect(bytes, offset, length);
|
||||
}
|
||||
else if (!fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int fromBuffer = readBuffered(bytes, offset, length);
|
||||
|
||||
if (length > fromBuffer) {
|
||||
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
|
||||
// we'll read as much as possible from the buffer, and the rest directly after
|
||||
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
|
||||
}
|
||||
|
||||
return fromBuffer;
|
||||
}
|
||||
|
||||
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
|
||||
bufferLimit = 0;
|
||||
|
||||
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
|
||||
int read = 0;
|
||||
while (wrapped.hasRemaining()) {
|
||||
int count = channel.read(wrapped);
|
||||
if (count == -1) {
|
||||
if (read == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
read += count;
|
||||
}
|
||||
|
||||
streamPos += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private int readBuffered(final byte[] bytes, final int offset, final int length) {
|
||||
// Read as much as possible from buffer
|
||||
int available = Math.min(bufferLimit - bufferPos, length);
|
||||
|
||||
if (available > 0) {
|
||||
System.arraycopy(buffer, bufferPos, bytes, offset, available);
|
||||
bufferPos += available;
|
||||
streamPos += available;
|
||||
}
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
public long length() {
|
||||
// WTF?! This method is allowed to throw IOException in the interface...
|
||||
try {
|
||||
checkClosed();
|
||||
return channel.size();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
|
||||
buffer = null;
|
||||
byteBuffer = null;
|
||||
|
||||
channel = null;
|
||||
|
||||
try {
|
||||
closeable.close();
|
||||
}
|
||||
finally {
|
||||
closeable = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to override the readShort(), readInt() and readLong() methods,
|
||||
// because the implementations in ImageInputStreamImpl expects the
|
||||
// read(byte[], int, int) to always read the expected number of bytes,
|
||||
// causing uninitialized values, alignment issues and EOFExceptions at
|
||||
// random places...
|
||||
// Notes:
|
||||
// * readUnsignedXx() is covered by their signed counterparts
|
||||
// * readChar() is covered by readShort()
|
||||
// * readFloat() and readDouble() is covered by readInt() and readLong()
|
||||
// respectively.
|
||||
// * readLong() may be covered by two readInt()s, we'll override to be safe
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
readFully(integralCacheArray, 0, 2);
|
||||
|
||||
return integralCache.getShort(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
readFully(integralCacheArray, 0, 4);
|
||||
|
||||
return integralCache.getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
readFully(integralCacheArray, 0, 8);
|
||||
|
||||
return integralCache.getLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (position < flushedPos) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPos!");
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
|
||||
if (streamPos == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimized to not invalidate buffer if new position is within current buffer
|
||||
long newBufferPos = bufferPos + position - streamPos;
|
||||
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
|
||||
bufferPos = (int) newBufferPos;
|
||||
}
|
||||
else {
|
||||
// Will invalidate buffer
|
||||
bufferLimit = 0;
|
||||
channel.position(position);
|
||||
}
|
||||
|
||||
streamPos = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBefore(final long pos) throws IOException {
|
||||
super.flushBefore(pos);
|
||||
|
||||
if (channel instanceof Cache) {
|
||||
// In case of memory cache, free up memory
|
||||
((Cache) channel).flushBefore(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
-261
@@ -1,261 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.max;
|
||||
|
||||
/**
|
||||
* A buffered replacement for {@link javax.imageio.stream.FileImageInputStream}
|
||||
* that provides greatly improved performance for shorter reads, like single
|
||||
* byte or bit reads.
|
||||
* As with {@code javax.imageio.stream.FileImageInputStream}, either
|
||||
* {@link File} or {@link RandomAccessFile} can be used as input.
|
||||
*
|
||||
* @see javax.imageio.stream.FileImageInputStream
|
||||
* @deprecated Use {@link BufferedChannelImageInputStream} instead.
|
||||
*/
|
||||
// TODO: Create a memory-mapped version?
|
||||
// Or not... From java.nio.channels.FileChannel.map:
|
||||
// For most operating systems, mapping a file into memory is more
|
||||
// expensive than reading or writing a few tens of kilobytes of data via
|
||||
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||
// standpoint of performance it is generally only worth mapping relatively
|
||||
// large files into memory.
|
||||
@Deprecated
|
||||
public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
|
||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
private byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
|
||||
private int bufferPos;
|
||||
private int bufferLimit;
|
||||
|
||||
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
|
||||
private final byte[] integralCacheArray = integralCache.array();
|
||||
|
||||
private RandomAccessFile raf;
|
||||
|
||||
/**
|
||||
* Constructs a <code>BufferedFileImageInputStream</code> that will read from a given <code>File</code>.
|
||||
*
|
||||
* @param file a <code>File</code> to read from.
|
||||
* @throws IllegalArgumentException if <code>file</code> is <code>null</code>.
|
||||
* @throws FileNotFoundException if <code>file</code> is a directory or cannot be opened for reading
|
||||
* for any reason.
|
||||
*/
|
||||
public BufferedFileImageInputStream(final File file) throws FileNotFoundException {
|
||||
this(new RandomAccessFile(notNull(file, "file"), "r"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a <code>BufferedFileImageInputStream</code> that will read from a given <code>RandomAccessFile</code>.
|
||||
*
|
||||
* @param raf a <code>RandomAccessFile</code> to read from.
|
||||
* @throws IllegalArgumentException if <code>raf</code> is <code>null</code>.
|
||||
*/
|
||||
public BufferedFileImageInputStream(final RandomAccessFile raf) {
|
||||
this.raf = notNull(raf, "raf");
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean fillBuffer() throws IOException {
|
||||
int length = raf.read(buffer, 0, buffer.length);
|
||||
bufferPos = 0;
|
||||
bufferLimit = max(length, 0);
|
||||
|
||||
return bufferLimit > 0;
|
||||
}
|
||||
|
||||
private boolean bufferEmpty() {
|
||||
return bufferPos >= bufferLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setByteOrder(ByteOrder byteOrder) {
|
||||
super.setByteOrder(byteOrder);
|
||||
integralCache.order(byteOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (bufferEmpty() && !fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
streamPos++;
|
||||
|
||||
return buffer[bufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
checkClosed();
|
||||
bitOffset = 0;
|
||||
|
||||
if (bufferEmpty()) {
|
||||
// Bypass buffer if buffer is empty for reads longer than buffer
|
||||
if (length >= buffer.length) {
|
||||
return readDirect(bytes, offset, length);
|
||||
}
|
||||
else if (!fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int fromBuffer = readBuffered(bytes, offset, length);
|
||||
|
||||
if (length > fromBuffer) {
|
||||
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
|
||||
// we'll read as much as possible from the buffer, and the rest directly after
|
||||
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
|
||||
}
|
||||
|
||||
return fromBuffer;
|
||||
}
|
||||
|
||||
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
|
||||
bufferLimit = 0;
|
||||
int read = raf.read(bytes, offset, length);
|
||||
|
||||
if (read > 0) {
|
||||
streamPos += read;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private int readBuffered(final byte[] bytes, final int offset, final int length) {
|
||||
// Read as much as possible from buffer
|
||||
int available = Math.min(bufferLimit - bufferPos, length);
|
||||
|
||||
if (available > 0) {
|
||||
System.arraycopy(buffer, bufferPos, bytes, offset, available);
|
||||
bufferPos += available;
|
||||
streamPos += available;
|
||||
}
|
||||
|
||||
return available;
|
||||
}
|
||||
|
||||
public long length() {
|
||||
// WTF?! This method is allowed to throw IOException in the interface...
|
||||
try {
|
||||
checkClosed();
|
||||
return raf.length();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
|
||||
buffer = null;
|
||||
|
||||
raf.close();
|
||||
raf = null;
|
||||
}
|
||||
|
||||
// Need to override the readShort(), readInt() and readLong() methods,
|
||||
// because the implementations in ImageInputStreamImpl expects the
|
||||
// read(byte[], int, int) to always read the expected number of bytes,
|
||||
// causing uninitialized values, alignment issues and EOFExceptions at
|
||||
// random places...
|
||||
// Notes:
|
||||
// * readUnsignedXx() is covered by their signed counterparts
|
||||
// * readChar() is covered by readShort()
|
||||
// * readFloat() and readDouble() is covered by readInt() and readLong()
|
||||
// respectively.
|
||||
// * readLong() may be covered by two readInt()s, we'll override to be safe
|
||||
|
||||
@Override
|
||||
public short readShort() throws IOException {
|
||||
readFully(integralCacheArray, 0, 2);
|
||||
|
||||
return integralCache.getShort(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readInt() throws IOException {
|
||||
readFully(integralCacheArray, 0, 4);
|
||||
|
||||
return integralCache.getInt(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long readLong() throws IOException {
|
||||
readFully(integralCacheArray, 0, 8);
|
||||
|
||||
return integralCache.getLong(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long position) throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (position < flushedPos) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPos!");
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
|
||||
if (streamPos == position) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimized to not invalidate buffer if new position is within current buffer
|
||||
long newBufferPos = bufferPos + position - streamPos;
|
||||
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
|
||||
bufferPos = (int) newBufferPos;
|
||||
}
|
||||
else {
|
||||
// Will invalidate buffer
|
||||
bufferLimit = 0;
|
||||
raf.seek(position);
|
||||
}
|
||||
|
||||
streamPos = position;
|
||||
}
|
||||
}
|
||||
-107
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamSpi
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||
*/
|
||||
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public BufferedFileImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
|
||||
private BufferedFileImageInputStreamSpi(ProviderInfo providerInfo) {
|
||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), File.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new FileInputFilter(), true);
|
||||
|
||||
while (providers.hasNext()) {
|
||||
ImageInputStreamSpi provider = providers.next();
|
||||
if (provider != this) {
|
||||
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||
if (input instanceof File) {
|
||||
try {
|
||||
return new BufferedChannelImageInputStream((File) input);
|
||||
}
|
||||
catch (FileNotFoundException | NoSuchFileException e) {
|
||||
// For consistency with the JRE bundled SPIs, we'll return null here,
|
||||
// even though the spec does not say that's allowed.
|
||||
// The problem is that the SPIs can only declare that they support an input type like a File,
|
||||
// instead they should be allowed to inspect the instance, to see that the file does exist...
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type File: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseCacheFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a File";
|
||||
}
|
||||
|
||||
private static class FileInputFilter implements ServiceRegistry.Filter {
|
||||
@Override
|
||||
public boolean filter(final Object provider) {
|
||||
return ((ImageInputStreamSpi) provider).getInputClass() == File.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-7
@@ -43,19 +43,15 @@ import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
* A buffered {@code ImageInputStream}.
|
||||
* Experimental - seems to be effective for {@link javax.imageio.stream.FileImageInputStream}
|
||||
* and {@link javax.imageio.stream.FileCacheImageInputStream} when doing a lot of single-byte reads
|
||||
* (or short byte-array reads).
|
||||
* (or short byte-array reads) on OS X at least.
|
||||
* Code that uses the {@code readFully} methods are not affected by the issue.
|
||||
* <p>
|
||||
* NOTE: Invoking {@code close()} will <em>NOT</em> close the wrapped stream.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStream.java,v 1.0 May 15, 2008 4:36:49 PM haraldk Exp$
|
||||
*
|
||||
* @deprecated Use {@link BufferedFileImageInputStream} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
// TODO: Create a provider for this (wrapping the FileIIS and FileCacheIIS classes), and disable the Sun built-in spis?
|
||||
// TODO: Test on other platforms, might be just an OS X issue
|
||||
public final class BufferedImageInputStream extends ImageInputStreamImpl implements ImageInputStream {
|
||||
static final int DEFAULT_BUFFER_SIZE = 8192;
|
||||
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* BufferedInputStreamImageInputStreamSpi.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
|
||||
*/
|
||||
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public BufferedInputStreamImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
|
||||
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
|
||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
|
||||
|
||||
while (providers.hasNext()) {
|
||||
ImageInputStreamSpi provider = providers.next();
|
||||
if (provider != this) {
|
||||
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||
if (input instanceof InputStream) {
|
||||
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
|
||||
|
||||
if (channel instanceof SeekableByteChannel) {
|
||||
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
|
||||
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
|
||||
}
|
||||
|
||||
// Otherwise, create a cache for backwards seeking
|
||||
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseCacheFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Service provider that instantiates an ImageInputStream from an InputStream";
|
||||
}
|
||||
|
||||
private static class InputStreamFilter implements ServiceRegistry.Filter {
|
||||
@Override
|
||||
public boolean filter(final Object provider) {
|
||||
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
-97
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.util.Iterator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* BufferedRAFImageInputStreamSpi
|
||||
* Experimental
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||
*/
|
||||
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public BufferedRAFImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
|
||||
private BufferedRAFImageInputStreamSpi(ProviderInfo providerInfo) {
|
||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), RandomAccessFile.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
|
||||
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new RAFInputFilter(), true);
|
||||
|
||||
while (providers.hasNext()) {
|
||||
ImageInputStreamSpi provider = providers.next();
|
||||
if (provider != this) {
|
||||
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
|
||||
if (input instanceof RandomAccessFile) {
|
||||
return new BufferedChannelImageInputStream((RandomAccessFile) input);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canUseCacheFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final Locale locale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
|
||||
}
|
||||
|
||||
private static class RAFInputFilter implements ServiceRegistry.Filter {
|
||||
@Override
|
||||
public boolean filter(final Object provider) {
|
||||
return ((ImageInputStreamSpi) provider).getInputClass() == RandomAccessFile.class;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
-11
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
|
||||
private final int dataOffset;
|
||||
private final int dataLength;
|
||||
|
||||
public ByteArrayImageInputStream(final byte[] data) {
|
||||
this(data, 0, data != null ? data.length : -1);
|
||||
public ByteArrayImageInputStream(final byte[] pData) {
|
||||
this(pData, 0, pData != null ? pData.length : -1);
|
||||
}
|
||||
|
||||
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");
|
||||
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");
|
||||
}
|
||||
|
||||
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));
|
||||
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));
|
||||
}
|
||||
|
||||
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[] buffer, int offset, int len) throws IOException {
|
||||
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
if (streamPos >= dataLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int length = (int) Math.min(dataLength - streamPos, len);
|
||||
int length = (int) Math.min(this.dataLength - streamPos, pLength);
|
||||
bitOffset = 0;
|
||||
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
|
||||
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
|
||||
streamPos += length;
|
||||
|
||||
return length;
|
||||
|
||||
+8
-9
@@ -45,7 +45,7 @@ import java.util.Locale;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
|
||||
*/
|
||||
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
|
||||
public ByteArrayImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
@@ -55,17 +55,16 @@ public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) {
|
||||
if (input instanceof byte[]) {
|
||||
return new ByteArrayImageInputStream((byte[]) input);
|
||||
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
|
||||
if (pInput instanceof byte[]) {
|
||||
return new ByteArrayImageInputStream((byte[]) pInput);
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type byte[]: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(Locale locale) {
|
||||
public String getDescription(Locale pLocale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a byte array";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
|
||||
interface Cache extends SeekableByteChannel {
|
||||
void flushBefore(long pos);
|
||||
}
|
||||
-133
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* An {@code ImageInputStream} that adapts an {@code InputSteam},
|
||||
* by reading directly from the stream without and form of caching or buffering.
|
||||
* <p>
|
||||
* Note: This is <em>not</em> a general-purpose {@code ImageInputStream}, and is designed for reading large chunks,
|
||||
* typically of pixel data, from an {@code InputStream}.
|
||||
* It does <em>not</em> support backwards seeking, or reading bits.
|
||||
* </p>
|
||||
*/
|
||||
public final class DirectImageInputStream extends ImageInputStreamImpl {
|
||||
private final InputStream stream;
|
||||
private final long length;
|
||||
|
||||
public DirectImageInputStream(final InputStream stream) {
|
||||
this(stream, -1L);
|
||||
}
|
||||
|
||||
public DirectImageInputStream(final InputStream stream, long length) {
|
||||
this.stream = notNull(stream, "stream");
|
||||
this.length = isTrue(length >= 0L || length == -1L, length, "negative length: %d");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
bitOffset = 0;
|
||||
streamPos++;
|
||||
return stream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] bytes, int off, int len) throws IOException {
|
||||
bitOffset = 0;
|
||||
|
||||
int read = stream.read(bytes, off, len);
|
||||
if (read > 0) {
|
||||
streamPos += read;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (pos < streamPos) {
|
||||
// Handle as if flushedPos == streamPos at any time
|
||||
throw new IndexOutOfBoundsException("pos < flushedPos");
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
|
||||
while (streamPos < pos) {
|
||||
long skipped = stream.skip(pos - streamPos);
|
||||
|
||||
if (skipped <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
streamPos += skipped;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFlushedPosition() {
|
||||
// Handle as if flushedPos == streamPos at any time
|
||||
return streamPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Override
|
||||
public int readBit() throws IOException {
|
||||
throw new UnsupportedOperationException("Bit reading not supported");
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Override
|
||||
public long readBits(int numBits) throws IOException {
|
||||
throw new UnsupportedOperationException("Bit reading not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// We could seek to EOF here, but the usual case is we know where the next chunk of data is
|
||||
|
||||
stream.close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
|
||||
import static java.nio.file.StandardOpenOption.READ;
|
||||
import static java.nio.file.StandardOpenOption.WRITE;
|
||||
|
||||
// Note: We could consider creating a memory-mapped version...
|
||||
// But, from java.nio.channels.FileChannel.map:
|
||||
// For most operating systems, mapping a file into memory is more
|
||||
// expensive than reading or writing a few tens of kilobytes of data via
|
||||
// the usual {@link #read read} and {@link #write write} methods. From the
|
||||
// standpoint of performance it is generally only worth mapping relatively
|
||||
// large files into memory.
|
||||
final class FileCache implements Cache {
|
||||
final static int BLOCK_SIZE = 1 << 13;
|
||||
|
||||
private final FileChannel cache;
|
||||
private final ReadableByteChannel channel;
|
||||
|
||||
// TODO: Perhaps skip this constructor?
|
||||
FileCache(InputStream stream, File cacheDir) throws IOException {
|
||||
// Stream will be closed with channel, documented behavior
|
||||
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
|
||||
}
|
||||
|
||||
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
|
||||
this.channel = notNull(channel, "channel");
|
||||
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
|
||||
|
||||
// Create a temp file to hold our cache,
|
||||
// will be deleted when this channel is closed, as we close the cache
|
||||
Path cacheFile = cacheDir == null
|
||||
? Files.createTempFile("imageio", ".tmp")
|
||||
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
|
||||
|
||||
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("StatementWithEmptyBody")
|
||||
void fetch() throws IOException {
|
||||
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
|
||||
// Continue transfer...
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return channel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
cache.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dest) throws IOException {
|
||||
fetch();
|
||||
|
||||
if (cache.position() >= cache.size()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return cache.read(dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException {
|
||||
return cache.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||
cache.position(newPosition);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() {
|
||||
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate(long size) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override public void flushBefore(long pos) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.NonWritableChannelException;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
final class MemoryCache implements Cache {
|
||||
|
||||
final static int BLOCK_SIZE = 1 << 13;
|
||||
|
||||
private static final byte[] NULL_BLOCK = new byte[0];
|
||||
|
||||
private final List<byte[]> cache = new ArrayList<>();
|
||||
private final ReadableByteChannel channel;
|
||||
|
||||
private int maxBlock = Integer.MAX_VALUE;
|
||||
private long length;
|
||||
private long position;
|
||||
private long start;
|
||||
|
||||
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
|
||||
MemoryCache(InputStream stream) {
|
||||
this(Channels.newChannel(notNull(stream, "stream")));
|
||||
}
|
||||
|
||||
public MemoryCache(ReadableByteChannel channel) {
|
||||
this.channel = notNull(channel, "channel");
|
||||
}
|
||||
|
||||
byte[] fetchBlock() throws IOException {
|
||||
long currPos = position;
|
||||
long index = currPos / BLOCK_SIZE;
|
||||
|
||||
if (index >= Integer.MAX_VALUE) {
|
||||
throw new IOException("Memory cache max size exceeded");
|
||||
}
|
||||
if (index > maxBlock) {
|
||||
return NULL_BLOCK;
|
||||
}
|
||||
|
||||
while (index >= cache.size()) {
|
||||
byte[] block;
|
||||
try {
|
||||
block = new byte[BLOCK_SIZE];
|
||||
}
|
||||
catch (OutOfMemoryError e) {
|
||||
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
|
||||
}
|
||||
|
||||
cache.add(block);
|
||||
int bytesRead = readBlock(block);
|
||||
length += bytesRead;
|
||||
|
||||
if (bytesRead < BLOCK_SIZE) {
|
||||
// Last block, EOF found
|
||||
maxBlock = (int) index;
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
return cache.get((int) index);
|
||||
}
|
||||
|
||||
private int readBlock(final byte[] block) throws IOException {
|
||||
ByteBuffer wrapped = ByteBuffer.wrap(block);
|
||||
|
||||
while (wrapped.hasRemaining()) {
|
||||
int count = channel.read(wrapped);
|
||||
if (count == -1) {
|
||||
// Last block, EOF found
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return wrapped.position();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return channel.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
cache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(ByteBuffer dest) throws IOException {
|
||||
byte[] buffer = fetchBlock();
|
||||
|
||||
if (position >= length) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int bufferPos = (int) (position % BLOCK_SIZE);
|
||||
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
|
||||
dest.put(buffer, bufferPos, len);
|
||||
|
||||
position += len;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long position() throws IOException {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel position(long newPosition) throws IOException {
|
||||
if (newPosition < start) {
|
||||
throw new IOException("Seek before flush position");
|
||||
}
|
||||
|
||||
this.position = newPosition;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long size() throws IOException {
|
||||
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int write(ByteBuffer src) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SeekableByteChannel truncate(long size) {
|
||||
throw new NonWritableChannelException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flushBefore(long pos) {
|
||||
if (pos < start) {
|
||||
throw new IndexOutOfBoundsException("pos < flushed position");
|
||||
}
|
||||
if (pos > position) {
|
||||
throw new IndexOutOfBoundsException("pos > current position");
|
||||
}
|
||||
|
||||
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
|
||||
|
||||
// Clear blocks no longer needed
|
||||
for (int i = 0; i < blocks; i++) {
|
||||
cache.set(i, null);
|
||||
}
|
||||
|
||||
start = pos;
|
||||
}
|
||||
}
|
||||
|
||||
+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 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.
|
||||
* @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.
|
||||
*
|
||||
* @throws IOException if {@code stream}'s position can't be determined.
|
||||
* @throws IllegalArgumentException if {@code stream == null} or {@code length < 0}
|
||||
* @throws IOException if {@code pStream}'s position can't be determined.
|
||||
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
|
||||
*/
|
||||
public SubImageInputStream(final ImageInputStream stream, final long length) throws IOException {
|
||||
Validate.notNull(stream, "stream");
|
||||
Validate.isTrue(length >= 0, length, "length < 0: %d");
|
||||
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
|
||||
Validate.notNull(pStream, "stream");
|
||||
Validate.isTrue(pLength >= 0, pLength, "length < 0: %d");
|
||||
|
||||
this.stream = stream;
|
||||
this.startPos = stream.getStreamPosition();
|
||||
this.length = length;
|
||||
stream = pStream;
|
||||
startPos = pStream.getStreamPosition();
|
||||
length = pLength;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
@@ -84,14 +84,14 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public int read(final byte[] bytes, final int off, final int len) throws IOException {
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (streamPos >= length) { // Local EOF
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
|
||||
if (count >= 0) {
|
||||
streamPos += count;
|
||||
@@ -113,18 +113,18 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(final long position) throws IOException {
|
||||
if (position < getFlushedPosition()) {
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
if (pPosition < getFlushedPosition()) {
|
||||
throw new IndexOutOfBoundsException("pos < flushedPosition");
|
||||
}
|
||||
|
||||
stream.seek(startPos + position);
|
||||
streamPos = position;
|
||||
stream.seek(startPos + pPosition);
|
||||
streamPos = pPosition;
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodDoesntCallSuperMethod")
|
||||
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
|
||||
@Override
|
||||
protected void finalize() {
|
||||
protected void finalize() throws Throwable {
|
||||
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
|
||||
}
|
||||
}
|
||||
|
||||
+43
-15
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2021, Harald Kuhr
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
@@ -33,7 +33,10 @@ package com.twelvemonkeys.imageio.stream;
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.stream.FileCacheImageInputStream;
|
||||
import javax.imageio.stream.FileImageInputStream;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -50,7 +53,7 @@ import java.util.Locale;
|
||||
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
|
||||
*/
|
||||
// TODO: URI instead of URL?
|
||||
public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
public URLImageInputStreamSpi() {
|
||||
this(new StreamProviderInfo());
|
||||
}
|
||||
@@ -62,28 +65,53 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
|
||||
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
|
||||
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
|
||||
@Override
|
||||
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
|
||||
if (input instanceof URL) {
|
||||
URL url = (URL) input;
|
||||
public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
|
||||
if (pInput instanceof URL) {
|
||||
URL url = (URL) pInput;
|
||||
|
||||
// Special case for file protocol, a lot faster than FileCacheImageInputStream
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
try {
|
||||
return new BufferedChannelImageInputStream(new File(url.toURI()));
|
||||
return new BufferedImageInputStream(new FileImageInputStream(new File(url.toURI())));
|
||||
}
|
||||
catch (URISyntaxException shouldNeverHappen) {
|
||||
// This should never happen, but if it does, we'll fall back to using the stream
|
||||
shouldNeverHappen.printStackTrace();
|
||||
catch (URISyntaxException ignore) {
|
||||
// This should never happen, but if it does, we'll fall back to using the stream
|
||||
ignore.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise revert to cached
|
||||
InputStream urlStream = url.openStream();
|
||||
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
|
||||
final InputStream stream = url.openStream();
|
||||
if (pUseCache) {
|
||||
return new BufferedImageInputStream(new FileCacheImageInputStream(stream, pCacheDir) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
}
|
||||
finally {
|
||||
stream.close(); // NOTE: If this line throws IOE, it will shadow the original..
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
return new MemoryCacheImageInputStream(stream) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
}
|
||||
finally {
|
||||
stream.close(); // NOTE: If this line throws IOE, it will shadow the original..
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Expected input of type URL: " + pInput);
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Expected input of type URL: " + input);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,7 +119,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getDescription(final Locale locale) {
|
||||
public String getDescription(final Locale pLocale) {
|
||||
return "Service provider that instantiates an ImageInputStream from a URL";
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -81,7 +81,7 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
|
||||
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
|
||||
Validate.notNull(pInput, "stream");
|
||||
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %d");
|
||||
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %f");
|
||||
|
||||
input = pInput;
|
||||
left = pLength;
|
||||
|
||||
@@ -45,7 +45,6 @@ import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Iterator;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
@@ -159,39 +158,33 @@ public final class IIOUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
|
||||
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
|
||||
*
|
||||
* @param registry the registry to unregister from.
|
||||
* @param provider the provider to unregister.
|
||||
* @param category the category to unregister from.
|
||||
*/
|
||||
public static <T> void deregisterProvider(final ServiceRegistry registry, final IIOServiceProvider provider, final Class<T> category) {
|
||||
// http://www.ibm.com/developerworks/java/library/j-jtp04298.html
|
||||
registry.deregisterServiceProvider(category.cast(provider), category);
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD WILL BE MOVED/RENAMED, DO NOT USE.
|
||||
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
|
||||
*
|
||||
* @param registry the registry to lookup from.
|
||||
* @param providerClassName name of the provider class.
|
||||
* @param category provider category
|
||||
*
|
||||
* @return the provider instance, or {@code null} if not found
|
||||
* @return the provider instance, or {@code null}.
|
||||
*/
|
||||
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
|
||||
// NOTE: While more verbose, this is more OSGi-friendly than using
|
||||
// registry.getServiceProviderByClass(Class.forName(providerClassName))
|
||||
Iterator<T> providers = registry.getServiceProviders(category, true);
|
||||
|
||||
while (providers.hasNext()) {
|
||||
T provider = providers.next();
|
||||
|
||||
if (provider.getClass().getName().equals(providerClassName)) {
|
||||
return provider;
|
||||
}
|
||||
try {
|
||||
return category.cast(registry.getServiceProviderByClass(Class.forName(providerClassName)));
|
||||
}
|
||||
catch (ClassNotFoundException ignore) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,12 +223,7 @@ public final class IIOUtil {
|
||||
public static void subsampleRow(byte[] srcRow, int srcPos, int srcWidth,
|
||||
byte[] destRow, int destPos,
|
||||
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
|
||||
// Period == 1 is a no-op...
|
||||
if (samplePeriod == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
|
||||
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
|
||||
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 8 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
|
||||
"bitsPerSample must be > 0 and <= 8 and a power of 2");
|
||||
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
|
||||
@@ -273,12 +261,7 @@ public final class IIOUtil {
|
||||
public static void subsampleRow(short[] srcRow, int srcPos, int srcWidth,
|
||||
short[] destRow, int destPos,
|
||||
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
|
||||
// Period == 1 is a no-op...
|
||||
if (samplePeriod == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
|
||||
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
|
||||
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 16 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
|
||||
"bitsPerSample must be > 0 and <= 16 and a power of 2");
|
||||
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
|
||||
@@ -295,12 +278,7 @@ public final class IIOUtil {
|
||||
public static void subsampleRow(int[] srcRow, int srcPos, int srcWidth,
|
||||
int[] destRow, int destPos,
|
||||
int samplesPerPixel, int bitsPerSample, int samplePeriod) {
|
||||
// Period == 1 is a no-op...
|
||||
if (samplePeriod == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1");
|
||||
Validate.isTrue(samplePeriod > 1, "samplePeriod must be > 1"); // Period == 1 could be a no-op...
|
||||
Validate.isTrue(bitsPerSample > 0 && bitsPerSample <= 32 && (bitsPerSample == 1 || bitsPerSample % 2 == 0),
|
||||
"bitsPerSample must be > 0 and <= 32 and a power of 2");
|
||||
Validate.isTrue(samplesPerPixel > 0, "samplesPerPixel must be > 0");
|
||||
|
||||
+39
-112
@@ -33,7 +33,7 @@ package com.twelvemonkeys.imageio.util;
|
||||
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
@@ -51,52 +51,28 @@ import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
*/
|
||||
public final class ImageTypeSpecifiers {
|
||||
|
||||
private static final ImageTypeSpecifier TYPE_INT_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
|
||||
0xFF0000,
|
||||
0x00FF00,
|
||||
0x0000FF,
|
||||
0x0,
|
||||
DataBuffer.TYPE_INT,
|
||||
false);
|
||||
private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
|
||||
0x0000FF,
|
||||
0x00FF00,
|
||||
0xFF0000,
|
||||
0x0,
|
||||
DataBuffer.TYPE_INT,
|
||||
false);
|
||||
private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16,
|
||||
0xF800,
|
||||
0x07E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15,
|
||||
0x7C00,
|
||||
0x03E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
|
||||
private ImageTypeSpecifiers() {}
|
||||
|
||||
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
|
||||
switch (bufferedImageType) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types
|
||||
case BufferedImage.TYPE_INT_RGB:
|
||||
return TYPE_INT_RGB;
|
||||
|
||||
case BufferedImage.TYPE_INT_BGR:
|
||||
return TYPE_INT_BGR;
|
||||
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types
|
||||
case BufferedImage.TYPE_USHORT_565_RGB:
|
||||
return TYPE_USHORT_565_RGB;
|
||||
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
0xF800,
|
||||
0x07E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
|
||||
case BufferedImage.TYPE_USHORT_555_RGB:
|
||||
return TYPE_USHORT_555_RGB;
|
||||
|
||||
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
|
||||
0x7C00,
|
||||
0x03E0,
|
||||
0x001F,
|
||||
0x0,
|
||||
DataBuffer.TYPE_USHORT,
|
||||
false);
|
||||
default:
|
||||
}
|
||||
|
||||
@@ -107,41 +83,23 @@ public final class ImageTypeSpecifiers {
|
||||
final int redMask, final int greenMask,
|
||||
final int blueMask, final int alphaMask,
|
||||
final int transferType, boolean isAlphaPremultiplied) {
|
||||
int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask);
|
||||
if (bits != 32) {
|
||||
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
|
||||
return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
|
||||
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
|
||||
|
||||
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
|
||||
isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
|
||||
}
|
||||
|
||||
private static int calculateRequiredBits(int mask) {
|
||||
// See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
|
||||
int r = 1;
|
||||
|
||||
while ((mask >>>= 1) != 0) {
|
||||
r++;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits,
|
||||
final int redMask, final int greenMask,
|
||||
final int blueMask, final int alphaMask,
|
||||
final int transferType, boolean isAlphaPremultiplied) {
|
||||
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround
|
||||
notNull(colorSpace, "colorSpace");
|
||||
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
|
||||
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
|
||||
|
||||
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
|
||||
isAlphaPremultiplied, transferType);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace,
|
||||
final int[] bandOffsets,
|
||||
final int dataType,
|
||||
@@ -211,36 +169,21 @@ public final class ImageTypeSpecifiers {
|
||||
|
||||
int numEntries = 1 << bits;
|
||||
|
||||
ColorModel colorModel;
|
||||
byte[] r = new byte[numEntries];
|
||||
byte[] g = new byte[numEntries];
|
||||
byte[] b = new byte[numEntries];
|
||||
|
||||
if (ColorSpace.getInstance(ColorSpace.CS_GRAY).equals(colorSpace)) {
|
||||
// For default gray, use linear response
|
||||
byte[] gray = new byte[numEntries];
|
||||
// Scale array values according to color profile..
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
float[] gray = new float[]{i / (float) (numEntries - 1)};
|
||||
float[] rgb = colorSpace.toRGB(gray);
|
||||
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
gray[i] = (byte) ((i * 255) / (numEntries - 1));
|
||||
}
|
||||
|
||||
colorModel = new IndexColorModel(bits, numEntries, gray, gray, gray);
|
||||
}
|
||||
else {
|
||||
byte[] r = new byte[numEntries];
|
||||
byte[] g = new byte[numEntries];
|
||||
byte[] b = new byte[numEntries];
|
||||
|
||||
// Scale array values according to color profile..
|
||||
for (int i = 0; i < numEntries; i++) {
|
||||
float[] gray = new float[] { i / (float) (numEntries - 1) };
|
||||
float[] rgb = colorSpace.toRGB(gray);
|
||||
|
||||
r[i] = (byte) Math.round(rgb[0] * 255);
|
||||
g[i] = (byte) Math.round(rgb[1] * 255);
|
||||
b[i] = (byte) Math.round(rgb[2] * 255);
|
||||
}
|
||||
|
||||
colorModel = new IndexColorModel(bits, numEntries, r, g, b);
|
||||
r[i] = (byte) (rgb[0] * 255);
|
||||
g[i] = (byte) (rgb[1] * 255);
|
||||
b[i] = (byte) (rgb[2] * 255);
|
||||
}
|
||||
|
||||
ColorModel colorModel = new IndexColorModel(bits, numEntries, r, g, b);
|
||||
SampleModel sampleModel = new MultiPixelPackedSampleModel(dataType, 1, 1, bits);
|
||||
|
||||
return new ImageTypeSpecifier(colorModel, sampleModel);
|
||||
@@ -258,7 +201,7 @@ public final class ImageTypeSpecifiers {
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) {
|
||||
return new IndexedImageTypeSpecifier(pColorModel);
|
||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(pColorModel);
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createDiscreteAlphaIndexedFromIndexColorModel(final IndexColorModel pColorModel) {
|
||||
@@ -270,20 +213,4 @@ public final class ImageTypeSpecifiers {
|
||||
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
|
||||
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
|
||||
public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
|
||||
if (image == null) {
|
||||
throw new IllegalArgumentException("image == null!");
|
||||
}
|
||||
|
||||
if (image instanceof BufferedImage) {
|
||||
int bufferedImageType = ((BufferedImage) image).getType();
|
||||
|
||||
if (bufferedImageType != BufferedImage.TYPE_CUSTOM) {
|
||||
return createFromBufferedImageType(bufferedImageType);
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageTypeSpecifier(image);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-24
@@ -30,13 +30,13 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* IndexedImageTypeSpecifier
|
||||
@@ -45,24 +45,27 @@ import javax.imageio.ImageTypeSpecifier;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IndexedImageTypeSpecifier.java,v 1.0 May 19, 2008 11:04:28 AM haraldk Exp$
|
||||
*/
|
||||
final class IndexedImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
IndexedImageTypeSpecifier(final ColorModel colorModel) {
|
||||
// For some reason, we need a sample model, even though we won't use it
|
||||
super(notNull(colorModel, "colorModel"), colorModel.createCompatibleSampleModel(1, 1));
|
||||
}
|
||||
final class IndexedImageTypeSpecifier {
|
||||
private IndexedImageTypeSpecifier() {}
|
||||
|
||||
@Override
|
||||
public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) {
|
||||
try {
|
||||
// This is a fix for the super-method, that first creates a sample model, and then
|
||||
// creates a raster from it, using Raster.createWritableRaster. The problem with
|
||||
// that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images.
|
||||
WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
||||
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
|
||||
}
|
||||
catch (NegativeArraySizeException e) {
|
||||
// Exception most likely thrown from a DataBuffer constructor
|
||||
throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!");
|
||||
}
|
||||
static ImageTypeSpecifier createFromIndexColorModel(final IndexColorModel pColorModel) {
|
||||
// For some reason, we need a sample model
|
||||
return new ImageTypeSpecifier(notNull(pColorModel, "colorModel"), pColorModel.createCompatibleSampleModel(1, 1)) {
|
||||
|
||||
@Override
|
||||
public final BufferedImage createBufferedImage(final int pWidth, final int pHeight) {
|
||||
try {
|
||||
// This is a fix for the super-method, that first creates a sample model, and then
|
||||
// creates a raster from it, using Raster.createWritableRaster. The problem with
|
||||
// that approach, is that it always creates a TYPE_CUSTOM BufferedImage for indexed images.
|
||||
WritableRaster raster = colorModel.createCompatibleWritableRaster(pWidth, pHeight);
|
||||
return new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), new Hashtable());
|
||||
}
|
||||
catch (NegativeArraySizeException e) {
|
||||
// Exception most likely thrown from a DataBuffer constructor
|
||||
throw new IllegalArgumentException("Array size > Integer.MAX_VALUE!");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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.util;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* A class containing various raster utility methods.
|
||||
*/
|
||||
public final class RasterUtils {
|
||||
|
||||
private RasterUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a raster with {@code DataBuffer.TYPE_BYTE} transfer type.
|
||||
* Works for any raster from a {@code BufferedImage.TYPE_INT_*} image
|
||||
*
|
||||
* @param raster a {@code Raster} with either transfer type {@code DataBuffer.TYPE_BYTE}
|
||||
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`, not {@code null}.
|
||||
* @return a raster with {@code DataBuffer.TYPE_BYTE} transfer type.
|
||||
* @throws IllegalArgumentException if {@code raster} does not have transfer type {@code DataBuffer.TYPE_BYTE}
|
||||
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`
|
||||
* @throws NullPointerException if {@code raster} is {@code null}.
|
||||
*/
|
||||
public static Raster asByteRaster(final Raster raster) {
|
||||
return asByteRaster0(raster);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a writable raster with {@code DataBuffer.TYPE_BYTE} transfer type.
|
||||
* Works for any raster from a {@code BufferedImage.TYPE_INT_*} image.
|
||||
*
|
||||
* @param raster a {@code WritableRaster} with either transfer type {@code DataBuffer.TYPE_BYTE}
|
||||
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`, not {@code null}.
|
||||
* @return a writable raster with {@code DataBuffer.TYPE_BYTE} transfer type.
|
||||
* @throws IllegalArgumentException if {@code raster} does not have transfer type {@code DataBuffer.TYPE_BYTE}
|
||||
* or {@code DataBuffer.TYPE_INT} with `SinglePixelPackedSampleModel`
|
||||
* @throws NullPointerException if {@code raster} is {@code null}.
|
||||
*/
|
||||
public static WritableRaster asByteRaster(final WritableRaster raster) {
|
||||
return (WritableRaster) asByteRaster0(raster);
|
||||
}
|
||||
|
||||
private static Raster asByteRaster0(final Raster raster) {
|
||||
switch (raster.getTransferType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
return raster;
|
||||
case DataBuffer.TYPE_INT:
|
||||
SampleModel sampleModel = raster.getSampleModel();
|
||||
|
||||
if (!(sampleModel instanceof SinglePixelPackedSampleModel)) {
|
||||
throw new IllegalArgumentException(String.format("Requires SinglePixelPackedSampleModel, %s not supported", sampleModel.getClass().getSimpleName()));
|
||||
}
|
||||
|
||||
final int bands = 4;
|
||||
final DataBufferInt buffer = (DataBufferInt) raster.getDataBuffer();
|
||||
|
||||
int w = raster.getWidth();
|
||||
int h = raster.getHeight();
|
||||
int size = buffer.getSize();
|
||||
|
||||
return new WritableRaster(
|
||||
new PixelInterleavedSampleModel(DataBuffer.TYPE_BYTE, w, h, bands, w * bands, createBandOffsets((SinglePixelPackedSampleModel) sampleModel)),
|
||||
new DataBuffer(DataBuffer.TYPE_BYTE, size * bands) {
|
||||
final int[] MASKS = {
|
||||
0xffffff00,
|
||||
0xffff00ff,
|
||||
0xff00ffff,
|
||||
0x00ffffff,
|
||||
};
|
||||
|
||||
@Override
|
||||
public int getElem(int bank, int i) {
|
||||
int index = i / bands;
|
||||
int shift = (i % bands) * 8;
|
||||
|
||||
return (buffer.getElem(index) >>> shift) & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setElem(int bank, int i, int val) {
|
||||
int index = i / bands;
|
||||
int element = i % bands;
|
||||
int shift = element * 8;
|
||||
|
||||
int value = (buffer.getElem(index) & MASKS[element]) | ((val & 0xff) << shift);
|
||||
buffer.setElem(index, value);
|
||||
}
|
||||
}, new Point()) {
|
||||
};
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException(String.format("Raster type %d not supported", raster.getTransferType()));
|
||||
}
|
||||
}
|
||||
|
||||
private static int[] createBandOffsets(final SinglePixelPackedSampleModel sampleModel) {
|
||||
notNull(sampleModel, "sampleModel");
|
||||
|
||||
int[] masks = sampleModel.getBitMasks();
|
||||
int[] offs = new int[masks.length];
|
||||
|
||||
for (int i = 0; i < masks.length; i++) {
|
||||
int mask = masks[i];
|
||||
int off = 0;
|
||||
|
||||
// TODO: FixMe! This only works for standard 8 bit masks (0xFF)
|
||||
if (mask != 0) {
|
||||
while ((mask & 0xFF) == 0) {
|
||||
mask >>>= 8;
|
||||
off++;
|
||||
}
|
||||
}
|
||||
|
||||
offs[i] = off;
|
||||
}
|
||||
|
||||
return offs;
|
||||
}
|
||||
}
|
||||
+9
-18
@@ -30,16 +30,15 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.UInt32ColorModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.image.BandedSampleModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.PixelInterleavedSampleModel;
|
||||
import java.awt.image.SampleModel;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
|
||||
import com.twelvemonkeys.imageio.color.UInt32ColorModel;
|
||||
|
||||
/**
|
||||
* ImageTypeSpecifier for interleaved 32 bit unsigned integral samples.
|
||||
*
|
||||
@@ -48,13 +47,11 @@ import com.twelvemonkeys.imageio.color.UInt32ColorModel;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: UInt32ImageTypeSpecifier.java,v 1.0 24.01.11 17.51 haraldk Exp$
|
||||
*/
|
||||
final class UInt32ImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
private UInt32ImageTypeSpecifier(final ColorSpace cs, final boolean hasAlpha, final boolean isAlphaPremultiplied, final SampleModel sampleModel) {
|
||||
super(new UInt32ColorModel(cs, hasAlpha, isAlphaPremultiplied), sampleModel);
|
||||
}
|
||||
final class UInt32ImageTypeSpecifier {
|
||||
private UInt32ImageTypeSpecifier() {}
|
||||
|
||||
static ImageTypeSpecifier createInterleaved(final ColorSpace cs, final int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
|
||||
return new UInt32ImageTypeSpecifier(
|
||||
return create(
|
||||
cs, hasAlpha, isAlphaPremultiplied,
|
||||
new PixelInterleavedSampleModel(
|
||||
DataBuffer.TYPE_INT, 1, 1,
|
||||
@@ -66,7 +63,7 @@ final class UInt32ImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
}
|
||||
|
||||
static ImageTypeSpecifier createBanded(final ColorSpace cs, final int[] bandIndices, final int[] bandOffsets, final boolean hasAlpha, final boolean isAlphaPremultiplied) {
|
||||
return new UInt32ImageTypeSpecifier(
|
||||
return create(
|
||||
cs, hasAlpha, isAlphaPremultiplied,
|
||||
new BandedSampleModel(
|
||||
DataBuffer.TYPE_INT, 1, 1, 1,
|
||||
@@ -75,13 +72,7 @@ final class UInt32ImageTypeSpecifier extends ImageTypeSpecifier {
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object other) {
|
||||
if (!(other instanceof UInt32ImageTypeSpecifier)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt32ImageTypeSpecifier that = (UInt32ImageTypeSpecifier) other;
|
||||
return colorModel.equals(that.colorModel) && sampleModel.equals(that.sampleModel);
|
||||
private static ImageTypeSpecifier create(final ColorSpace cs, final boolean hasAlpha, final boolean isAlphaPremultiplied, final SampleModel sampleModel) {
|
||||
return new ImageTypeSpecifier(new UInt32ColorModel(cs, hasAlpha, isAlphaPremultiplied), sampleModel);
|
||||
}
|
||||
}
|
||||
|
||||
-5
@@ -1,5 +0,0 @@
|
||||
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
|
||||
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
|
||||
com.twelvemonkeys.imageio.stream.BufferedInputStreamImageInputStreamSpi
|
||||
# Use SPI loading as a hook for early profile activation
|
||||
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
|
||||
-332
@@ -1,332 +0,0 @@
|
||||
package com.twelvemonkeys.imageio;
|
||||
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ImageOrientation;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.PlanarConfiguration;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.SubimageInterpretation;
|
||||
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.TextEntry;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.builder;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class StandardImageMetadataSupportTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void createNullBuilder() {
|
||||
new StandardImageMetadataSupport(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void createNullType() {
|
||||
new StandardImageMetadataSupport(builder(null));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void builderNullType() {
|
||||
builder(null).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createValid() {
|
||||
IIOMetadata metadata = new StandardImageMetadataSupport(builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
|
||||
assertNotNull(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderValid() {
|
||||
IIOMetadata metadata = builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB))
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesUnspecified() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.build();
|
||||
|
||||
assertNull(metadata.getStandardCompressionNode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesNone() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionTypeName("nOnE") // Case-insensitive
|
||||
.build();
|
||||
|
||||
assertNull(metadata.getStandardCompressionNode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesName() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionTypeName("foo")
|
||||
.build();
|
||||
|
||||
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compressionNode);
|
||||
|
||||
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
|
||||
assertEquals("foo", compressionName.getAttribute("value"));
|
||||
|
||||
// Defaults to lossless true
|
||||
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
|
||||
assertEquals("TRUE", compressionLossless.getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void withCompressionLossyIllegal() {
|
||||
builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionLossless(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compressionValuesLossy() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withCompressionTypeName("bar")
|
||||
.withCompressionLossless(false)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
|
||||
assertNotNull(compressionNode);
|
||||
|
||||
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
|
||||
assertEquals("bar", compressionName.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
|
||||
assertEquals("FALSE", compressionLossless.getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withDocumentValuesDefault() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.build();
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
|
||||
assertNull(documentNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withDocumentValues() {
|
||||
Calendar creationTime = Calendar.getInstance();
|
||||
creationTime.set(2022, Calendar.SEPTEMBER, 8, 14, 5, 0);
|
||||
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withFormatVersion("42")
|
||||
.withDocumentCreationTime(creationTime)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode formatVersion = (IIOMetadataNode) documentNode.getElementsByTagName("FormatVersion").item(0);
|
||||
assertEquals("42", formatVersion.getAttribute("value"));
|
||||
|
||||
IIOMetadataNode imageCreationTime = (IIOMetadataNode) documentNode.getElementsByTagName("ImageCreationTime").item(0);
|
||||
assertEquals("2022", imageCreationTime.getAttribute("year"));
|
||||
assertEquals("9", imageCreationTime.getAttribute("month"));
|
||||
assertEquals("8", imageCreationTime.getAttribute("day"));
|
||||
assertEquals("14", imageCreationTime.getAttribute("hour"));
|
||||
assertEquals("5", imageCreationTime.getAttribute("minute"));
|
||||
assertEquals("0", imageCreationTime.getAttribute("second"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesDefault() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNull(textNode);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesSingle() {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withTextEntry("foo", "bar")
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNotNull(textNode);
|
||||
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textNode.getElementsByTagName("TextEntry").item(0);
|
||||
assertEquals("foo", textEntry.getAttribute("keyword"));
|
||||
assertEquals("bar", textEntry.getAttribute("value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesMap() {
|
||||
Map<String, String> entries = new HashMap<>();
|
||||
entries.put("foo", "bar");
|
||||
entries.put("bar", "xyzzy");
|
||||
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withTextEntries(entries)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNotNull(textNode);
|
||||
|
||||
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
|
||||
assertEquals(entries.size(), textEntries.getLength());
|
||||
|
||||
int i = 0;
|
||||
for (Entry<String, String> entry : entries.entrySet()) {
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
|
||||
assertEquals(entry.getKey(), textEntry.getAttribute("keyword"));
|
||||
assertEquals(entry.getValue(), textEntry.getAttribute("value"));
|
||||
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withTextValuesList() {
|
||||
List<TextEntry> entries = Arrays.asList(
|
||||
new TextEntry(null, "foo"), // No key allowed
|
||||
new TextEntry("foo", "bar"),
|
||||
new TextEntry("bar", "xyzzy"),
|
||||
new TextEntry("bar", "nothing happens..."), // Duplicates allowed
|
||||
new TextEntry("everything", "válüè", "unknown", "UTF-8", "zip")
|
||||
);
|
||||
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withTextEntries(entries)
|
||||
.build();
|
||||
|
||||
IIOMetadataNode textNode = metadata.getStandardTextNode();
|
||||
assertNotNull(textNode);
|
||||
|
||||
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
|
||||
assertEquals(entries.size(), textEntries.getLength());
|
||||
|
||||
for (int i = 0; i < entries.size(); i++) {
|
||||
TextEntry entry = entries.get(i);
|
||||
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
|
||||
|
||||
assertAttributeEqualOrAbsent(entry.keyword, textEntry, "keyword");
|
||||
|
||||
assertEquals(entry.value, textEntry.getAttribute("value"));
|
||||
|
||||
assertAttributeEqualOrAbsent(entry.language, textEntry, "language");
|
||||
assertAttributeEqualOrAbsent(entry.encoding, textEntry, "encoding");
|
||||
assertAttributeEqualOrAbsent(entry.compression, textEntry, "compression");
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertAttributeEqualOrAbsent(final String expectedValue, IIOMetadataNode node, final String attribute) {
|
||||
if (expectedValue != null) {
|
||||
assertEquals(expectedValue, node.getAttribute(attribute));
|
||||
}
|
||||
else {
|
||||
assertFalse(node.hasAttribute(attribute));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPlanarColorspaceType() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList(
|
||||
"XYZ", "Lab", "Luv", "YCbCr", "Yxy", "YCCK", "PhotoYCC",
|
||||
"RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY",
|
||||
"2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR",
|
||||
"9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR"
|
||||
);
|
||||
|
||||
for (ColorSpaceType value : ColorSpaceType.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withColorSpaceType(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardChromaNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ColorSpaceType").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("name")); // Format oddity: Why is this not "value"?
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withPlanarConfiguration() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList("PixelInterleaved", "PlaneInterleaved", "LineInterleaved", "TileInterleaved");
|
||||
|
||||
for (PlanarConfiguration value : PlanarConfiguration.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR))
|
||||
.withPlanarConfiguration(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDataNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("PlanarConfiguration").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withImageOrientation() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList("Normal", "Rotate90", "Rotate180", "Rotate270", "FlipH", "FlipV", "FlipHRotate90", "FlipVRotate90");
|
||||
|
||||
for (ImageOrientation value : ImageOrientation.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
|
||||
.withOrientation(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDimensionNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ImageOrientation").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withSubimageInterpretation() {
|
||||
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
|
||||
Collection<String> allowedValues = Arrays.asList(
|
||||
"Standalone", "SinglePage", "FullResolution", "ReducedResolution", "PyramidLayer",
|
||||
"Preview", "VolumeSlice", "ObjectView", "Panorama", "AnimationFrame",
|
||||
"TransparencyMask", "CompositingLayer", "SpectralSlice", "Unknown"
|
||||
);
|
||||
|
||||
for (SubimageInterpretation value : SubimageInterpretation.values()) {
|
||||
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB))
|
||||
.withSubimageInterpretation(value)
|
||||
.build();
|
||||
|
||||
assertNotNull(metadata);
|
||||
|
||||
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
|
||||
assertNotNull(documentNode);
|
||||
|
||||
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("SubimageInterpretation").item(0);
|
||||
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
|
||||
assertTrue(allowedValues.contains(value.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
-247
@@ -1,247 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class ColorProfilesTest {
|
||||
@Test
|
||||
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile = createBrokenProfile(internal);
|
||||
assertNotSame(internal, profile); // Sanity check
|
||||
|
||||
assertTrue(ColorProfiles.isOffendingColorProfile(profile));
|
||||
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
||||
byte[] data = internal.getData();
|
||||
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
|
||||
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
|
||||
return ICC_Profile.getInstance(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsOffendingColorProfile() {
|
||||
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
assertTrue(ColorProfiles.isOffendingColorProfile(broken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_sRGBTrue() {
|
||||
assertTrue(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_sRGBFalse() {
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||
assertFalse(ColorProfiles.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_sRGBNull() {
|
||||
ColorProfiles.isCS_sRGB(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_GRAYTrue() {
|
||||
assertTrue(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsCS_GRAYFalse() {
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||
assertFalse(ColorProfiles.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_GRAYNull() {
|
||||
ColorProfiles.isCS_GRAY(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileNull() {
|
||||
ColorProfiles.createProfile(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileNull() throws IOException {
|
||||
ColorProfiles.readProfile(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawNull() {
|
||||
ColorProfiles.createProfileRaw(null);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawNull() throws IOException {
|
||||
ColorProfiles.readProfileRaw(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileRaw() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ICC_Profile profileRaw = ColorProfiles.createProfileRaw(data);
|
||||
assertArrayEquals(data, profileRaw.getData());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileRaw() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ICC_Profile profileRaw = ColorProfiles.readProfileRaw(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
assertArrayEquals(data, profileRaw.getData());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawBadData() {
|
||||
ColorProfiles.createProfileRaw(new byte[5]);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawBadData() throws IOException {
|
||||
// NOTE: The array here is larger, as there's a bug in OpenJDK 15 & 16, that throws
|
||||
// ArrayIndexOutOfBoundsException if the stream is shorter than the profile signature...
|
||||
ColorProfiles.readProfileRaw(new ByteArrayInputStream(new byte[40]));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileBadData() {
|
||||
ColorProfiles.createProfile(new byte[5]);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileBadData() throws IOException {
|
||||
ColorProfiles.readProfile(new ByteArrayInputStream(new byte[5]));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfile(Arrays.copyOf(data, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileTruncated() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 200));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileRawTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfileRaw(Arrays.copyOf(data, 125));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileRawTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfileRaw(new ByteArrayInputStream(data, 0, 125));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testCreateProfileTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.createProfile(Arrays.copyOf(data, 125));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testReadProfileTruncatedHeader() throws IOException {
|
||||
byte[] data = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")).getData();
|
||||
ColorProfiles.readProfile(new ByteArrayInputStream(data, 0, 125));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileBytesSame() throws IOException {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile1 = ColorProfiles.createProfile(profile.getData());
|
||||
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSame() throws IOException {
|
||||
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileDifferent() throws IOException {
|
||||
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different profiles)...
|
||||
ICC_Profile profile1 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
||||
|
||||
assertNotSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileBytesSameAsCached() throws IOException {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(profile);
|
||||
ICC_Profile profile2 = ColorProfiles.createProfile(profile.getData());
|
||||
|
||||
assertEquals(cs1.getProfile(), profile2);
|
||||
assertSame(cs1.getProfile(), profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSameAsCached() throws IOException {
|
||||
ICC_ColorSpace cs1 = ColorSpaces.createColorSpace(ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc")));
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
|
||||
assertEquals(cs1.getProfile(), profile2);
|
||||
assertSame(cs1.getProfile(), profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateProfileBytesSameAsInternal() {
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile2 = ColorProfiles.createProfile(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData());
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadProfileInputStreamSameAsInternal() throws IOException {
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile2 = ColorProfiles.readProfile(new ByteArrayInputStream(ICC_Profile.getInstance(ColorSpace.CS_sRGB).getData()));
|
||||
|
||||
assertEquals(profile1, profile2);
|
||||
assertSame(profile1, profile2);
|
||||
}
|
||||
}
|
||||
+29
-25
@@ -90,6 +90,34 @@ public class ColorSpacesTest {
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromBrokenProfileIsFixedCS_sRGB() {
|
||||
ICC_Profile internal = ICC_Profile.getInstance(ColorSpace.CS_sRGB);
|
||||
ICC_Profile profile = createBrokenProfile(internal);
|
||||
assertNotSame(internal, profile); // Sanity check
|
||||
|
||||
assertTrue(ColorSpaces.isOffendingColorProfile(profile));
|
||||
|
||||
ICC_ColorSpace created = ColorSpaces.createColorSpace(profile);
|
||||
assertSame(ColorSpace.getInstance(ColorSpace.CS_sRGB), created);
|
||||
assertTrue(created.isCS_sRGB());
|
||||
}
|
||||
|
||||
private ICC_Profile createBrokenProfile(ICC_Profile internal) {
|
||||
byte[] data = internal.getData();
|
||||
data[ICC_Profile.icHdrRenderingIntent] = 1; // Intent: 1 == Relative Colormetric Little Endian
|
||||
data[ICC_Profile.icHdrRenderingIntent + 1] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 2] = 0;
|
||||
data[ICC_Profile.icHdrRenderingIntent + 3] = 0;
|
||||
return ICC_Profile.getInstance(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsOffendingColorProfile() {
|
||||
ICC_Profile broken = createBrokenProfile(ICC_Profile.getInstance(ColorSpace.CS_GRAY));
|
||||
assertTrue(ColorSpaces.isOffendingColorProfile(broken));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateColorSpaceFromKnownProfileReturnsInternalCS_GRAY() {
|
||||
ICC_Profile profile = ICC_Profile.getInstance(ColorSpace.CS_GRAY);
|
||||
@@ -138,13 +166,11 @@ public class ColorSpacesTest {
|
||||
assertEquals(ColorSpace.TYPE_CMYK, ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK).getType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_sRGBTrue() {
|
||||
assertTrue(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_sRGBFalse() {
|
||||
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
@@ -153,36 +179,14 @@ public class ColorSpacesTest {
|
||||
assertFalse(ColorSpaces.isCS_sRGB(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_sRGBNull() {
|
||||
ColorSpaces.isCS_sRGB(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_GRAYTrue() {
|
||||
assertTrue(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_GRAY)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test
|
||||
public void testIsCS_GRAYFalse() {
|
||||
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_sRGB)));
|
||||
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB)));
|
||||
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ)));
|
||||
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testIsCS_GRAYNull() {
|
||||
ColorSpaces.isCS_GRAY(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEqualHeadersDifferentProfile() throws IOException {
|
||||
// These profiles are extracted from various JPEGs, and have the exact same profile header (but are different)...
|
||||
// These profiles are extracted from various JPEGs, and have the exact same profile header...
|
||||
ICC_Profile profile1 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/adobe_rgb_1998.icc"));
|
||||
ICC_Profile profile2 = ICC_Profile.getInstance(getClass().getResourceAsStream("/profiles/color_match_rgb.icc"));
|
||||
|
||||
|
||||
+1
-22
@@ -37,9 +37,7 @@ import java.awt.image.*;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class DiscreteAlphaIndexColorModelTest {
|
||||
|
||||
@@ -206,25 +204,6 @@ public class DiscreteAlphaIndexColorModelTest {
|
||||
assertThat(raster.getTransferType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumComponents() {
|
||||
int[] colors = createIntLut(1 << 8);
|
||||
IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
|
||||
ColorModel colorModelDiscreteAlpha = new DiscreteAlphaIndexColorModel(icm, 1, true);
|
||||
ColorModel colorModelDiscreteAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 2, true);
|
||||
ColorModel colorModelNoAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 42, false);
|
||||
|
||||
assertEquals(3, colorModelDiscreteAlpha.getNumColorComponents());
|
||||
assertEquals(4, colorModelDiscreteAlpha.getNumComponents());
|
||||
|
||||
assertEquals(3, colorModelDiscreteAlphaExtra.getNumColorComponents());
|
||||
assertEquals(5, colorModelDiscreteAlphaExtra.getNumComponents()); // Questionable
|
||||
|
||||
assertEquals(3, colorModelNoAlphaExtra.getNumColorComponents());
|
||||
assertEquals(45, colorModelNoAlphaExtra.getNumComponents()); // Questionable
|
||||
}
|
||||
|
||||
private static int[] createIntLut(final int count) {
|
||||
int[] lut = new int[count];
|
||||
|
||||
|
||||
+2
-15
@@ -36,11 +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.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class KCMSSanitizerStrategyTest {
|
||||
@@ -58,8 +58,6 @@ 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);
|
||||
@@ -73,17 +71,6 @@ 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,7 +34,6 @@ 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;
|
||||
|
||||
@@ -47,8 +46,6 @@ 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);
|
||||
|
||||
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.color;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import javax.imageio.spi.ServiceRegistry;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class ProfileDeferralActivatorTest {
|
||||
|
||||
@Test
|
||||
public void testActivateProfiles() {
|
||||
// Should just run with no exceptions...
|
||||
ProfileDeferralActivator.activateProfiles();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSpiRegistration() {
|
||||
ProfileDeferralActivator.Spi spi = new ProfileDeferralActivator.Spi();
|
||||
ServiceRegistry registry = mock(ServiceRegistry.class);
|
||||
Class<ImageInputStreamSpi> category = ImageInputStreamSpi.class;
|
||||
|
||||
spi.onRegistration(registry, category);
|
||||
|
||||
verify(registry, only()).deregisterServiceProvider(spi, category);
|
||||
}
|
||||
}
|
||||
-434
@@ -1,434 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
// TODO: Remove this test, and instead test the disk cache directly!
|
||||
public class BufferedChannelImageInputStreamFileCacheTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
private InputStream randomDataToInputStream(byte[] data) {
|
||||
random.nextBytes(data);
|
||||
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() throws IOException {
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(new ByteArrayInputStream(new byte[0]), null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullStream() throws IOException {
|
||||
try {
|
||||
new FileCache((InputStream) null, null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullChannel() throws IOException {
|
||||
try {
|
||||
new FileCache((ReadableByteChannel) null, null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
for (byte value : data) {
|
||||
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[7];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||
stream.readFully(result);
|
||||
stream.skipBytes(result.length);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[9];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
// Read backwards
|
||||
long newPos = data.length - result.length - i * result.length;
|
||||
stream.seek(newPos);
|
||||
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||
byte[] data = new byte[256];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] buffer = new byte[data.length * 2];
|
||||
stream.read(buffer);
|
||||
stream.seek(0);
|
||||
assertNotEquals(-1, stream.read());
|
||||
assertNotEquals(-1, stream.read(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
stream.seek(0);
|
||||
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandomOffset() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
for (int i = 1; i <= 60; i++) {
|
||||
stream.seek(0);
|
||||
stream.setBitOffset(i % 8);
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekPastEOF() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
stream.seek(1000);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readByte();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() throws IOException {
|
||||
// Create wrapper stream
|
||||
Cache cache = mock(Cache.class);
|
||||
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
|
||||
|
||||
stream.close();
|
||||
verify(cache, only()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||
// See #606 for details.
|
||||
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||
// Ie: Relies on read to return all bytes at once, without blocking
|
||||
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||
byte[] bytes = new byte[size];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
|
||||
byte[] result = new byte[size];
|
||||
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||
|
||||
assertEquals(size, len + head);
|
||||
assertArrayEquals(bytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
-452
@@ -1,452 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.only;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
// TODO: Remove this test, and instead test the memory cache directly!
|
||||
public class BufferedChannelImageInputStreamMemoryCacheTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
private InputStream randomDataToInputStream(byte[] data) {
|
||||
random.nextBytes(data);
|
||||
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() throws IOException {
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(new ByteArrayInputStream(new byte[0])))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullStream() {
|
||||
try {
|
||||
new MemoryCache((InputStream) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullChannel() {
|
||||
try {
|
||||
new MemoryCache((ReadableByteChannel) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
for (byte value : data) {
|
||||
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
|
||||
assertEquals("Wrong data read", -1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[7];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||
stream.readFully(result);
|
||||
stream.skipBytes(result.length);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] result = new byte[9];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
// Read backwards
|
||||
long newPos = data.length - result.length - i * result.length;
|
||||
stream.seek(newPos);
|
||||
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||
byte[] data = new byte[256];
|
||||
InputStream input = randomDataToInputStream(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
assertEquals("Stream length should be unknown", -1, stream.length());
|
||||
|
||||
byte[] buffer = new byte[data.length * 2];
|
||||
stream.read(buffer);
|
||||
stream.seek(0);
|
||||
assertNotEquals(-1, stream.read());
|
||||
assertNotEquals(-1, stream.read(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
stream.seek(0);
|
||||
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandomOffset() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
for (int i = 1; i <= 60; i++) {
|
||||
stream.seek(0);
|
||||
stream.setBitOffset(i % 8);
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekPastEOF() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.seek(1000);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readByte();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void testSeekWayPastEOFShouldNotThrowOOME() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
stream.seek(Integer.MAX_VALUE * 4L * 512L); // ~4 TB
|
||||
|
||||
assertEquals(-1, stream.read()); // No OOME should happen...
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClose() throws IOException {
|
||||
// Create wrapper stream
|
||||
Cache cache = mock(Cache.class);
|
||||
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
|
||||
|
||||
stream.close();
|
||||
verify(cache, only()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||
// See #606 for details.
|
||||
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||
// Ie: Relies on read to return all bytes at once, without blocking
|
||||
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||
byte[] bytes = new byte[size];
|
||||
InputStream input = randomDataToInputStream(bytes);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
|
||||
byte[] result = new byte[size];
|
||||
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||
|
||||
assertEquals(size, len + head);
|
||||
assertArrayEquals(bytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
-431
@@ -1,431 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.channels.SeekableByteChannel;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Random;
|
||||
|
||||
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* BufferedFileImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
|
||||
*/
|
||||
public class BufferedChannelImageInputStreamTest {
|
||||
private final Random random = new Random(170984354357234566L);
|
||||
|
||||
private File randomDataToFile(byte[] data) throws IOException {
|
||||
random.nextBytes(data);
|
||||
|
||||
File file = File.createTempFile("read", ".tmp");
|
||||
Files.write(file.toPath(), data);
|
||||
return file;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreate() throws IOException {
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(File.createTempFile("empty", ".tmp")))) {
|
||||
assertEquals("Data length should be same as stream length", 0, stream.length());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullFileInputStream() {
|
||||
try {
|
||||
new BufferedChannelImageInputStream((FileInputStream) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("inputstream"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateNullByteChannel() {
|
||||
try {
|
||||
new BufferedChannelImageInputStream((SeekableByteChannel) null);
|
||||
fail("Expected IllegalArgumentException");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertNotNull("Null exception message", expected.getMessage());
|
||||
String message = expected.getMessage().toLowerCase();
|
||||
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
|
||||
assertTrue("Exception message does not contain null", message.contains("null"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRead() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
for (byte value : data) {
|
||||
assertEquals("Wrong data read", value & 0xff, stream.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadArray() throws IOException {
|
||||
byte[] data = new byte[1024 * 1024];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] result = new byte[1024];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSkip() throws IOException {
|
||||
byte[] data = new byte[1024 * 14];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] result = new byte[7];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i += 2) {
|
||||
stream.readFully(result);
|
||||
stream.skipBytes(result.length);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadSeek() throws IOException {
|
||||
byte[] data = new byte[1024 * 18];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] result = new byte[9];
|
||||
|
||||
for (int i = 0; i < data.length / result.length; i++) {
|
||||
// Read backwards
|
||||
long newPos = stream.length() - result.length - i * result.length;
|
||||
stream.seek(newPos);
|
||||
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
|
||||
stream.readFully(result);
|
||||
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadOutsideDataSeek0Read() throws IOException {
|
||||
byte[] data = new byte[256];
|
||||
File file = randomDataToFile(data);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
assertEquals("File length should be same as stream length", file.length(), stream.length());
|
||||
|
||||
byte[] buffer = new byte[data.length * 2];
|
||||
stream.read(buffer);
|
||||
stream.seek(0);
|
||||
assertNotEquals(-1, stream.read());
|
||||
assertNotEquals(-1, stream.read(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
File file = randomDataToFile(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandom() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
File file = randomDataToFile(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
for (int i = 1; i <= 64; i++) {
|
||||
stream.seek(0);
|
||||
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadBitsRandomOffset() throws IOException {
|
||||
byte[] bytes = new byte[8];
|
||||
File file = randomDataToFile(bytes);
|
||||
long value = ByteBuffer.wrap(bytes).getLong();
|
||||
|
||||
// Create stream
|
||||
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
for (int i = 1; i <= 60; i++) {
|
||||
stream.seek(0);
|
||||
stream.setBitOffset(i % 8);
|
||||
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
|
||||
assertEquals(i * 2 % 8, stream.getBitOffset());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadShort() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
File file = randomDataToFile(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 2; i++) {
|
||||
assertEquals(buffer.getShort(), stream.readShort());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadInt() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
File file = randomDataToFile(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 4; i++) {
|
||||
assertEquals(buffer.getInt(), stream.readInt());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLong() throws IOException {
|
||||
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
|
||||
File file = randomDataToFile(bytes);
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
buffer.position(0);
|
||||
buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
for (int i = 0; i < bytes.length / 8; i++) {
|
||||
assertEquals(buffer.getLong(), stream.readLong());
|
||||
}
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSeekPastEOF() throws IOException {
|
||||
byte[] bytes = new byte[9];
|
||||
File file = randomDataToFile(bytes);
|
||||
|
||||
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
stream.seek(1000);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
assertEquals(-1, stream.read(new byte[1], 0, 1));
|
||||
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readFully(new byte[1]);
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readByte();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readShort();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readInt();
|
||||
}
|
||||
});
|
||||
assertThrows(EOFException.class, new ThrowingRunnable() {
|
||||
@Override
|
||||
public void run() throws Throwable {
|
||||
stream.readLong();
|
||||
}
|
||||
});
|
||||
|
||||
stream.seek(0);
|
||||
for (byte value : bytes) {
|
||||
assertEquals(value, stream.readByte());
|
||||
}
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseChannel() throws IOException {
|
||||
SeekableByteChannel channel = mock(SeekableByteChannel.class);
|
||||
ImageInputStream stream = new BufferedChannelImageInputStream(channel);
|
||||
|
||||
stream.close();
|
||||
verify(channel, never()).close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
|
||||
// See #606 for details.
|
||||
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
|
||||
// Ie: Relies on read to return all bytes at once, without blocking
|
||||
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
|
||||
byte[] bytes = new byte[size];
|
||||
File file = randomDataToFile(bytes);
|
||||
|
||||
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
|
||||
byte[] result = new byte[size];
|
||||
int head = stream.read(result, 0, 12); // Provoke a buffered read
|
||||
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
|
||||
|
||||
assertEquals(size, len + head);
|
||||
assertArrayEquals(bytes, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.spi.ImageInputStreamSpi;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
|
||||
public class BufferedFileImageInputStreamSpiTest extends ImageInputStreamSpiTest<File> {
|
||||
@Override
|
||||
protected ImageInputStreamSpi createProvider() {
|
||||
return new BufferedFileImageInputStreamSpi();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected File createInput() throws IOException {
|
||||
return File.createTempFile("test-", ".tst");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReturnNullWhenFileDoesNotExist() throws IOException {
|
||||
// This is really stupid behavior, but it is consistent with the JRE bundled SPIs.
|
||||
File input = new File("a-file-that-should-not-exist-ever.fnf");
|
||||
assumeFalse("File should not exist: " + input.getPath(), input.exists());
|
||||
assertNull(provider.createInputStreamInstance(input));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user