Compare commits

..

1 Commits

Author SHA1 Message Date
snyk-bot b794d05035 fix: upgrade com.twelvemonkeys.common:common-image from 3.1-SNAPSHOT to 3.7.0
Snyk has created this PR to upgrade com.twelvemonkeys.common:common-image from 3.1-SNAPSHOT to 3.7.0.

See this package in Maven Repository:
https://mvnrepository.com/artifact/com.twelvemonkeys.common/common-image/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/b53d0a96-fb95-4707-a6b4-5e37b1b75566?utm_source=github&utm_medium=referral&page=upgrade-pr
2021-12-03 20:34:07 +00:00
393 changed files with 44018 additions and 8044 deletions
+3 -3
View File
@@ -7,14 +7,14 @@ 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.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem or use case is.
**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.
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
-90
View File
@@ -1,90 +0,0 @@
name: CI
on: [ push, pull_request ]
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 ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
java-package: jdk
cache: 'maven'
- name: Run Tests
run: mvn test
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
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
strategy:
matrix:
kcms: [ true, false ]
steps:
- uses: actions/checkout@v2
- 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@v2
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 test
- name: Publish Test Report
uses: mikepenz/action-junit-report@v2
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@v2
- name: Set up Maven Central
uses: actions/setup-java@v2
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 help:evaluate -Dexpression=project.version -q -DforceStdout)" >> $GITHUB_ENV
- name: Publish to Maven Central
if: ${{ endsWith(env.PROJECT_VERSION, '-SNAPSHOT') }}
run: mvn deploy -P release -DskipTests
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)
+16
View File
@@ -0,0 +1,16 @@
dist: trusty
language: java
jdk:
- oraclejdk8 # LTS until Mar 2022
- oraclejdk11 # LTS until Sep 2023
- oraclejdk15 # out of support
- oraclejdk16 # out of support
- oraclejdk17 # LTS until Sep 2026
jobs:
include:
# Extra job, testing legacy CMM option
- jdk: oraclejdk8
env: MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
cache:
directories:
- $HOME/.m2
+66 -77
View File
@@ -1,6 +1,5 @@
[![CI](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml/badge.svg)](https://github.com/haraldk/TwelveMonkeys/actions/workflows/ci.yml)
[![Maven Central](https://img.shields.io/maven-central/v/com.twelvemonkeys.imageio/imageio?color=slateblue)](https://search.maven.org/search?q=g:com.twelvemonkeys.imageio)
[![Maven Snapshot](https://img.shields.io/nexus/s/com.twelvemonkeys.imageio/imageio?label=development&server=https%3A%2F%2Foss.sonatype.org&color=slateblue)](https://oss.sonatype.org/content/repositories/snapshots/com/twelvemonkeys/)
[![Build Status](https://travis-ci.com/haraldk/TwelveMonkeys.svg?branch=master)](https://travis-ci.com/github/haraldk/TwelveMonkeys)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio/badge.svg?color=slateblue)](https://maven-badges.herokuapp.com/maven-central/com.twelvemonkeys.imageio/imageio)
[![StackOverflow](https://img.shields.io/badge/stack_overflow-twelvemonkeys-orange.svg)](https://stackoverflow.com/questions/tagged/twelvemonkeys)
[![Donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://paypal.me/haraldk76/100)
@@ -17,36 +16,36 @@ As there is lots of legacy data out there, we see the need for open implementati
## 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](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) |
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
@@ -272,12 +271,12 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.8.1</version>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.8.1</version>
<version>3.7.0</version>
</dependency>
<!--
@@ -287,17 +286,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.8.1</version>
</dependency>
<!--
Or Jakarta version, for Servlet API 5.0
-->
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.8.1</version>
<classifier>jakarta</classifier>
<version>3.7.0</version>
</dependency>
</dependencies>
```
@@ -306,13 +295,13 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
To depend on the JPEG and TIFF plugin in your IDE or program, add all of the following JARs to your class path:
twelvemonkeys-common-lang-3.8.1.jar
twelvemonkeys-common-io-3.8.1.jar
twelvemonkeys-common-image-3.8.1.jar
twelvemonkeys-imageio-core-3.8.1.jar
twelvemonkeys-imageio-metadata-3.8.1.jar
twelvemonkeys-imageio-jpeg-3.8.1.jar
twelvemonkeys-imageio-tiff-3.8.1.jar
twelvemonkeys-common-lang-3.7.0.jar
twelvemonkeys-common-io-3.7.0.jar
twelvemonkeys-common-image-3.7.0.jar
twelvemonkeys-imageio-core-3.7.0.jar
twelvemonkeys-imageio-metadata-3.7.0.jar
twelvemonkeys-imageio-jpeg-3.7.0.jar
twelvemonkeys-imageio-tiff-3.7.0.jar
#### Deploying the plugins in a web app
@@ -378,44 +367,44 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
### Links to prebuilt binaries
##### Latest version (3.8.1)
##### Latest version (3.7.0)
Requires Java 7 or later.
Common dependencies
* [common-lang-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.8.1/common-lang-3.8.1.jar)
* [common-io-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.8.1/common-io-3.8.1.jar)
* [common-image-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.8.1/common-image-3.8.1.jar)
* [common-lang-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.7.0/common-lang-3.7.0.jar)
* [common-io-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.7.0/common-io-3.7.0.jar)
* [common-image-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.7.0/common-image-3.7.0.jar)
ImageIO dependencies
* [imageio-core-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.8.1/imageio-core-3.8.1.jar)
* [imageio-metadata-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.8.1/imageio-metadata-3.8.1.jar)
* [imageio-core-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.7.0/imageio-core-3.7.0.jar)
* [imageio-metadata-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.7.0/imageio-metadata-3.7.0.jar)
ImageIO plugins
* [imageio-bmp-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.8.1/imageio-bmp-3.8.1.jar)
* [imageio-hdr-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.8.1/imageio-hdr-3.8.1.jar)
* [imageio-icns-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.8.1/imageio-icns-3.8.1.jar)
* [imageio-iff-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.8.1/imageio-iff-3.8.1.jar)
* [imageio-jpeg-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.8.1/imageio-jpeg-3.8.1.jar)
* [imageio-pcx-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.8.1/imageio-pcx-3.8.1.jar)
* [imageio-pict-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.8.1/imageio-pict-3.8.1.jar)
* [imageio-pnm-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.8.1/imageio-pnm-3.8.1.jar)
* [imageio-psd-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.8.1/imageio-psd-3.8.1.jar)
* [imageio-sgi-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.8.1/imageio-sgi-3.8.1.jar)
* [imageio-tga-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.8.1/imageio-tga-3.8.1.jar)
* [imageio-thumbsdb-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.8.1/imageio-thumbsdb-3.8.1.jar)
* [imageio-tiff-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.8.1/imageio-tiff-3.8.1.jar)
* [imageio-webp-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.8.1/imageio-webp-3.8.1.jar)
* [imageio-xwd-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.8.1/imageio-xwd-3.8.1.jar)
* [imageio-bmp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.7.0/imageio-bmp-3.7.0.jar)
* [imageio-hdr-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.7.0/imageio-hdr-3.7.0.jar)
* [imageio-icns-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.7.0/imageio-icns-3.7.0.jar)
* [imageio-iff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.7.0/imageio-iff-3.7.0.jar)
* [imageio-jpeg-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.7.0/imageio-jpeg-3.7.0.jar)
* [imageio-pcx-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.7.0/imageio-pcx-3.7.0.jar)
* [imageio-pict-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.7.0/imageio-pict-3.7.0.jar)
* [imageio-pnm-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.7.0/imageio-pnm-3.7.0.jar)
* [imageio-psd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.7.0/imageio-psd-3.7.0.jar)
* [imageio-sgi-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.7.0/imageio-sgi-3.7.0.jar)
* [imageio-tga-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.7.0/imageio-tga-3.7.0.jar)
* [imageio-thumbsdb-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.7.0/imageio-thumbsdb-3.7.0.jar)
* [imageio-tiff-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.7.0/imageio-tiff-3.7.0.jar)
* [imageio-webp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.7.0/imageio-webp-3.7.0.jar)
* [imageio-xwd-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.7.0/imageio-xwd-3.7.0.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.8.1/imageio-batik-3.8.1.jar)
* [imageio-batik-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.7.0/imageio-batik-3.7.0.jar)
Photoshop Path support for ImageIO
* [imageio-clippath-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.8.1/imageio-clippath-3.8.1.jar)
* [imageio-clippath-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.7.0/imageio-clippath-3.7.0.jar)
Servlet support
* [servlet-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.8.1/servlet-3.8.1.jar)
* [servlet-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.7.0/servlet-3.7.0.jar)
##### Old version (3.0.x)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
@@ -31,7 +31,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>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
@@ -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;
}
/**
@@ -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;
}
}
}
@@ -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());
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
@@ -30,14 +30,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 +43,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 +56,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 +69,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 {
+3 -10
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -27,9 +27,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 +48,6 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.xmlgraphics</groupId>
<artifactId>batik-rasterizer-ext</artifactId>
@@ -30,10 +30,21 @@
package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGOMDocument;
@@ -57,19 +68,10 @@ import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGSVGElement;
import javax.imageio.IIOException;
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.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
/**
* Image reader for SVG document fragments.
@@ -77,13 +79,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;
@@ -149,23 +150,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 +187,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 +211,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 +220,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 +235,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,13 +246,22 @@ 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) {
@@ -259,11 +275,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;
@@ -324,66 +341,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 +401,7 @@ public class SVGImageReader extends ImageReaderBase {
ctx = null;
}
private BufferedImage readImage() throws IOException {
private BufferedImage readImage() throws TranscoderException {
init();
if (abortRequested()) {
@@ -421,8 +426,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 +444,7 @@ public class SVGImageReader extends ImageReaderBase {
// ----
setImageSize(defaultSize.width, defaultSize.height);
setImageSize(defaultWidth, defaultHeight);
if (abortRequested()) {
processReadAborted();
@@ -454,17 +458,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 +519,7 @@ public class SVGImageReader extends ImageReaderBase {
}
}
catch (BridgeException ex) {
throw new IIOException(ex.getMessage(), ex);
throw new TranscoderException(ex);
}
this.root = gvtRoot;
@@ -583,7 +588,7 @@ public class SVGImageReader extends ImageReaderBase {
return dest;
}
catch (Exception ex) {
throw new IIOException(ex.getMessage(), ex);
throw new TranscoderException(ex.getMessage(), ex);
}
finally {
if (context != null) {
@@ -592,7 +597,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 +605,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 +617,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) {
@@ -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);
}
}
@@ -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,7 @@ 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.Mockito.*;
/**
@@ -194,12 +192,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
@@ -339,69 +337,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

+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -39,11 +39,7 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.*;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
@@ -51,7 +47,7 @@ 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.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInput;
import java.io.File;
@@ -81,7 +77,7 @@ public final class BMPImageReader extends ImageReaderBase {
super(new BMPImageReaderSpi());
}
BMPImageReader(final ImageReaderSpi pProvider) {
protected BMPImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
@@ -362,18 +358,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 +379,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);
@@ -484,7 +476,6 @@ 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;
@@ -501,7 +492,9 @@ 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);
@@ -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);
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -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);
}
}
}
@@ -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,89 @@ 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#CS_sRGB
* @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 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
*/
@Deprecated
public static boolean isCS_GRAY(final ICC_Profile profile) {
return ColorProfiles.isCS_GRAY(profile);
Validate.notNull(profile, "profile");
return profile.getColorSpaceType() == ColorSpace.TYPE_GRAY && Arrays.equals(getProfileHeaderWithProfileId(profile), GRAY.header);
}
/**
* @deprecated Use {@link ColorProfiles#validateProfile(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}
*/
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.
*/
@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 +398,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 +464,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);
}
}
}
@@ -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()));
@@ -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,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
stream.close();
super.close();
}
}
@@ -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;
}
/**
@@ -1,4 +1,2 @@
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
# Use SPI loading as a hook for early profile activation
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
@@ -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);
}
}
@@ -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,19 +179,16 @@ 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)));
@@ -174,7 +197,6 @@ public class ColorSpacesTest {
assertFalse(ColorSpaces.isCS_GRAY(ICC_Profile.getInstance(ColorSpace.CS_PYCC)));
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void testIsCS_GRAYNull() {
ColorSpaces.isCS_GRAY(null);
@@ -182,7 +204,7 @@ public class ColorSpacesTest {
@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"));
@@ -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];
@@ -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);
}
}
@@ -53,7 +53,6 @@ import static org.mockito.Mockito.*;
* @author last modified by $Author: haraldk$
* @version $Id: BufferedImageInputStreamTest.java,v 1.0 Jun 30, 2008 3:07:42 PM haraldk Exp$
*/
@SuppressWarnings("deprecation")
public class BufferedImageInputStreamTest {
private final Random random = new Random(3450972865211L);
@@ -434,7 +433,7 @@ public class BufferedImageInputStreamTest {
* and {@code pFirstOffset == pSecondOffset}.
* Otherwise {@code false}.
*/
public static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
if (pFirst == pSecond && pFirstOffset == pSecondOffset) {
return true;
}
@@ -1,380 +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 org.junit.Ignore;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* NonSeekableImageInputStreamTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: NonSeekableImageInputStreamTest.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
public class DirectImageInputStreamTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomData(byte[] data) {
random.nextBytes(data);
return new ByteArrayInputStream(data);
}
@Test
public void testCreate() throws IOException {
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(new byte[0]), 0)) {
assertEquals("Data length should be same as stream length", 0, stream.length());
}
}
@Test
public void testCreateNullFile() throws IOException {
try (@SuppressWarnings("unused") DirectImageInputStream stream = new DirectImageInputStream(null)) {
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 10];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[24 * 18];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
byte[] result = new byte[9];
for (int i = 0; i < data.length / (2 * result.length); i++) {
long newPos = i * 2 * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@SuppressWarnings("ConstantConditions")
@Ignore("Bit reading requires backwards seek or buffer...")
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomData(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@SuppressWarnings("ConstantConditions")
@Ignore("Bit reading requires backwards seek or buffer...")
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomData(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@SuppressWarnings("ConstantConditions")
@Ignore("Bit reading requires backwards seek or buffer...")
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomData(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2L % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[31];
InputStream input = randomData(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[31];
InputStream input = randomData(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomData(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomData(bytes);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testClose() throws IOException {
// Create wrapper stream
InputStream input = mock(InputStream.class);
ImageInputStream stream = new DirectImageInputStream(input);
stream.close();
verify(input, only()).close();
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
@@ -69,14 +69,14 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett
protected WeakReference<IndexColorModel> originalPalette;
protected MutableIndexColorModel mutablePalette;
public AbstractMultiPaletteChunk(int chunkId, int chunkLength) {
super(chunkId, chunkLength);
public AbstractMultiPaletteChunk(int pChunkId, int pChunkLength) {
super(pChunkId, pChunkLength);
}
@Override
void readChunk(final DataInput input) throws IOException {
void readChunk(final DataInput pInput) throws IOException {
if (chunkId == IFF.CHUNK_SHAM) {
input.readUnsignedShort(); // Version, typically 0, skipped
pInput.readUnsignedShort(); // Version, typically 0, skipped
}
int rows = chunkLength / 32; /* sizeof(word) * 16 */
@@ -91,7 +91,7 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett
}
for (int i = 0; i < 16; i++ ) {
int data = input.readUnsignedShort();
int data = pInput.readUnsignedShort();
changes[row][i].index = i;
changes[row][i].r = (byte) (((data & 0x0f00) >> 8) * FACTOR_4BIT);
@@ -102,7 +102,7 @@ abstract class AbstractMultiPaletteChunk extends IFFChunk implements MultiPalett
}
@Override
void writeChunk(DataOutput output) {
void writeChunk(DataOutput pOutput) {
throw new UnsupportedOperationException("Method writeChunk not implemented");
}
@@ -30,11 +30,12 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import javax.imageio.IIOException;
/**
* BMHDChunk
*
@@ -109,65 +110,64 @@ final class BMHDChunk extends IFFChunk {
int pageWidth;
int pageHeight;
BMHDChunk(int chunkLength) {
super(IFF.CHUNK_BMHD, chunkLength);
protected BMHDChunk(int pChunkLength) {
super(IFF.CHUNK_BMHD, pChunkLength);
}
BMHDChunk(int width, int height, int bitplanes, int maskType, int compressionType, int transparentIndex) {
protected BMHDChunk(int pWidth, int pHeight, int pBitplanes, int pMaskType, int pCompressionType, int pTransparentIndex) {
super(IFF.CHUNK_BMHD, 20);
this.width = width;
this.height = height;
width = pWidth;
height = pHeight;
xPos = 0;
yPos = 0;
this.bitplanes = bitplanes;
this.maskType = maskType;
this.compressionType = compressionType;
this.transparentIndex = transparentIndex;
bitplanes = pBitplanes;
maskType = pMaskType;
compressionType = pCompressionType;
transparentIndex = pTransparentIndex;
xAspect = 1;
yAspect = 1;
pageWidth = Math.min(width, Short.MAX_VALUE); // For some reason, these are signed?
pageHeight = Math.min(height, Short.MAX_VALUE);
pageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed?
pageHeight = Math.min(pHeight, Short.MAX_VALUE);
}
@Override
void readChunk(final DataInput input) throws IOException {
void readChunk(final DataInput pInput) throws IOException {
if (chunkLength != 20) {
throw new IIOException("Unknown BMHD chunk length: " + chunkLength);
}
width = input.readUnsignedShort();
height = input.readUnsignedShort();
xPos = input.readShort();
yPos = input.readShort();
bitplanes = input.readUnsignedByte();
maskType = input.readUnsignedByte();
compressionType = input.readUnsignedByte();
input.readByte(); // PAD
transparentIndex = input.readUnsignedShort();
xAspect = input.readUnsignedByte();
yAspect = input.readUnsignedByte();
pageWidth = input.readShort();
pageHeight = input.readShort();
width = pInput.readUnsignedShort();
height = pInput.readUnsignedShort();
xPos = pInput.readShort();
yPos = pInput.readShort();
bitplanes = pInput.readUnsignedByte();
maskType = pInput.readUnsignedByte();
compressionType = pInput.readUnsignedByte();
pInput.readByte(); // PAD
transparentIndex = pInput.readUnsignedShort();
xAspect = pInput.readUnsignedByte();
yAspect = pInput.readUnsignedByte();
pageWidth = pInput.readShort();
pageHeight = pInput.readShort();
}
@Override
void writeChunk(final DataOutput output) throws IOException {
output.writeInt(chunkId);
output.writeInt(chunkLength);
void writeChunk(final DataOutput pOutput) throws IOException {
pOutput.writeInt(chunkId);
pOutput.writeInt(chunkLength);
output.writeShort(width);
output.writeShort(height);
output.writeShort(xPos);
output.writeShort(yPos);
output.writeByte(bitplanes);
output.writeByte(maskType);
output.writeByte(compressionType);
output.writeByte(0); // PAD
output.writeShort(transparentIndex);
output.writeByte(xAspect);
output.writeByte(yAspect);
output.writeShort(pageWidth);
output.writeShort(pageHeight);
pOutput.writeShort(width);
pOutput.writeShort(height);
pOutput.writeShort(xPos);
pOutput.writeShort(yPos);
pOutput.writeByte(bitplanes);
pOutput.writeByte(maskType);
pOutput.writeByte(compressionType);
pOutput.writeByte(0); // PAD
pOutput.writeShort(transparentIndex);
pOutput.writeByte(xAspect);
pOutput.writeByte(yAspect);
pOutput.writeShort(pageWidth);
pOutput.writeShort(pageHeight);
}
@Override
@@ -33,8 +33,6 @@ package com.twelvemonkeys.imageio.plugins.iff;
import java.io.DataInput;
import java.io.DataOutput;
import static com.twelvemonkeys.lang.Validate.isTrue;
/**
* BODYChunk
*
@@ -42,20 +40,17 @@ import static com.twelvemonkeys.lang.Validate.isTrue;
* @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$
*/
final class BODYChunk extends IFFChunk {
final long chunkOffset;
BODYChunk(int chunkId, int chunkLength, long chunkOffset) {
super(isTrue(chunkId == IFF.CHUNK_BODY || chunkId == IFF.CHUNK_DBOD, chunkId, "Illegal body chunk: '%s'"), chunkLength);
this.chunkOffset = chunkOffset;
protected BODYChunk(int pChunkLength) {
super(IFF.CHUNK_BODY, pChunkLength);
}
@Override
void readChunk(final DataInput input) {
void readChunk(final DataInput pInput) {
throw new InternalError("BODY chunk should only be read from IFFImageReader");
}
@Override
void writeChunk(final DataOutput output) {
void writeChunk(final DataOutput pOutput) {
throw new InternalError("BODY chunk should only be written from IFFImageWriter");
}
}
@@ -30,11 +30,12 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import javax.imageio.IIOException;
/**
* CAMGChunk
*
@@ -48,21 +49,21 @@ final class CAMGChunk extends IFFChunk {
int camg;
CAMGChunk(int chunkLength) {
super(IFF.CHUNK_CAMG, chunkLength);
public CAMGChunk(int pLength) {
super(IFF.CHUNK_CAMG, pLength);
}
@Override
void readChunk(final DataInput input) throws IOException {
void readChunk(final DataInput pInput) throws IOException {
if (chunkLength != 4) {
throw new IIOException("Unknown CAMG chunk length: " + chunkLength);
}
camg = input.readInt();
camg = pInput.readInt();
}
@Override
void writeChunk(final DataOutput output) {
void writeChunk(final DataOutput pOutput) {
throw new InternalError("Not implemented: writeChunk()");
}
@@ -30,13 +30,16 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.WritableRaster;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
import javax.imageio.IIOException;
/**
* CMAPChunk
*
@@ -57,7 +60,7 @@ final class CMAPChunk extends IFFChunk {
private IndexColorModel model;
CMAPChunk(final int pChunkLength) {
protected CMAPChunk(final int pChunkLength) {
super(IFF.CHUNK_CMAP, pChunkLength);
}
@@ -67,7 +70,7 @@ final class CMAPChunk extends IFFChunk {
}
@Override
void readChunk(final DataInput input) throws IOException {
void readChunk(final DataInput pInput) throws IOException {
int numColors = chunkLength / 3;
reds = new byte[numColors];
@@ -75,9 +78,9 @@ final class CMAPChunk extends IFFChunk {
blues = reds.clone();
for (int i = 0; i < numColors; i++) {
reds[i] = input.readByte();
greens[i] = input.readByte();
blues[i] = input.readByte();
reds[i] = pInput.readByte();
greens[i] = pInput.readByte();
blues[i] = pInput.readByte();
}
// TODO: When reading in a CMAP for 8-bit-per-gun display or
@@ -90,25 +93,25 @@ final class CMAPChunk extends IFFChunk {
// All chunks are WORD aligned (even sized), may need to read pad...
if (chunkLength % 2 != 0) {
input.readByte();
pInput.readByte();
}
}
@Override
void writeChunk(final DataOutput output) throws IOException {
output.writeInt(chunkId);
output.writeInt(chunkLength);
void writeChunk(final DataOutput pOutput) throws IOException {
pOutput.writeInt(chunkId);
pOutput.writeInt(chunkLength);
final int length = model.getMapSize();
for (int i = 0; i < length; i++) {
output.writeByte(model.getRed(i));
output.writeByte(model.getGreen(i));
output.writeByte(model.getBlue(i));
pOutput.writeByte(model.getRed(i));
pOutput.writeByte(model.getGreen(i));
pOutput.writeByte(model.getBlue(i));
}
if (chunkLength % 2 != 0) {
output.writeByte(0); // PAD
pOutput.writeByte(0); // PAD
}
}
@@ -117,11 +120,25 @@ final class CMAPChunk extends IFFChunk {
return super.toString() + " {colorMap=" + model + "}";
}
public IndexColorModel getIndexColorModel(final Form.ILBMForm header) throws IIOException {
BufferedImage createPaletteImage(final BMHDChunk header, boolean isEHB) throws IIOException {
// Create a 1 x colors.length image
IndexColorModel cm = getIndexColorModel(header, isEHB);
WritableRaster raster = cm.createCompatibleWritableRaster(cm.getMapSize(), 1);
byte[] pixel = null;
for (int x = 0; x < cm.getMapSize(); x++) {
pixel = (byte[]) cm.getDataElements(cm.getRGB(x), pixel);
raster.setDataElements(x, 0, pixel);
}
return new BufferedImage(cm, raster, cm.isAlphaPremultiplied(), null);
}
public IndexColorModel getIndexColorModel(final BMHDChunk header, boolean isEHB) throws IIOException {
if (model == null) {
int numColors = reds.length; // All arrays are same size
if (header.isEHB()) {
if (isEHB) {
if (numColors == 32) {
reds = Arrays.copyOf(reds, numColors * 2);
blues = Arrays.copyOf(blues, numColors * 2);
@@ -144,10 +161,8 @@ final class CMAPChunk extends IFFChunk {
// Would it work to double to numbers of colors, and create an indexcolormodel,
// with alpha, where all colors above the original color is all transparent?
// This is a waste of time and space, of course...
int transparent = header.transparentIndex();
int bitplanes = header.bitplanes() == 25 ? 8 : header.bitplanes();
model = new IndexColorModel(bitplanes, reds.length, reds, greens, blues, transparent); // https://github.com/haraldk/TwelveMonkeys/issues/15
int transparent = header.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? header.transparentIndex : -1;
model = new IndexColorModel(header.bitplanes, reds.length, reds, greens, blues, transparent); // https://github.com/haraldk/TwelveMonkeys/issues/15
}
return model;
@@ -38,7 +38,7 @@ package com.twelvemonkeys.imageio.plugins.iff;
* @version $Id: CTBLChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$
*/
final class CTBLChunk extends AbstractMultiPaletteChunk {
CTBLChunk(int chunkLength) {
super(IFF.CHUNK_CTBL, chunkLength);
protected CTBLChunk(int pChunkLength) {
super(IFF.CHUNK_CTBL, pChunkLength);
}
}
@@ -1,103 +0,0 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.Arrays;
/**
* DPELChunk.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: DPELChunk.java,v 1.0 01/02/2022 haraldk Exp$
*/
final class DPELChunk extends IFFChunk {
/*
//
// Chunk DPEL
// ----------
struct DPEL = {
//
// Number of pixel components
//
ULONG nElements;
//
// The TypeDepth structure is repeated nElement times to identify
// the content of every pixel. Pixels will always be padded to
// byte boundaries. The DBOD chunk will be padded to an even
// longword boundary.
//
struct TypeDepth = {
//
// Type of data
//
UWORD cType;
//
// Bit depth of this type
//
UWORD cBitDepth;
} typedepth[Nelements];
};
*/
TypeDepth[] typeDepths;
DPELChunk(final int chunkLength) {
super(IFF.CHUNK_DPEL, chunkLength);
}
@Override
void readChunk(final DataInput input) throws IOException {
int components = input.readInt(); // Strictly, it's unsigned, but that many components is unlikely...
if (chunkLength != 4 + components * 4) {
throw new IIOException("Unsupported DPEL chunk length: " + chunkLength);
}
typeDepths = new TypeDepth[components];
for (int i = 0; i < components; i++) {
typeDepths[i] = new TypeDepth(input.readUnsignedShort(), input.readUnsignedShort());
}
}
@Override
void writeChunk(final DataOutput output) {
throw new InternalError("Not implemented: writeChunk()");
}
@Override
public String toString() {
return super.toString()
+ "{typeDepths=" + Arrays.toString(typeDepths) + '}';
}
public int bitsPerPixel() {
int bitCount = 0;
for (TypeDepth typeDepth : typeDepths) {
bitCount += typeDepth.bitDepth;
}
return bitCount;
}
static class TypeDepth {
final int type;
final int bitDepth;
TypeDepth(final int type, final int bitDepth) {
this.type = type;
this.bitDepth = bitDepth;
}
@Override
public String toString() {
return "TypeDepth{" +
"type=" + type +
", bits=" + bitDepth +
'}';
}
}
}
@@ -1,421 +0,0 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
/**
* Form.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: Form.java,v 1.0 31/01/2022 haraldk Exp$
*/
abstract class Form {
final int formType;
final List<GenericChunk> meta = new ArrayList<>();
Form(int formType) {
this.formType = formType;
}
abstract int width();
abstract int height();
abstract float aspect();
abstract int bitplanes();
abstract int compressionType();
boolean isMultiPalette() {
return false;
}
boolean isHAM() {
return false;
}
public boolean premultiplied() {
return false;
}
public int sampleSize() {
return 1;
}
public int transparentIndex() {
return -1;
}
public IndexColorModel colorMap() throws IIOException {
return null;
}
public ColorModel colorMapForRow(IndexColorModel colorModel, int row) {
throw new UnsupportedOperationException();
}
public abstract boolean hasThumbnail();
public abstract int thumbnailWidth();
public abstract int thumbnailHeight();
public abstract BufferedImage thumbnail();
abstract long bodyOffset();
abstract long bodyLength();
@Override
public String toString() {
return toChunkStr(formType);
}
Form with(final IFFChunk chunk) throws IIOException {
if (chunk instanceof GenericChunk) {
// TODO: This feels kind of hackish, as it breaks the immutable design, perhaps we should just reconsider...
meta.add((GenericChunk) chunk);
return this;
}
throw new IllegalArgumentException(chunk + " not supported in FORM type " + toChunkStr(formType));
}
static Form ofType(int formType) {
switch (formType) {
case IFF.TYPE_ACBM:
case IFF.TYPE_ILBM:
case IFF.TYPE_PBM:
case IFF.TYPE_RGB8:
return new ILBMForm(formType);
case IFF.TYPE_DEEP:
case IFF.TYPE_TVPP:
return new DEEPForm(formType);
default:
throw new IllegalArgumentException("FORM type " + toChunkStr(formType) + " not supported");
}
}
/**
* The set of chunks used in the "original" ILBM,
* and also ACBM, PBM and RGB8 FORMs.
*/
static final class ILBMForm extends Form {
private final BMHDChunk bitmapHeader;
private final CAMGChunk viewMode;
private final CMAPChunk colorMap;
private final AbstractMultiPaletteChunk multiPalette;
private final XS24Chunk thumbnail; // TVPaint puts these into normal IFF ILBM 24 bit files as well as DEEP/TVPP
private final BODYChunk body;
ILBMForm(int formType) {
this(formType, null, null, null, null, null, null);
}
private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) {
super(formType);
this.bitmapHeader = bitmapHeader;
this.viewMode = viewMode;
this.colorMap = colorMap;
this.multiPalette = multiPalette;
this.thumbnail = thumbnail;
this.body = body;
}
@Override
int width() {
return bitmapHeader.width;
}
@Override
int height() {
return bitmapHeader.height;
}
@Override
int bitplanes() {
return bitmapHeader.bitplanes;
}
@Override
int compressionType() {
return bitmapHeader.compressionType;
}
@Override
float aspect() {
return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (float) bitmapHeader.yAspect);
}
@Override
boolean isMultiPalette() {
return multiPalette != null;
}
boolean isEHB() {
return viewMode != null && viewMode.isEHB();
}
@Override
boolean isHAM() {
return viewMode != null && viewMode.isHAM();
}
boolean isLaced() {
return viewMode != null && viewMode.isLaced();
}
@Override
public int transparentIndex() {
return bitmapHeader.maskType == BMHDChunk.MASK_TRANSPARENT_COLOR ? bitmapHeader.transparentIndex : -1;
}
@Override
public IndexColorModel colorMap() throws IIOException {
return colorMap != null ? colorMap.getIndexColorModel(this) : null;
}
@Override
public ColorModel colorMapForRow(final IndexColorModel colorModel, final int row) {
return multiPalette != null ? multiPalette.getColorModel(colorModel, row, isLaced()) : null;
}
@Override
public boolean hasThumbnail() {
return thumbnail != null;
}
@Override
public int thumbnailWidth() {
return thumbnail != null ? thumbnail.width : -1;
}
@Override
public int thumbnailHeight() {
return thumbnail != null ? thumbnail.height : -1;
}
@Override
public BufferedImage thumbnail() {
return thumbnail != null ? thumbnail.thumbnail() : null;
}
@Override
long bodyOffset() {
return body.chunkOffset;
}
@Override
long bodyLength() {
return body.chunkLength;
}
@Override
ILBMForm with(final IFFChunk chunk) throws IIOException {
if (chunk instanceof BMHDChunk) {
if (bitmapHeader != null) {
throw new IIOException("Multiple BMHD chunks not allowed");
}
return new ILBMForm(formType, (BMHDChunk) chunk, null, colorMap, multiPalette, thumbnail, body);
}
else if (chunk instanceof CAMGChunk) {
if (viewMode != null) {
throw new IIOException("Multiple CAMG chunks not allowed");
}
return new ILBMForm(formType, bitmapHeader, (CAMGChunk) chunk, colorMap, multiPalette, thumbnail, body);
}
else if (chunk instanceof CMAPChunk) {
if (colorMap != null) {
throw new IIOException("Multiple CMAP chunks not allowed");
}
return new ILBMForm(formType, bitmapHeader, viewMode, (CMAPChunk) chunk, multiPalette, thumbnail, body);
}
else if (chunk instanceof AbstractMultiPaletteChunk) {
// NOTE: We prefer PHCG over SHAM/CTBL style palette changes, if both are present
if (multiPalette instanceof PCHGChunk) {
if (chunk instanceof PCHGChunk) {
throw new IIOException("Multiple PCHG/SHAM/CTBL chunks not allowed");
}
return this;
}
return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, (AbstractMultiPaletteChunk) chunk, thumbnail, body);
}
else if (chunk instanceof XS24Chunk) {
if (thumbnail != null) {
throw new IIOException("Multiple XS24 chunks not allowed");
}
return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, multiPalette, (XS24Chunk) chunk, body);
}
else if (chunk instanceof BODYChunk) {
if (body != null) {
throw new IIOException("Multiple " + toChunkStr(chunk.chunkId) + " chunks not allowed");
}
return new ILBMForm(formType, bitmapHeader, viewMode, colorMap, multiPalette, thumbnail, (BODYChunk) chunk);
}
else if (chunk instanceof GRABChunk) {
// Ignored for now
return this;
}
return (ILBMForm) super.with(chunk);
}
@Override
public String toString() {
return super.toString() + '{' + bitmapHeader +
(viewMode != null ? ", " + viewMode : "" ) +
(colorMap != null ? ", " + colorMap : "" ) +
(multiPalette != null ? ", " + multiPalette : "" ) +
'}';
}
}
/**
* The set of chunks used in DEEP and TVPP FORMs.
*/
private static final class DEEPForm extends Form {
private final DGBLChunk deepGlobal;
private final DLOCChunk deepLocation;
private final DPELChunk deepPixel;
private final XS24Chunk thumbnail;
private final BODYChunk body;
DEEPForm(int formType) {
this(formType, null, null, null, null, null);
}
private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) {
super(formType);
this.deepGlobal = deepGlobal;
this.deepLocation = deepLocation;
this.deepPixel = deepPixel;
this.thumbnail = thumbnail;
this.body = body;
}
@Override
int width() {
return deepLocation.width;
}
@Override
int height() {
return deepLocation.height;
}
@Override
int bitplanes() {
return deepPixel.bitsPerPixel();
}
@Override
public int sampleSize() {
return bitplanes() / 8;
}
@Override
public boolean premultiplied() {
return true;
}
@Override
int compressionType() {
return deepGlobal.compressionType;
}
@Override
float aspect() {
return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (float) deepGlobal.yAspect;
}
@Override
public boolean hasThumbnail() {
return thumbnail != null;
}
@Override
public int thumbnailWidth() {
return thumbnail != null ? thumbnail.width : -1;
}
@Override
public int thumbnailHeight() {
return thumbnail != null ? thumbnail.height : -1;
}
@Override
public BufferedImage thumbnail() {
return thumbnail != null ? thumbnail.thumbnail() : null;
}
@Override
long bodyOffset() {
return body.chunkOffset;
}
@Override
long bodyLength() {
return body.chunkLength;
}
@Override
DEEPForm with(final IFFChunk chunk) throws IIOException {
if (chunk instanceof DGBLChunk) {
if (deepGlobal != null) {
throw new IIOException("Multiple DGBL chunks not allowed");
}
return new DEEPForm(formType, (DGBLChunk) chunk, null, null, thumbnail, body);
}
else if (chunk instanceof DLOCChunk) {
if (deepLocation != null) {
throw new IIOException("Multiple DLOC chunks not allowed");
}
return new DEEPForm(formType, deepGlobal, (DLOCChunk) chunk, deepPixel, thumbnail, body);
}
else if (chunk instanceof DPELChunk) {
if (deepPixel != null) {
throw new IIOException("Multiple DPEL chunks not allowed");
}
return new DEEPForm(formType, deepGlobal, deepLocation, (DPELChunk) chunk, thumbnail, body);
}
else if (chunk instanceof XS24Chunk) {
if (thumbnail != null) {
throw new IIOException("Multiple XS24 chunks not allowed");
}
return new DEEPForm(formType, deepGlobal, deepLocation, deepPixel, (XS24Chunk) chunk, body);
}
else if (chunk instanceof BODYChunk) {
// TODO: Make a better approach!
// if (body != null) {
// throw new IIOException("Multiple " + toChunkStr(chunk.chunkId) + " chunks not allowed");
// }
return new DEEPForm(formType, deepGlobal, deepLocation, deepPixel, thumbnail, (BODYChunk) chunk);
}
return (DEEPForm) super.with(chunk);
}
@Override
public String toString() {
return super.toString() + '{' + deepGlobal + ", " + deepLocation + ", " + deepPixel + '}';
}
}
}
@@ -50,25 +50,25 @@ final class GRABChunk extends IFFChunk {
Point2D point;
GRABChunk(int chunkLength) {
super(IFF.CHUNK_GRAB, chunkLength);
protected GRABChunk(int pChunkLength) {
super(IFF.CHUNK_GRAB, pChunkLength);
}
GRABChunk(Point2D point) {
protected GRABChunk(Point2D pPoint) {
super(IFF.CHUNK_GRAB, 4);
this.point = point;
point = pPoint;
}
void readChunk(DataInput input) throws IOException {
void readChunk(DataInput pInput) throws IOException {
if (chunkLength != 4) {
throw new IIOException("Unknown GRAB chunk size: " + chunkLength);
}
point = new Point(input.readShort(), input.readShort());
point = new Point(pInput.readShort(), pInput.readShort());
}
void writeChunk(DataOutput output) throws IOException {
output.writeShort((int) point.getX());
output.writeShort((int) point.getY());
void writeChunk(DataOutput pOutput) throws IOException {
pOutput.writeShort((int) point.getX());
pOutput.writeShort((int) point.getY());
}
public String toString() {
@@ -44,31 +44,31 @@ final class GenericChunk extends IFFChunk {
byte[] data;
GenericChunk(int chunkId, int chunkLength) {
super(chunkId, chunkLength);
data = new byte[this.chunkLength];
protected GenericChunk(int pChunkId, int pChunkLength) {
super(pChunkId, pChunkLength);
data = new byte[chunkLength];
}
GenericChunk(int chunkId, byte[] chunkData) {
super(chunkId, chunkData.length);
data = chunkData;
protected GenericChunk(int pChunkId, byte[] pChunkData) {
super(pChunkId, pChunkData.length);
data = pChunkData;
}
@Override
void readChunk(final DataInput input) throws IOException {
input.readFully(data, 0, data.length);
void readChunk(final DataInput pInput) throws IOException {
pInput.readFully(data, 0, data.length);
skipData(input, chunkLength, data.length);
skipData(pInput, chunkLength, data.length);
}
@Override
void writeChunk(final DataOutput output) throws IOException {
output.writeInt(chunkId);
output.writeInt(chunkLength);
output.write(data, 0, data.length);
void writeChunk(final DataOutput pOutput) throws IOException {
pOutput.writeInt(chunkId);
pOutput.writeInt(chunkLength);
pOutput.write(data, 0, data.length);
if (data.length % 2 != 0) {
output.writeByte(0); // PAD
pOutput.writeByte(0); // PAD
}
}
@@ -49,8 +49,6 @@ interface IFF {
// TODO:
/** IFF DEEP form type (TVPaint) */
int TYPE_DEEP = ('D' << 24) + ('E' << 16) + ('E' << 8) + 'P';
/** IFF TVPP form type (TVPaint Project) */
int TYPE_TVPP = ('T' << 24) + ('V' << 16) + ('P' << 8) + 'P';
/** IFF RGB8 form type (TurboSilver) */
int TYPE_RGB8 = ('R' << 24) + ('G' << 16) + ('B' << 8) + '8';
/** IFF RGBN form type (TurboSilver) */
@@ -94,7 +92,7 @@ interface IFF {
int CHUNK_COPY = ('(' << 24) + ('c' << 16) + (')' << 8) + ' ';
/** EA IFF 85 Generic annotation chunk (usually used for Software) */
int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O';
int CHUNK_ANNO = ('A' << 24) + ('N' << 16) + ('N' << 8) + 'O';;
/** Third-party defined UTF-8 text. */
int CHUNK_UTF8 = ('U' << 24) + ('T' << 16) + ('F' << 8) + '8';
@@ -131,18 +129,6 @@ interface IFF {
int CHUNK_SHAM = ('S' << 24) + ('H' << 16) + ('A' << 8) + 'M';
/** ACBM body chunk */
int CHUNK_ABIT = ('A' << 24) + ('B' << 16) + ('I' << 8) + 'T';
/** Unofficial direct color */
/** unofficial direct color */
int CHUNK_DCOL = ('D' << 24) + ('C' << 16) + ('O' << 8) + 'L';
/** TVPaint Deep GloBaL information */
int CHUNK_DGBL = ('D' << 24) + ('G' << 16) + ('B' << 8) + 'L';
/** TVPaint Deep Pixel ELements */
int CHUNK_DPEL = ('D' << 24) + ('P' << 16) + ('E' << 8) + 'L';
/** TVPaint Deep LOCation information */
int CHUNK_DLOC = ('D' << 24) + ('L' << 16) + ('O' << 8) + 'C';
/** TVPaint Deep BODy */
int CHUNK_DBOD = ('D' << 24) + ('B' << 16) + ('O' << 8) + 'D';
/** TVPaint Deep CHanGe buffer */
int CHUNK_DCHG = ('D' << 24) + ('C' << 16) + ('H' << 8) + 'G';
/** TVPaint 24 bit thumbnail */
int CHUNK_XS24 = ('X' << 24) + ('S' << 16) + ('2' << 8) + '4';
}
@@ -44,25 +44,25 @@ abstract class IFFChunk {
int chunkId;
int chunkLength;
protected IFFChunk(int chunkId, int chunkLength) {
this.chunkId = chunkId;
this.chunkLength = chunkLength;
protected IFFChunk(int pChunkId, int pChunkLength) {
chunkId = pChunkId;
chunkLength = pChunkLength;
}
abstract void readChunk(DataInput input) throws IOException;
abstract void readChunk(DataInput pInput) throws IOException;
abstract void writeChunk(DataOutput output) throws IOException;
abstract void writeChunk(DataOutput pOutput) throws IOException;
protected static void skipData(final DataInput input, final int chunkLength, final int dataReadSoFar) throws IOException {
protected static void skipData(final DataInput pInput, final int chunkLength, final int dataReadSoFar) throws IOException {
int toSkip = chunkLength - dataReadSoFar;
while (toSkip > 0) {
toSkip -= input.skipBytes(toSkip);
toSkip -= pInput.skipBytes(toSkip);
}
// Read pad
if (chunkLength % 2 != 0) {
input.readByte();
pInput.readByte();
}
}
@@ -13,29 +13,31 @@ import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
final class IFFImageMetadata extends AbstractMetadata {
private final Form header;
private final int formType;
private final BMHDChunk header;
private final IndexColorModel colorMap;
private final CAMGChunk viewPort;
private final List<GenericChunk> meta;
IFFImageMetadata(Form header, IndexColorModel colorMap) {
IFFImageMetadata(int formType, BMHDChunk header, IndexColorModel colorMap, CAMGChunk viewPort, List<GenericChunk> meta) {
this.formType = isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s");
this.header = notNull(header, "header");
isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s");
this.colorMap = colorMap;
this.meta = header.meta;
this.viewPort = viewPort;
this.meta = meta;
}
private boolean validFormType(int formType) {
switch (formType) {
default:
return false;
case TYPE_ACBM:
case TYPE_DEEP:
case TYPE_ILBM:
case TYPE_PBM:
case TYPE_RGB8:
case TYPE_RGBN:
case TYPE_TVPP:
return true;
default:
return false;
}
}
@@ -46,7 +48,7 @@ final class IFFImageMetadata extends AbstractMetadata {
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
switch (header.bitplanes()) {
switch (header.bitplanes) {
case 8:
if (colorMap == null) {
csType.setAttribute("name", "GRAY");
@@ -60,7 +62,6 @@ final class IFFImageMetadata extends AbstractMetadata {
case 6:
case 7:
case 24:
case 25:
case 32:
csType.setAttribute("name", "RGB");
break;
@@ -71,10 +72,10 @@ final class IFFImageMetadata extends AbstractMetadata {
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
chroma.appendChild(numChannels);
if (colorMap == null && header.bitplanes() == 8) {
if (colorMap == null && header.bitplanes == 8) {
numChannels.setAttribute("value", Integer.toString(1));
}
else if (header.bitplanes() == 25 || header.bitplanes() == 32) {
else if (header.bitplanes == 32) {
numChannels.setAttribute("value", Integer.toString(4));
}
else {
@@ -101,16 +102,9 @@ final class IFFImageMetadata extends AbstractMetadata {
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
}
if (colorMap.getTransparentPixel() != -1) {
IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex");
chroma.appendChild(backgroundIndex);
backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
}
}
// TODO: TVPP TVPaint Project files have a MIXR chunk with a background color
// and also a BGP1 (background pen 1?) and BGP2 chunks
// TODO: Background color is the color of the transparent index in the color model?
// if (extensions != null && extensions.getBackgroundColor() != 0) {
// Color background = new Color(extensions.getBackgroundColor(), true);
//
@@ -127,7 +121,7 @@ final class IFFImageMetadata extends AbstractMetadata {
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) {
if (header.compressionType == BMHDChunk.COMPRESSION_NONE) {
return null; // All defaults
}
@@ -150,10 +144,7 @@ final class IFFImageMetadata extends AbstractMetadata {
// PlanarConfiguration
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
switch (header.formType) {
case TYPE_DEEP:
case TYPE_TVPP:
case TYPE_RGB8:
switch (formType) {
case TYPE_PBM:
planarConfiguration.setAttribute("value", "PixelInterleaved");
break;
@@ -161,7 +152,7 @@ final class IFFImageMetadata extends AbstractMetadata {
planarConfiguration.setAttribute("value", "PlaneInterleaved");
break;
default:
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType));
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(formType));
break;
}
data.appendChild(planarConfiguration);
@@ -172,7 +163,7 @@ final class IFFImageMetadata extends AbstractMetadata {
// BitsPerSample
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
String value = bitsPerSampleValue(header.bitplanes());
String value = bitsPerSampleValue(header.bitplanes);
bitsPerSample.setAttribute("value", value);
data.appendChild(bitsPerSample);
@@ -180,6 +171,7 @@ final class IFFImageMetadata extends AbstractMetadata {
// SampleMSB not in format
return data;
}
private String bitsPerSampleValue(int bitplanes) {
@@ -195,22 +187,16 @@ final class IFFImageMetadata extends AbstractMetadata {
return Integer.toString(bitplanes);
case 24:
return "8 8 8";
case 25:
if (header.formType != TYPE_RGB8) {
throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType)));
}
return "8 8 8 1";
case 32:
return "8 8 8 8";
default:
throw new IllegalArgumentException("Unknown bit count: " + bitplanes);
throw new IllegalArgumentException("Ubknown bit count: " + bitplanes);
}
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
if (header.aspect() == 0) {
if (viewPort == null) {
return null;
}
@@ -218,7 +204,7 @@ final class IFFImageMetadata extends AbstractMetadata {
// PixelAspectRatio
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", String.valueOf(header.aspect()));
pixelAspectRatio.setAttribute("value", String.valueOf((viewPort.isHires() ? 2f : 1f) / (viewPort.isLaced() ? 2f : 1f)));
dimension.appendChild(pixelAspectRatio);
// TODO: HorizontalScreenSize?
@@ -255,19 +241,20 @@ final class IFFImageMetadata extends AbstractMetadata {
}
return text;
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) {
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32) {
return null;
}
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
if (header.bitplanes() == 25 || header.bitplanes() == 32) {
if (header.bitplanes == 32) {
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied");
alpha.setAttribute("value", "nonpremultiplied");
transparency.appendChild(alpha);
}
@@ -30,12 +30,13 @@
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import java.io.IOException;
import java.util.Locale;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.Locale;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
/**
* IFFImageReaderSpi
@@ -53,41 +54,41 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase {
}
@Override
public boolean canDecodeInput(final Object source) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
public boolean canDecodeInput(Object pSource) throws IOException {
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
}
private static boolean canDecode(final ImageInputStream input) throws IOException {
input.mark();
private static boolean canDecode(ImageInputStream pInput) throws IOException {
pInput.mark();
try {
// Is it IFF
if (input.readInt() == IFF.CHUNK_FORM) {
input.readInt();// Skip length field
if (pInput.readInt() == IFF.CHUNK_FORM) {
pInput.readInt();// Skip length field
int type = input.readInt();
if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM
|| type == IFF.TYPE_RGB8 // Impulse RGB8 format
|| type == IFF.TYPE_DEEP || type == IFF.TYPE_TVPP) { // TVPaint DEEP format
int type = pInput.readInt();
// Is it ILBM or PBM
if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM) {
return true;
}
}
}
finally {
input.reset();
pInput.reset();
}
return false;
}
@Override
public ImageReader createReaderInstance(final Object extension) {
public ImageReader createReaderInstance(Object pExtension) throws IOException {
return new IFFImageReader(this);
}
@Override
public String getDescription(Locale locale) {
public String getDescription(Locale pLocale) {
return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image reader";
}
}
@@ -30,22 +30,31 @@
package com.twelvemonkeys.imageio.plugins.iff;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.image.*;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
/**
* Writer for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format.
* The IFF format (Interchange File Format) is the standard file format
@@ -59,8 +68,8 @@ import java.io.OutputStream;
*/
public final class IFFImageWriter extends ImageWriterBase {
IFFImageWriter(ImageWriterSpi provider) {
super(provider);
IFFImageWriter(ImageWriterSpi pProvider) {
super(pProvider);
}
@Override
@@ -74,29 +83,23 @@ public final class IFFImageWriter extends ImageWriterBase {
}
@Override
public ImageWriteParam getDefaultWriteParam() {
return new IFFWriteParam(getLocale());
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException {
assertOutput();
if (image.hasRaster()) {
if (pImage.hasRaster()) {
throw new UnsupportedOperationException("Cannot write raster");
}
processImageStarted(0);
RenderedImage renderedImage = image.getRenderedImage();
boolean compress = shouldCompress(renderedImage, param);
// Prepare image data to be written
ByteArrayOutputStream imageData = new FastByteArrayOutputStream(1024);
packImageData(imageData, renderedImage, compress);
packImageData(imageData, pImage.getRenderedImage(), pParam);
//System.out.println("Image data: " + imageData.size());
// Write metadata
writeMeta(renderedImage, imageData.size(), compress);
writeMeta(pImage.getRenderedImage(), imageData.size());
// Write image data
writeBody(imageData);
@@ -104,31 +107,34 @@ public final class IFFImageWriter extends ImageWriterBase {
processImageComplete();
}
private void writeBody(ByteArrayOutputStream imageData) throws IOException {
private void writeBody(ByteArrayOutputStream pImageData) throws IOException {
imageOutput.writeInt(IFF.CHUNK_BODY);
imageOutput.writeInt(imageData.size());
imageOutput.writeInt(pImageData.size());
// NOTE: This is much faster than imageOutput.write(imageData.toByteArray())
// NOTE: This is much faster than imageOutput.write(pImageData.toByteArray())
// as the data array is not duplicated
try (OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput)) {
imageData.writeTo(adapter);
pImageData.writeTo(adapter);
}
if (imageData.size() % 2 == 0) {
if (pImageData.size() % 2 == 0) {
imageOutput.writeByte(0); // PAD
}
imageOutput.flush();
}
private void packImageData(OutputStream outputStream, RenderedImage image, final boolean compress) throws IOException {
private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException {
// TODO: Allow param to dictate uncompressed
// TODO: Allow param to dictate type PBM?
// TODO: Subsample/AOI
final OutputStream output = compress ? new EncoderStream(outputStream, new PackBitsEncoder(), true) : outputStream;
final ColorModel model = image.getColorModel();
final Raster raster = image.getData();
final boolean compress = shouldCompress(pImage);
final OutputStream output = compress ? new EncoderStream(pOutput, new PackBitsEncoder(), true) : pOutput;
final ColorModel model = pImage.getColorModel();
final Raster raster = pImage.getData();
final int width = image.getWidth();
final int height = image.getHeight();
final int width = pImage.getWidth();
final int height = pImage.getHeight();
// Store each row of pixels
// 0. Loop pr channel
@@ -136,6 +142,7 @@ public final class IFFImageWriter extends ImageWriterBase {
// 2. Perform byteRun1 compression for each plane separately
// 3. Write the plane data for each plane
//final int planeWidth = (width + 7) / 8;
final int planeWidth = 2 * ((width + 15) / 16);
final byte[] planeData = new byte[8 * planeWidth];
final int channels = (model.getPixelSize() + 7) / 8;
@@ -160,6 +167,10 @@ public final class IFFImageWriter extends ImageWriterBase {
for (int p = 0; p < planesPerChannel; p++) {
output.write(planeData, p * planeWidth, planeWidth);
if (!compress && planeWidth % 2 != 0) {
output.write(0); // PAD
}
}
}
@@ -171,16 +182,17 @@ public final class IFFImageWriter extends ImageWriterBase {
output.flush();
}
private void writeMeta(RenderedImage image, int bodyLength, boolean compress) throws IOException {
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
// Annotation ANNO chunk, 8 + annoData.length bytes
String annotation = String.format("Written by %s IFFImageWriter %s", getOriginatingProvider().getVendorName(), getOriginatingProvider().getVersion());
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes());
ColorModel cm = image.getColorModel();
ColorModel cm = pImage.getColorModel();
IndexColorModel icm = null;
// Bitmap header BMHD chunk, 8 + 20 bytes
int compression = compress ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE;
// By default, don't compress narrow images
int compression = shouldCompress(pImage) ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE;
BMHDChunk header;
if (cm instanceof IndexColorModel) {
@@ -188,12 +200,12 @@ public final class IFFImageWriter extends ImageWriterBase {
icm = (IndexColorModel) cm;
int trans = icm.getTransparency() == Transparency.BITMASK ? BMHDChunk.MASK_TRANSPARENT_COLOR : BMHDChunk.MASK_NONE;
int transPixel = icm.getTransparency() == Transparency.BITMASK ? icm.getTransparentPixel() : 0;
header = new BMHDChunk(image.getWidth(), image.getHeight(), icm.getPixelSize(),
header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), icm.getPixelSize(),
trans, compression, transPixel);
}
else {
//System.out.println(cm.getClass().getName());
header = new BMHDChunk(image.getWidth(), image.getHeight(), cm.getPixelSize(),
header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), cm.getPixelSize(),
BMHDChunk.MASK_NONE, compression, 0);
}
@@ -205,7 +217,7 @@ public final class IFFImageWriter extends ImageWriterBase {
}
// ILBM(4) + anno(8+len) + header(8+20) + cmap(8+len)? + body(8+len);
int size = 4 + 8 + anno.chunkLength + 28 + 8 + bodyLength;
int size = 4 + 8 + anno.chunkLength + 28 + 8 + pBodyLength;
if (cmap != null) {
size += 8 + cmap.chunkLength;
}
@@ -219,30 +231,21 @@ public final class IFFImageWriter extends ImageWriterBase {
header.writeChunk(imageOutput);
if (cmap != null) {
//System.out.println("CMAP written");
cmap.writeChunk(imageOutput);
}
}
private boolean shouldCompress(final RenderedImage image, final ImageWriteParam param) {
if (param != null && param.canWriteCompressed()) {
switch (param.getCompressionMode()) {
case ImageWriteParam.MODE_DISABLED:
return false;
case ImageWriteParam.MODE_EXPLICIT:
return IFFWriteParam.COMPRESSION_TYPES[1].equals(param.getCompressionType());
default:
// Fall through
}
}
return image.getWidth() >= 32;
private boolean shouldCompress(RenderedImage pImage) {
return pImage.getWidth() >= 32;
}
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File(args[0]));
public static void main(String[] pArgs) throws IOException {
BufferedImage image = ImageIO.read(new File(pArgs[0]));
ImageWriter writer = new IFFImageWriter(new IFFImageWriterSpi());
writer.setOutput(ImageIO.createImageOutputStream(new File(args[1])));
writer.setOutput(ImageIO.createImageOutputStream(new File(pArgs[1])));
//writer.addIIOWriteProgressListener(new ProgressListenerBase() {
// int mCurrPct = 0;
//
@@ -30,11 +30,13 @@
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import java.io.IOException;
import java.util.Locale;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import java.util.Locale;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
/**
* IFFImageWriterSpi
@@ -51,19 +53,19 @@ public class IFFImageWriterSpi extends ImageWriterSpiBase {
super(new IFFProviderInfo());
}
public boolean canEncodeImage(final ImageTypeSpecifier type) {
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
// TODO: Probably can't store 16 bit types etc...
// TODO: Can't store CMYK (well.. it does, but they can't be read back)
return true;
}
@Override
public ImageWriter createWriterInstance(Object extension) {
public ImageWriter createWriterInstance(Object pExtension) throws IOException {
return new IFFImageWriter(this);
}
@Override
public String getDescription(Locale locale) {
public String getDescription(Locale pLocale) {
return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image writer";
}
}
@@ -40,11 +40,11 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
* @version $Id: IFFProviderInfo.java,v 1.0 20/03/15 harald.kuhr Exp$
*/
final class IFFProviderInfo extends ReaderWriterProviderInfo {
IFFProviderInfo() {
protected IFFProviderInfo() {
super(
IFFProviderInfo.class,
new String[] {"iff", "IFF"},
new String[] {"iff", "lbm", "ham", "ham8", "ilbm", "rgb8", "deep"},
new String[] {"iff", "lbm", "ham", "ham8", "ilbm"},
new String[] {"image/iff", "image/x-iff"},
"com.twelvemonkeys.imageio.plugins.iff.IFFImageReader",
new String[]{"com.twelvemonkeys.imageio.plugins.iff.IFFImageReaderSpi"},
@@ -56,11 +56,11 @@ final class IFFUtil {
* @return the rotation table
*/
static private long[] rtable(int n) {
return new long[] {
0x00000000L , 0x00000001L << n, 0x00000100L << n, 0x00000101L << n,
0x00010000L << n, 0x00010001L << n, 0x00010100L << n, 0x00010101L << n,
0x01000000L << n, 0x01000001L << n, 0x01000100L << n, 0x01000101L << n,
0x01010000L << n, 0x01010001L << n, 0x01010100L << n, 0x01010101L << n
return new long[]{
0x00000000l << n, 0x00000001l << n, 0x00000100l << n, 0x00000101l << n,
0x00010000l << n, 0x00010001l << n, 0x00010100l << n, 0x00010101l << n,
0x01000000l << n, 0x01000001l << n, 0x01000100l << n, 0x01000101l << n,
0x01010000l << n, 0x01010001l << n, 0x01010100l << n, 0x01010101l << n
};
}
@@ -75,16 +75,16 @@ final class IFFUtil {
* Bits from the source are rotated 90 degrees clockwise written to the
* destination.
*
* @param src source pixel data
* @param srcPos starting index of 8 x 8 bit source tile
* @param srcStep byte offset between adjacent rows in source
* @param dst destination pixel data
* @param dstPos starting index of 8 x 8 bit destination tile
* @param dstStep byte offset between adjacent rows in destination
* @param pSrc source pixel data
* @param pSrcPos starting index of 8 x 8 bit source tile
* @param pSrcStep byte offset between adjacent rows in source
* @param pDst destination pixel data
* @param pDstPos starting index of 8 x 8 bit destination tile
* @param pDstStep byte offset between adjacent rows in destination
*/
static void bitRotateCW(final byte[] src, int srcPos, int srcStep,
final byte[] dst, int dstPos, int dstStep) {
int idx = srcPos;
static void bitRotateCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
int lonyb;
int hinyb;
@@ -92,41 +92,41 @@ final class IFFUtil {
long hi = 0;
for (int i = 0; i < 8; i++) {
lonyb = src[idx] & 0xF;
hinyb = (src[idx] >> 4) & 0xF;
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= RTABLE[i][hinyb];
idx += srcStep;
idx += pSrcStep;
}
idx = dstPos;
idx = pDstPos;
dst[idx] = (byte)((hi >> 24) & 0xFF);
idx += dstStep;
if (idx < dst.length) {
dst[idx] = (byte)((hi >> 16) & 0xFF);
idx += dstStep;
if (idx < dst.length) {
dst[idx] = (byte)((hi >> 8) & 0xFF);
idx += dstStep;
if (idx < dst.length) {
dst[idx] = (byte)(hi & 0xFF);
idx += dstStep;
pDst[idx] = (byte)((hi >> 24) & 0xFF);
idx += pDstStep;
if (idx < pDst.length) {
pDst[idx] = (byte)((hi >> 16) & 0xFF);
idx += pDstStep;
if (idx < pDst.length) {
pDst[idx] = (byte)((hi >> 8) & 0xFF);
idx += pDstStep;
if (idx < pDst.length) {
pDst[idx] = (byte)(hi & 0xFF);
idx += pDstStep;
}
}
}
if (idx < dst.length) {
dst[idx] = (byte)((lo >> 24) & 0xFF);
idx += dstStep;
if (idx < dst.length) {
dst[idx] = (byte)((lo >> 16) & 0xFF);
idx += dstStep;
if (idx < dst.length) {
dst[idx] = (byte)((lo >> 8) & 0xFF);
idx += dstStep;
if (idx < dst.length) {
dst[idx] = (byte)(lo & 0xFF);
if (idx < pDst.length) {
pDst[idx] = (byte)((lo >> 24) & 0xFF);
idx += pDstStep;
if (idx < pDst.length) {
pDst[idx] = (byte)((lo >> 16) & 0xFF);
idx += pDstStep;
if (idx < pDst.length) {
pDst[idx] = (byte)((lo >> 8) & 0xFF);
idx += pDstStep;
if (idx < pDst.length) {
pDst[idx] = (byte)(lo & 0xFF);
}
}
}
@@ -137,16 +137,16 @@ final class IFFUtil {
* Rotate bits counterclockwise.
* The IFFImageWriter uses this to convert pixel bits from chunky to planar.
*
* @param src source pixel data (only lower 8 bits used)
* @param srcPos starting index of 8 x 8 bit source tile
* @param srcStep byte offset between adjacent rows in source
* @param dst destination pixel data
* @param dstPos starting index of 8 x 8 bit destination tile
* @param dstStep byte offset between adjacent rows in destination
* @param pSrc source pixel data (only lower 8 bits used)
* @param pSrcPos starting index of 8 x 8 bit source tile
* @param pSrcStep byte offset between adjacent rows in source
* @param pDst destination pixel data
* @param pDstPos starting index of 8 x 8 bit destination tile
* @param pDstStep byte offset between adjacent rows in destination
*/
static void bitRotateCCW(final int[] src, int srcPos, @SuppressWarnings("SameParameterValue") int srcStep,
final byte[] dst, int dstPos, int dstStep) {
int idx = srcPos;
static void bitRotateCCW(final int[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
int lonyb;
int hinyb;
@@ -154,49 +154,48 @@ final class IFFUtil {
long hi = 0;
for (int i = 7; i >= 0; i--) {
lonyb = src[idx] & 0xF;
hinyb = (src[idx] >> 4) & 0xF;
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= RTABLE[i][hinyb];
idx += srcStep;
idx += pSrcStep;
}
idx = dstPos;
idx = pDstPos;
dst[idx] = (byte)(lo & 0xFF);
idx += dstStep;
dst[idx] = (byte)((lo >> 8) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((lo >> 16) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((lo >> 24) & 0xFF);
pDst[idx] = (byte)(lo & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((lo >> 8) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((lo >> 16) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((lo >> 24) & 0xFF);
idx += dstStep;
idx += pDstStep;
dst[idx] = (byte)(hi & 0xFF);
idx += dstStep;
dst[idx] = (byte)((hi >> 8) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((hi >> 16) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((hi >> 24) & 0xFF);
pDst[idx] = (byte)(hi & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((hi >> 8) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((hi >> 16) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((hi >> 24) & 0xFF);
}
/**
* Rotate bits counterclockwise.
* The IFFImageWriter uses this to convert pixel bits from chunky to planar.
*
* @param src source pixel data
* @param srcPos starting index of 8 x 8 bit source tile
* @param srcStep byte offset between adjacent rows in source
* @param dst destination pixel data
* @param dstPos starting index of 8 x 8 bit destination tile
* @param dstStep byte offset between adjacent rows in destination
* @param pSrc source pixel data
* @param pSrcPos starting index of 8 x 8 bit source tile
* @param pSrcStep byte offset between adjacent rows in source
* @param pDst destination pixel data
* @param pDstPos starting index of 8 x 8 bit destination tile
* @param pDstStep byte offset between adjacent rows in destination
*/
@SuppressWarnings("unused")
static void bitRotateCCW(final byte[] src, int srcPos, int srcStep,
final byte[] dst, int dstPos, int dstStep) {
int idx = srcPos;
static void bitRotateCCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
int lonyb;
int hinyb;
@@ -204,57 +203,57 @@ final class IFFUtil {
long hi = 0;
for (int i = 7; i >= 0; i--) {
lonyb = src[idx] & 0xF;
hinyb = (src[idx] >> 4) & 0xF;
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= IFFUtil.RTABLE[i][hinyb];
idx += srcStep;
idx += pSrcStep;
}
idx = dstPos;
idx = pDstPos;
dst[idx] = (byte)(lo & 0xFF);
idx += dstStep;
dst[idx] = (byte)((lo >> 8) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((lo >> 16) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((lo >> 24) & 0xFF);
pDst[idx] = (byte)(lo & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((lo >> 8) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((lo >> 16) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((lo >> 24) & 0xFF);
idx += dstStep;
idx += pDstStep;
dst[idx] = (byte)(hi & 0xFF);
idx += dstStep;
dst[idx] = (byte)((hi >> 8) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((hi >> 16) & 0xFF);
idx += dstStep;
dst[idx] = (byte)((hi >> 24) & 0xFF);
pDst[idx] = (byte)(hi & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((hi >> 8) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((hi >> 16) & 0xFF);
idx += pDstStep;
pDst[idx] = (byte)((hi >> 24) & 0xFF);
}
/**
* Converts a byte array to an int.
*
* @param bytes a byte array of length 4
* @param pBytes a byte array of length 4
* @return the bytes converted to an int
*
* @throws ArrayIndexOutOfBoundsException if length is < 4
*/
static int toInt(final byte[] bytes) {
return (bytes[0] & 0xff) << 24 | (bytes[1] & 0xff) << 16
| (bytes[2] & 0xff) << 8 | (bytes[3] & 0xff);
static int toInt(final byte[] pBytes) {
return (pBytes[0] & 0xff) << 24 | (pBytes[1] & 0xff) << 16
| (pBytes[2] & 0xff) << 8 | (pBytes[3] & 0xff);
}
/**
* Converts an int to a four letter String.
*
* @param chunkId the chunk identifier
* @param pChunkId the chunk identifier
* @return a String
*/
static String toChunkStr(int chunkId) {
return new String(new byte[] {(byte) ((chunkId & 0xff000000) >> 24),
(byte) ((chunkId & 0x00ff0000) >> 16),
(byte) ((chunkId & 0x0000ff00) >> 8),
(byte) ((chunkId & 0x000000ff))});
static String toChunkStr(int pChunkId) {
return new String(new byte[] {(byte) ((pChunkId & 0xff000000) >> 24),
(byte) ((pChunkId & 0x00ff0000) >> 16),
(byte) ((pChunkId & 0x0000ff00) >> 8),
(byte) ((pChunkId & 0x000000ff))});
}
}
@@ -1,25 +0,0 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.ImageWriteParam;
import java.util.Locale;
/**
* IFFWriteParam.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: IFFWriteParam.java,v 1.0 03/02/2022 haraldk Exp$
*/
public final class IFFWriteParam extends ImageWriteParam {
static final String[] COMPRESSION_TYPES = {"NONE", "RLE"};
public IFFWriteParam(final Locale locale) {
super(locale);
compressionTypes = COMPRESSION_TYPES;
compressionType = compressionTypes[1];
canWriteCompressed = true;
}
}
@@ -80,9 +80,9 @@ final class MutableIndexColorModel extends ColorModel {
// TODO: Move validation to chunk (when reading)
if (index >= rgbs.length) {
// TODO: Issue IIO warning
System.err.println("warning - palette change register out of range");
System.err.printf("warning - palette change register out of range\n");
System.err.printf(" change structure %d index=%d (max %d)\n", i, index, getMapSize() - 1);
System.err.println(" ignoring it... colors might get messed up from here");
System.err.printf(" ignoring it... colors might get messed up from here\n");
}
else if (index != MP_REG_IGNORE) {
updateRGB(index, ((changes[i].r & 0xff) << 16) | ((changes[i].g & 0xff) << 8) | (changes[i].b & 0xff));
@@ -72,43 +72,42 @@ final class PCHGChunk extends AbstractMultiPaletteChunk {
private int totalChanges;
private int minReg;
PCHGChunk(int chunkLength) {
super(IFF.CHUNK_PCHG, chunkLength);
public PCHGChunk(int pChunkLength) {
super(IFF.CHUNK_PCHG, pChunkLength);
}
@Override
void readChunk(final DataInput input) throws IOException {
int compression = input.readUnsignedShort();
int flags = input.readUnsignedShort();
startLine = input.readShort();
lineCount = input.readUnsignedShort();
changedLines = input.readUnsignedShort();
minReg = input.readUnsignedShort();
int maxReg = input.readUnsignedShort();
/*int maxChangesPerLine = */
input.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware
totalChanges = input.readInt();
void readChunk(final DataInput pInput) throws IOException {
int compression = pInput.readUnsignedShort();
int flags = pInput.readUnsignedShort();
startLine = pInput.readShort();
lineCount = pInput.readUnsignedShort();
changedLines = pInput.readUnsignedShort();
minReg = pInput.readUnsignedShort();
int maxReg = pInput.readUnsignedShort();
/*int maxChangesPerLine = */pInput.readUnsignedShort(); // We don't really care, as we're not limited by the Amiga display hardware
totalChanges = pInput.readInt();
byte[] data;
switch (compression) {
case PCHG_COMP_NONE:
data = new byte[chunkLength - 20];
input.readFully(data);
pInput.readFully(data);
break;
case PCHG_COMP_HUFFMAN:
// NOTE: Huffman decompression is completely untested, due to lack of source data (read: Probably broken).
int compInfoSize = input.readInt();
int originalDataSize = input.readInt();
int compInfoSize = pInput.readInt();
int originalDataSize = pInput.readInt();
short[] compTree = new short[compInfoSize / 2];
for (int i = 0; i < compTree.length; i++) {
compTree[i] = input.readShort();
compTree[i] = pInput.readShort();
}
byte[] compData = new byte[chunkLength - 20 - 8 - compInfoSize];
input.readFully(compData);
pInput.readFully(compData);
data = new byte[originalDataSize];
@@ -38,8 +38,8 @@ package com.twelvemonkeys.imageio.plugins.iff;
* @version $Id: SHAMChunk.java,v 1.0 30.03.12 14:53 haraldk Exp$
*/
final class SHAMChunk extends AbstractMultiPaletteChunk {
SHAMChunk(int chunkLength) {
super(IFF.CHUNK_SHAM, chunkLength);
protected SHAMChunk(int pChunkLength) {
super(IFF.CHUNK_SHAM, pChunkLength);
}
@Override
@@ -1,65 +0,0 @@
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOException;
import java.awt.image.BufferedImage;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* XS24Chunk.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: XS24Chunk.java,v 1.0 01/02/2022 haraldk Exp$
*/
final class XS24Chunk extends IFFChunk {
private byte[] data;
int width;
int height;
XS24Chunk(final int chunkLength) {
super(IFF.CHUNK_XS24, chunkLength);
}
@Override
void readChunk(final DataInput input) throws IOException {
width = input.readUnsignedShort();
height = input.readUnsignedShort();
input.readShort(); // Not sure what this is?
int dataLength = width * height * 3;
if (dataLength > chunkLength - 6) {
throw new IIOException("Bad XS24 chunk: " + width + " * " + height + " * 3 > chunk length (" + chunkLength + ")");
}
data = new byte[dataLength];
input.readFully(data);
// Skip pad
for (int i = 0; i < chunkLength - dataLength - 6; i++) {
input.readByte();
}
}
@Override
void writeChunk(final DataOutput output) {
throw new InternalError("Not implemented: writeChunk()");
}
@Override
public String toString() {
return super.toString()
+ "{thumbnail=" + data.length + '}';
}
public BufferedImage thumbnail() {
BufferedImage thumbnail = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)
.createBufferedImage(width, height);
thumbnail.getRaster().setDataElements(0, 0, width, height, data);
return thumbnail;
}
}
@@ -1,24 +1,26 @@
package com.twelvemonkeys.imageio.plugins.iff;
import static org.junit.Assert.*;
import java.awt.image.IndexColorModel;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.w3c.dom.Node;
import javax.imageio.IIOException;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.*;
public class IFFImageMetadataTest {
@Test
public void testStandardFeatures() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardFeatures() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
final IFFImageMetadata metadata = new IFFImageMetadata(header, null);
final IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
// Standard metadata format
assertTrue(metadata.isStandardMetadataFormatSupported());
@@ -47,11 +49,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardChromaGray() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardChromaGray() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
@@ -74,11 +75,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardChromaRGB() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardChromaRGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
@@ -101,17 +101,16 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardChromaPalette() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
public void testStandardChromaPalette() {
BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1);
byte[] bw = {0, (byte) 0xff};
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.<GenericChunk>emptyList());
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(5, chroma.getLength());
assertEquals(4, chroma.getLength());
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
@@ -139,21 +138,14 @@ public class IFFImageMetadataTest {
assertEquals(rgb, item0.getAttribute("blue"));
}
// BackgroundIndex == 1
IIOMetadataNode backgroundIndex = (IIOMetadataNode) palette.getNextSibling();
assertEquals("BackgroundIndex", backgroundIndex.getNodeName());
assertEquals("1", backgroundIndex.getAttribute("value"));
// No more elements
assertNull(backgroundIndex.getNextSibling());
// TODO: BackgroundIndex == 1??
}
@Test
public void testStandardCompressionRLE() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardCompressionRLE() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode compression = metadata.getStandardCompressionNode();
assertNotNull(compression);
@@ -172,21 +164,19 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardCompressionNone() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0));
public void testStandardCompressionNone() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
}
@Test
public void testStandardDataILBM_Gray() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardDataILBM_Gray() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -209,11 +199,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataILBM_RGB() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardDataILBM_RGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -236,11 +225,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataILBM_RGBA() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardDataILBM_RGBA() {
BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -263,13 +251,12 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataILBM_Palette() throws IIOException {
public void testStandardDataILBM_Palette() {
for (int i = 1; i <= 8; i++) {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
BMHDChunk header = new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, rgb.length, rgb, rgb, rgb, 0), null, Collections.<GenericChunk>emptyList());
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -293,11 +280,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataPBM_Gray() throws IIOException {
Form header = Form.ofType(IFF.TYPE_PBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardDataPBM_Gray() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -320,11 +306,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataPBM_RGB() throws IIOException {
Form header = Form.ofType(IFF.TYPE_PBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardDataPBM_RGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -348,57 +333,40 @@ public class IFFImageMetadataTest {
@Test
public void testStandardDimensionNoViewport() throws IIOException {
BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
bitmapHeader.xAspect = 0;
bitmapHeader.yAspect = 0;
public void testStandardDimensionNoViewport() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNull(dimension);
}
@Test
public void testStandardDimensionNormal() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(new CAMGChunk(4));
public void testStandardDimensionNormal() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, new CAMGChunk(4), Collections.<GenericChunk>emptyList());
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName());
assertEquals(1, dimension.getLength());
// No Dimension node is okay, or one with an aspect ratio of 1.0
if (dimension != null) {
assertEquals("Dimension", dimension.getNodeName());
assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
public void testStandardDimensionHires() throws IIOException {
BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
bitmapHeader.xAspect = 2;
bitmapHeader.yAspect = 1;
public void testStandardDimensionHires() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
CAMGChunk viewPort = new CAMGChunk(4);
viewPort.camg = 0x8000;
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader)
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
@@ -413,19 +381,13 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDimensionInterlaced() throws IIOException {
BMHDChunk bitmapHeader = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
bitmapHeader.xAspect = 1;
bitmapHeader.yAspect = 2;
public void testStandardDimensionInterlaced() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
CAMGChunk viewPort = new CAMGChunk(4);
viewPort.camg = 0x4;
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader)
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
@@ -440,14 +402,13 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDimensionHiresInterlaced() throws IIOException {
public void testStandardDimensionHiresInterlaced() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
CAMGChunk viewPort = new CAMGChunk(4);
viewPort.camg = 0x8004;
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
@@ -462,11 +423,10 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDocument() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardDocument() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode document = metadata.getStandardDocumentNode();
assertNotNull(document);
@@ -481,15 +441,13 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardText() throws IIOException {
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
String[] texts = {"annotation", "äñnótâtïøñ"};
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII)))
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8)));
public void testStandardText() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
String[] texts = {"annotation", "äñnótâtïøñ"};
List<GenericChunk> meta = Arrays.asList(new GenericChunk(IFF.CHUNK_ANNO, texts[0].getBytes(StandardCharsets.US_ASCII)),
new GenericChunk(IFF.CHUNK_UTF8, texts[1].getBytes(StandardCharsets.UTF_8)));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, meta);
IIOMetadataNode text = metadata.getStandardTextNode();
assertNotNull(text);
@@ -499,28 +457,26 @@ public class IFFImageMetadataTest {
for (int i = 0; i < texts.length; i++) {
IIOMetadataNode textEntry = (IIOMetadataNode) text.item(i);
assertEquals("TextEntry", textEntry.getNodeName());
assertEquals(IFFUtil.toChunkStr(chunks[i]), textEntry.getAttribute("keyword"));
assertEquals(IFFUtil.toChunkStr(meta.get(i).chunkId), textEntry.getAttribute("keyword"));
assertEquals(texts[i], textEntry.getAttribute("value"));
}
}
@Test
public void testStandardTransparencyRGB() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardTransparencyRGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNull(transparency); // No transparency, just defaults
}
@Test
public void testStandardTransparencyRGBA() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
public void testStandardTransparencyRGBA() {
BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
@@ -535,12 +491,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardTransparencyPalette() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
public void testStandardTransparencyPalette() {
BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1);
byte[] bw = {0, (byte) 0xff};
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.<GenericChunk>emptyList());
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
@@ -553,131 +508,4 @@ public class IFFImageMetadataTest {
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
public void testStandardRGB8() throws IIOException {
Form header = Form.ofType(IFF.TYPE_RGB8)
.with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Chroma
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength());
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
assertEquals("RGB", colorSpaceType.getAttribute("name"));
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
assertEquals("NumChannels", numChannels.getNodeName());
assertEquals("4", numChannels.getAttribute("value"));
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
assertEquals("BlackIsZero", blackIsZero.getNodeName());
assertEquals("TRUE", blackIsZero.getAttribute("value"));
assertNull(blackIsZero.getNextSibling()); // No more children
// Data
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild();
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value"));
IIOMetadataNode sampleFormat = (IIOMetadataNode) planarConfiguration.getNextSibling();
assertEquals("SampleFormat", sampleFormat.getNodeName());
assertEquals("UnsignedIntegral", sampleFormat.getAttribute("value"));
IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFormat.getNextSibling();
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
assertEquals("8 8 8 1", bitsPerSample.getAttribute("value"));
assertNull(bitsPerSample.getNextSibling()); // No more children
// Transparency
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName());
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
assertNull(alpha.getNextSibling()); // No more children
}
@Test
public void testStandardDEEP() throws IIOException {
DPELChunk dpel = new DPELChunk(20);
dpel.typeDepths = new DPELChunk.TypeDepth[4];
for (int i = 0; i < dpel.typeDepths.length; i++) {
dpel.typeDepths[i] = new DPELChunk.TypeDepth(i == 0 ? 11 : i, 8);
}
Form header = Form.ofType(IFF.TYPE_DEEP)
.with(new DGBLChunk(8))
.with(dpel);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Chroma
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength());
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
assertEquals("RGB", colorSpaceType.getAttribute("name"));
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
assertEquals("NumChannels", numChannels.getNodeName());
assertEquals("4", numChannels.getAttribute("value"));
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
assertEquals("BlackIsZero", blackIsZero.getNodeName());
assertEquals("TRUE", blackIsZero.getAttribute("value"));
// TODO: BackgroundColor = 0x666666
assertNull(blackIsZero.getNextSibling()); // No more children
// Data
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
IIOMetadataNode planarConfiguration = (IIOMetadataNode) data.getFirstChild();
assertEquals("PlanarConfiguration", planarConfiguration.getNodeName());
assertEquals("PixelInterleaved", planarConfiguration.getAttribute("value"));
IIOMetadataNode sampleFormat = (IIOMetadataNode) planarConfiguration.getNextSibling();
assertEquals("SampleFormat", sampleFormat.getNodeName());
assertEquals("UnsignedIntegral", sampleFormat.getAttribute("value"));
IIOMetadataNode bitsPerSample = (IIOMetadataNode) sampleFormat.getNextSibling();
assertEquals("BitsPerSample", bitsPerSample.getNodeName());
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
assertNull(bitsPerSample.getNextSibling()); // No more children
// Transparency
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName());
assertEquals("premultiplied", alpha.getAttribute("value"));
assertNull(alpha.getNextSibling()); // No more children
}
}
@@ -88,16 +88,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
// 16 color indexed, multi palette (PCHG) - Ok
new TestData(getClassLoaderResource("/iff/Manhattan.PCHG"), new Dimension(704, 440)),
// 16 color indexed, multi palette (PCHG + SHAM) - Ok
new TestData(getClassLoaderResource("/iff/Somnambulist-2.SHAM"), new Dimension(704, 440)),
// Impulse RGB8 format straight from Imagine 2.0
new TestData(getClassLoaderResource("/iff/glowsphere2.rgb8"), new Dimension(640, 480)),
// Impulse RGB8 format written by ASDG ADPro, with cross boundary runs, which is probably not as per spec...
new TestData(getClassLoaderResource("/iff/tunnel04-adpro-cross-boundary-runs.rgb8"), new Dimension(640, 480)),
// TVPaint (TecSoft) DEEP format
new TestData(getClassLoaderResource("/iff/arch.deep"), new Dimension(800, 600)),
// TVPaint Project (TVPP is effectively same as the DEEP format, but multiple layers, background color etc.)
// TODO: This file contains one more image/layer, second DBOD chunk @1868908, len: 1199144!
new TestData(getClassLoaderResource("/iff/warm-and-bright.pro"), new Dimension(800, 600))
new TestData(getClassLoaderResource("/iff/Somnambulist-2.SHAM"), new Dimension(704, 440))
);
}
@@ -108,7 +99,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
@Override
protected List<String> getSuffixes() {
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm", "rgb8", "deep");
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm");
}
@Override
@@ -143,14 +134,9 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
for (int i = 0; i < 32; i++) {
// Make sure the color model is really EHB
try {
assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff);
assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff);
assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff);
}
catch (AssertionError err) {
throw new AssertionError("Color " + i + " " + err.getMessage(), err);
}
assertEquals("red", (reds[i] & 0xff) / 2, reds[i + 32] & 0xff);
assertEquals("blue", (blues[i] & 0xff) / 2, blues[i + 32] & 0xff);
assertEquals("green", (greens[i] & 0xff) / 2, greens[i + 32] & 0xff);
}
}
}
@@ -1,114 +0,0 @@
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.io.enc.DecodeException;
import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderAbstractTest;
import com.twelvemonkeys.io.enc.Encoder;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* RGB8RLEDecoderTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: RGB8RLEDecoderTest.java,v 1.0 28/01/2022 haraldk Exp$
*/
public class RGB8RLEDecoderTest extends DecoderAbstractTest {
public static final int BUFFER_SIZE = 1024;
@Override
public Decoder createDecoder() {
return new RGB8RLEDecoder();
}
@Override
public Encoder createCompatibleEncoder() {
return null;
}
@Test
public final void testDecodeEmpty() throws IOException {
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[0]);
int count = decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
assertEquals("Should not be able to read any bytes", 0, count);
}
@Test(expected = EOFException.class)
public final void testDecodePartial() throws IOException {
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0});
decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
fail("Should not be able to read any bytes");
}
@Test(expected = EOFException.class)
public final void testDecodePartialToo() throws IOException {
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, 1, 0, 0});
decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
fail("Should not be able to read any bytes");
}
@Test(expected = DecodeException.class)
public final void testDecodeZeroRun() throws IOException {
// The spec says that 0-runs should be used to signal that the run is > 127,
// and contained in the next byte, however, this is not used in practise and not supported.
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, 0});
decoder.decode(bytes, ByteBuffer.allocate(BUFFER_SIZE));
fail("Should not be able to read any bytes");
}
@Test
public final void testDecodeSingleOpaque() throws IOException {
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, 1});
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int count = decoder.decode(bytes, buffer);
assertEquals(4, count);
assertEquals(0x000000FF, buffer.getInt(0));
}
@Test
public final void testDecodeSingleTransparent() throws IOException {
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {0, 0, 0, (byte) 0x81});
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int count = decoder.decode(bytes, buffer);
assertEquals(4, count);
assertEquals(0x00000000, buffer.getInt(0));
}
@Test
public final void testDecodeMaxRun() throws IOException {
Decoder decoder = createDecoder();
ByteArrayInputStream bytes = new ByteArrayInputStream(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x7F});
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
int count = decoder.decode(bytes, buffer);
assertEquals(127 * 4, count);
for (int i = 0; i < 127; i++) {
assertEquals(0xFFFFFFFF, buffer.getInt(i));
}
}
}
Binary file not shown.
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -35,7 +35,6 @@ import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
import org.w3c.dom.Node;
import javax.imageio.IIOException;
@@ -159,25 +158,21 @@ class JPEGImage10Metadata extends AbstractMetadata {
case JPEG.DHT:
HuffmanTable huffmanTable = (HuffmanTable) segment;
IIOMetadataNode dht = new IIOMetadataNode("dht");
IIOMetadataNode dcTables = new IIOMetadataNode("dht");
IIOMetadataNode acTables = new IIOMetadataNode("dht");
appendHuffmanTables(huffmanTable, 0, dcTables);
appendHuffmanTables(huffmanTable, 1, acTables);
markerSequence.appendChild(dcTables);
// Native metadata has a limit of max 4 children of the DHT, we split by class only if we must...
if (dcTables.getLength() + acTables.getLength() > 4) {
markerSequence.appendChild(acTables);
}
else {
while (acTables.hasChildNodes()) {
dcTables.appendChild(acTables.removeChild(acTables.getFirstChild()));
for (int i = 0; i < 4; i++) {
for (int c = 0; c < 2; c++) {
if (huffmanTable.isPresent(i, c)) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(c));
dhtable.setAttribute("htableId", String.valueOf(i));
dhtable.setUserObject(huffmanTable.toNativeTable(i, c));
dht.appendChild(dhtable);
}
}
}
markerSequence.appendChild(dht);
break;
case JPEG.DQT:
@@ -275,18 +270,6 @@ class JPEGImage10Metadata extends AbstractMetadata {
}
}
private void appendHuffmanTables(HuffmanTable huffmanTable, int tableClass, IIOMetadataNode dht) {
for (int i = 0; i < 4; i++) {
if (huffmanTable.isPresent(i, tableClass)) {
IIOMetadataNode dhtable = new IIOMetadataNode("dhtable");
dhtable.setAttribute("class", String.valueOf(tableClass));
dhtable.setAttribute("htableId", String.valueOf(i));
dhtable.setUserObject(huffmanTable.toNativeTable(i, tableClass));
dht.appendChild(dhtable);
}
}
}
private void appendICCProfile(IIOMetadataNode app0JFIF) {
if (embeddedICCProfile != null) {
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
@@ -0,0 +1,345 @@
/*
* Copyright (c) 2013, 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.jpeg;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEG;
import com.twelvemonkeys.xml.XMLSerializer;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.metadata.IIOInvalidTreeException;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.color.ICC_Profile;
import java.io.IOException;
import java.util.List;
/**
* JPEGImage10MetadataCleaner
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: JPEGImage10MetadataCleaner.java,v 1.0 22.10.13 14:41 haraldk Exp$
*/
final class JPEGImage10MetadataCleaner {
private final JPEGImageReader reader;
JPEGImage10MetadataCleaner(final JPEGImageReader reader) {
this.reader = reader;
}
IIOMetadata cleanMetadata(final IIOMetadata imageMetadata) throws IOException {
// We filter out pretty much everything from the stream..
// Meaning we have to read get *all APP segments* and re-insert into metadata.
List<Application> appSegments = reader.getAppSegments(JPEGImageReader.ALL_APP_MARKERS, null);
// NOTE: There's a bug in the merging code in JPEGMetadata mergeUnknownNode that makes sure all "unknown" nodes are added twice in certain conditions.... ARGHBL...
// DONE: 1: Work around
// TODO: 2: REPORT BUG!
// TODO: Report dht inconsistency bug (reads any amount of tables but only allows setting 4 tables)
// TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node. Need new format, might as well create a completely new format...
// As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like:
// http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html#ImageMetadata
/*
from: http://docs.oracle.com/javase/6/docs/api/javax/imageio/metadata/doc-files/jpeg_metadata.html
In future versions of the JPEG metadata format, other varieties of JPEG metadata may be supported (e.g. Exif)
by defining other types of nodes which may appear as a child of the JPEGvariety node.
(Note that an application wishing to interpret Exif metadata given a metadata tree structure in the
javax_imageio_jpeg_image_1.0 format must check for an unknown marker segment with a tag indicating an
APP1 marker and containing data identifying it as an Exif marker segment. Then it may use application-specific
code to interpret the data in the marker segment. If such an application were to encounter a metadata tree
formatted according to a future version of the JPEG metadata format, the Exif marker segment might not be
unknown in that format - it might be structured as a child node of the JPEGvariety node.
Thus, it is important for an application to specify which version to use by passing the string identifying
the version to the method/constructor used to obtain an IIOMetadata object.)
*/
IIOMetadataNode tree = (IIOMetadataNode) imageMetadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
IIOMetadataNode jpegVariety = (IIOMetadataNode) tree.getElementsByTagName("JPEGvariety").item(0);
IIOMetadataNode markerSequence = (IIOMetadataNode) tree.getElementsByTagName("markerSequence").item(0);
JFIF jfifSegment = reader.getJFIF();
JFXX jfxx = reader.getJFXX();
AdobeDCT adobeDCT = reader.getAdobeDCT();
ICC_Profile embeddedICCProfile = reader.getEmbeddedICCProfile(true);
Frame sof = reader.getSOF();
boolean hasRealJFIF = false;
boolean hasRealJFXX = false;
boolean hasRealICC = false;
if (jfifSegment != null) {
// Normal case, conformant JFIF with 1 or 3 components
// TODO: Test if we have CMY or other non-JFIF color space?
if (sof.componentsInFrame() == 1 || sof.componentsInFrame() == 3) {
IIOMetadataNode jfif = new IIOMetadataNode("app0JFIF");
jfif.setAttribute("majorVersion", String.valueOf(jfifSegment.majorVersion));
jfif.setAttribute("minorVersion", String.valueOf(jfifSegment.minorVersion));
jfif.setAttribute("resUnits", String.valueOf(jfifSegment.units));
jfif.setAttribute("Xdensity", String.valueOf(Math.max(1, jfifSegment.xDensity))); // Avoid 0 density
jfif.setAttribute("Ydensity", String.valueOf(Math.max(1,jfifSegment.yDensity)));
jfif.setAttribute("thumbWidth", String.valueOf(jfifSegment.xThumbnail));
jfif.setAttribute("thumbHeight", String.valueOf(jfifSegment.yThumbnail));
jpegVariety.appendChild(jfif);
hasRealJFIF = true;
// Add app2ICC and JFXX as proper nodes
if (embeddedICCProfile != null) {
IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
app2ICC.setUserObject(embeddedICCProfile);
jfif.appendChild(app2ICC);
hasRealICC = true;
}
if (jfxx != null) {
IIOMetadataNode JFXX = new IIOMetadataNode("JFXX");
jfif.appendChild(JFXX);
IIOMetadataNode app0JFXX = new IIOMetadataNode("app0JFXX");
app0JFXX.setAttribute("extensionCode", String.valueOf(jfxx.extensionCode));
ThumbnailReader thumbnailReader = JFXXThumbnail.from(jfxx, reader.getThumbnailReader());
IIOMetadataNode jfifThumb;
switch (jfxx.extensionCode) {
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.JPEG:
jfifThumb = new IIOMetadataNode("JFIFthumbJPEG");
// Contains it's own "markerSequence" with full DHT, DQT, SOF etc...
IIOMetadata thumbMeta = thumbnailReader.readMetadata();
Node thumbTree = thumbMeta.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
jfifThumb.appendChild(thumbTree.getLastChild());
app0JFXX.appendChild(jfifThumb);
break;
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.INDEXED:
jfifThumb = new IIOMetadataNode("JFIFthumbPalette");
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
app0JFXX.appendChild(jfifThumb);
break;
case com.twelvemonkeys.imageio.plugins.jpeg.JFXX.RGB:
jfifThumb = new IIOMetadataNode("JFIFthumbRGB");
jfifThumb.setAttribute("thumbWidth", String.valueOf(thumbnailReader.getWidth()));
jfifThumb.setAttribute("thumbHeight", String.valueOf(thumbnailReader.getHeight()));
app0JFXX.appendChild(jfifThumb);
break;
default:
reader.processWarningOccurred(String.format("Unknown JFXX extension code: %d", jfxx.extensionCode));
}
JFXX.appendChild(app0JFXX);
hasRealJFXX = true;
}
}
else {
// Typically CMYK JPEG with JFIF segment (Adobe or similar).
reader.processWarningOccurred(String.format(
"Incompatible JFIF marker segment in stream. " +
"SOF%d has %d color components, JFIF allows only 1 or 3 components. Ignoring JFIF marker.",
sof.marker & 0xf, sof.componentsInFrame()
));
}
}
// Special case: Broken AdobeDCT segment, inconsistent with SOF, use values from SOF
if (adobeDCT != null && (adobeDCT.transform == AdobeDCT.YCCK && sof.componentsInFrame() < 4 ||
adobeDCT.transform == AdobeDCT.YCC && sof.componentsInFrame() < 3)) {
reader.processWarningOccurred(String.format(
"Invalid Adobe App14 marker. Indicates %s data, but SOF%d has %d color component(s). " +
"Ignoring Adobe App14 marker.",
adobeDCT.transform == AdobeDCT.YCCK ? "YCCK/CMYK" : "YCC/RGB",
sof.marker & 0xf, sof.componentsInFrame()
));
// Remove bad AdobeDCT
NodeList app14Adobe = tree.getElementsByTagName("app14Adobe");
for (int i = app14Adobe.getLength() - 1; i >= 0; i--) {
Node item = app14Adobe.item(i);
item.getParentNode().removeChild(item);
}
// We don't add this as unknown marker, as we are certain it's bogus by now
}
Node next = null;
for (Application segment : appSegments) {
// Except real app0JFIF, app0JFXX, app2ICC and app14Adobe, add all the app segments that we filtered away as "unknown" markers
if (segment.marker == JPEG.APP0 && "JFIF".equals(segment.identifier) && hasRealJFIF) {
continue;
}
else if (segment.marker == JPEG.APP0 && "JFXX".equals(segment.identifier) && hasRealJFXX) {
continue;
}
else if (segment.marker == JPEG.APP1 && "Exif".equals(segment.identifier) /* always inserted */) {
continue;
}
else if (segment.marker == JPEG.APP2 && "ICC_PROFILE".equals(segment.identifier) && hasRealICC) {
continue;
}
else if (segment.marker == JPEG.APP14 && "Adobe".equals(segment.identifier) /* always inserted */) {
continue;
}
IIOMetadataNode unknown = new IIOMetadataNode("unknown");
unknown.setAttribute("MarkerTag", Integer.toString(segment.marker & 0xff));
unknown.setUserObject(segment.data);
// try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(segment.data))) {
// String identifier = segment.identifier;
// int off = identifier != null ? identifier.length() + 1 : 0;
//
// byte[] data = new byte[off + segment.data.length];
//
// if (identifier != null) {
// System.arraycopy(identifier.getBytes(Charset.forName("ASCII")), 0, data, 0, identifier.length());
// }
//
// stream.readFully(data, off, segment.data.length);
//
// unknown.setUserObject(data);
// }
if (next == null) {
// To be semi-compatible with the functionality in mergeTree,
// let's insert after the last unknown tag, or before any other tag if no unknown tag exists
NodeList unknowns = markerSequence.getElementsByTagName("unknown");
if (unknowns.getLength() > 0) {
next = unknowns.item(unknowns.getLength() - 1).getNextSibling();
}
else {
next = markerSequence.getFirstChild();
}
}
markerSequence.insertBefore(unknown, next);
}
// Known issues in the com.sun classes, if sof/sos component id or selector is negative,
// setFromTree will fail. We'll fix the range from -128...127 to be 0...255.
NodeList sofs = markerSequence.getElementsByTagName("sof");
if (sofs.getLength() > 0) {
NodeList components = sofs.item(0).getChildNodes();
for (int i = 0; i < components.getLength(); i++) {
forceComponentIdInRange((IIOMetadataNode) components.item(i), "componentId");
}
}
NodeList sos = markerSequence.getElementsByTagName("sos");
for (int i = 0; i < sos.getLength(); i++) {
NodeList specs = sos.item(i).getChildNodes();
for (int j = 0; j < specs.getLength(); j++) {
forceComponentIdInRange((IIOMetadataNode) specs.item(j), "componentSelector");
}
}
// Inconsistency issue in the com.sun classes, it can read metadata with dht containing
// more than 4 children, but will not allow setting such a tree...
// We'll split AC/DC tables into separate dht nodes.
NodeList dhts = markerSequence.getElementsByTagName("dht");
for (int j = 0; j < dhts.getLength(); j++) {
Node dht = dhts.item(j);
NodeList dhtables = dht.getChildNodes();
if (dhtables.getLength() < 1) {
// Why is there an empty DHT node?
dht.getParentNode().removeChild(dht);
reader.processWarningOccurred("Metadata contains empty dht node. Ignoring.");
}
else if (dhtables.getLength() > 4) {
IIOMetadataNode acTables = new IIOMetadataNode("dht");
dht.getParentNode().insertBefore(acTables, dht.getNextSibling());
// Split into 2 dht nodes, one for AC and one for DC
for (int i = dhtables.getLength() - 1; i >= 0 ; i--) {
Element dhtable = (Element) dhtables.item(i);
String tableClass = dhtable.getAttribute("class");
if ("1".equals(tableClass)) {
dht.removeChild(dhtable);
acTables.insertBefore(dhtable, acTables.getFirstChild());
}
}
}
}
try {
imageMetadata.setFromTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0, tree);
}
catch (IIOInvalidTreeException e) {
if (JPEGImageReader.DEBUG) {
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(imageMetadata.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0), false);
System.out.println("-- 8< --");
new XMLSerializer(System.out, System.getProperty("file.encoding")).serialize(tree, false);
}
throw e;
}
return imageMetadata;
}
private void forceComponentIdInRange(final IIOMetadataNode component, final String attributeName) {
String attribute = component.getAttribute(attributeName);
if (attribute != null) {
try {
int componentId = Integer.parseInt(attribute);
if (componentId < 0) {
// Metadata doesn't like negative component ids/specs
// We'll convert to the positive value it probably should have been
componentId = ((byte) componentId) & 0xff;
component.setAttribute(attributeName, String.valueOf(componentId));
}
}
catch (NumberFormatException ignore) {
if ("scanComponentSpec".equals(component.getNodeName())) {
reader.processWarningOccurred("Bad SOS component selector: " + attribute);
}
else {
reader.processWarningOccurred("Bad SOF component id: " + attribute);
}
}
}
}
}
@@ -31,7 +31,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.color.YCbCrConverter;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
@@ -104,7 +103,6 @@ public final class JPEGImageReader extends ImageReaderBase {
// TODO: As we already parse the SOF segments, maybe we should stop delegating getWidth/getHeight etc?
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.jpeg.debug"));
final static boolean FORCE_RASTER_CONVERSION = "force".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.jpeg.raster"));
/** Internal constant for referring all APP segments */
static final int ALL_APP_MARKERS = -1;
@@ -336,8 +334,8 @@ public final class JPEGImageReader extends ImageReaderBase {
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
else if (FORCE_RASTER_CONVERSION || bogusAdobeDCT
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
else if (bogusAdobeDCT
|| profile != null && !ColorSpaces.isCS_sRGB(profile)
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
if (DEBUG) {
@@ -616,7 +614,7 @@ public final class JPEGImageReader extends ImageReaderBase {
}
}
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) throws IOException {
private ICC_Profile ensureDisplayProfile(final ICC_Profile profile) {
// NOTE: This is probably not the right way to do it... :-P
// TODO: Consider moving method to ColorSpaces class or new class in imageio.color package
@@ -632,7 +630,7 @@ public final class JPEGImageReader extends ImageReaderBase {
intToBigEndian(ICC_Profile.icSigDisplayClass, profileData, ICC_Profile.icHdrDeviceClass); // Header is first
return ColorProfiles.createProfile(profileData);
return ICC_Profile.getInstance(profileData);
}
}
@@ -952,7 +950,11 @@ public final class JPEGImageReader extends ImageReaderBase {
return null;
}
return readICCProfileSafe(stream, allowBadIndexes);
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
int iccChunkDataSize = segment.data.length - segmentDataStart;
int iccSize = segment.data.length < segmentDataStart + 4 ? 0 : intFromBigEndian(segment.data, segmentDataStart);
return readICCProfileSafe(stream, allowBadIndexes, iccSize, iccChunkDataSize);
}
else if (!segments.isEmpty()) {
// NOTE: This is probably over-complicated, as I've never encountered ICC_PROFILE chunks out of order...
@@ -987,6 +989,9 @@ public final class JPEGImageReader extends ImageReaderBase {
InputStream[] streams = new InputStream[count];
streams[badICC ? 0 : chunkNumber - 1] = stream;
int iccChunkDataSize = 0;
int iccSize = 0;
for (int i = 1; i < count; i++) {
Application segment = segments.get(i);
stream = new DataInputStream(segment.data());
@@ -999,20 +1004,33 @@ public final class JPEGImageReader extends ImageReaderBase {
int index = badICC ? i : chunkNumber - 1;
streams[index] = stream;
int segmentDataStart = segment.identifier.length() + 3; // ICC_PROFILE + null + chunk number + count
iccChunkDataSize += segment.data.length - segmentDataStart;
if (index == 0) {
iccSize = intFromBigEndian(segment.data, segmentDataStart);
}
}
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes);
return readICCProfileSafe(new SequenceInputStream(Collections.enumeration(Arrays.asList(streams))), allowBadIndexes, iccSize, iccChunkDataSize);
}
return null;
}
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile) {
try {
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
return allowBadProfile ? ColorProfiles.readProfileRaw(stream) : ensureDisplayProfile(ColorProfiles.readProfile(stream));
private ICC_Profile readICCProfileSafe(final InputStream stream, final boolean allowBadProfile, final int iccSize, final int iccChunkDataSize) throws IOException {
if (iccSize < 0 || iccSize > iccChunkDataSize) {
processWarningOccurred(String.format("Truncated 'ICC_PROFILE' chunk(s), size: %d. Ignoring ICC profile.", iccSize));
return null;
}
catch (IOException | RuntimeException e) {
try {
ICC_Profile profile = ICC_Profile.getInstance(stream);
// NOTE: Need to ensure we have a display profile *before* validating, for the caching to work
return allowBadProfile ? profile : ColorSpaces.validateProfile(ensureDisplayProfile(profile));
}
catch (RuntimeException e) {
// NOTE: Throws either IllegalArgumentException or CMMException, depending on platform.
// Usual reason: Broken tools store truncated ICC profiles in a single ICC_PROFILE chunk...
processWarningOccurred(String.format("Bad 'ICC_PROFILE' chunk(s): %s. Ignoring ICC profile.", e.getMessage()));
@@ -31,7 +31,6 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.junit.Test;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
@@ -43,7 +42,6 @@ import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
@@ -52,13 +50,14 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* JPEGImage10MetadataTest.
* JPEGImage10MetadataCleanerTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: JPEGImage10MetadataTest.java,v 1.0 12/01/2022 haraldk Exp$
* @author last modified by $Author: harald.kuhr$
* @version $Id: JPEGImage10MetadataCleanerTest.java,v 1.0 08/08/16 harald.kuhr Exp$
*/
public class JPEGImage10MetadataTest {
public class JPEGImage10MetadataCleanerTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
@@ -70,14 +69,14 @@ public class JPEGImage10MetadataTest {
return lookupProviderByName(IIORegistry.getDefaultInstance(), "com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi", ImageReaderSpi.class);
}
// Unit/regression test for #276, #559
// Unit/regression test for #276
@Test
public void splitMoreThan4DHTSegments() throws Exception {
public void cleanMetadataMoreThan4DHTSegments() throws Exception {
List<String> testData = Arrays.asList("/jpeg/5dhtsegments.jpg", "/jpeg/6dhtsegments.jpg");
for (String data : testData) {
try (ImageInputStream origInput = ImageIO.createImageInputStream(getClassResource(data));
ImageInputStream input = ImageIO.createImageInputStream(getClassResource(data))) {
try (ImageInputStream origInput = ImageIO.createImageInputStream(getClass().getResource(data));
ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource(data))) {
ImageReader origReader = SPI.delegateProvider.createReaderInstance();
origReader.setInput(origInput);
@@ -88,7 +87,9 @@ public class JPEGImage10MetadataTest {
IIOMetadata original = origReader.getImageMetadata(0);
IIOMetadataNode origTree = (IIOMetadataNode) original.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
JPEGImage10Metadata cleaned = (JPEGImage10Metadata) reader.getImageMetadata(0);
JPEGImage10MetadataCleaner cleaner = new JPEGImage10MetadataCleaner((JPEGImageReader) reader);
IIOMetadata cleaned = cleaner.cleanMetadata(origReader.getImageMetadata(0));
IIOMetadataNode cleanTree = (IIOMetadataNode) cleaned.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
NodeList origDHT = origTree.getElementsByTagName("dht");
@@ -123,8 +124,4 @@ public class JPEGImage10MetadataTest {
}
}
}
private URL getClassResource(String name) {
return getClass().getResource(name);
}
}
@@ -30,27 +30,19 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.Test;
import org.w3c.dom.NodeList;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
@@ -59,10 +51,29 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.IIORegistry;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import org.junit.Test;
import org.w3c.dom.NodeList;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
/**
* JPEGImageWriterTest
@@ -150,20 +161,6 @@ public class JPEGImageWriterTest extends ImageWriterAbstractTest<JPEGImageWriter
}
}
// Unit/regression test for #559
@Test
public void testTranscodeMoreThan4DHTSegments() throws IOException {
ImageWriter writer = createWriter();
ImageReader reader = ImageIO.getImageReader(writer);
ByteArrayOutputStream stream = transcode(reader, getClassLoaderResource("/jpeg/5dhtsegments.jpg"), writer, ColorSpace.TYPE_RGB);
reader.reset();
reader.setInput(new ByteArrayImageInputStream(stream.toByteArray()));
BufferedImage image = reader.read(0);
assertNotNull(image);
}
@Test
public void testTranscodeWithMetadataRGBtoRGB() throws IOException {
ImageWriter writer = createWriter();
+2 -2
View File
@@ -1,9 +1,9 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -30,7 +30,6 @@
package com.twelvemonkeys.imageio.metadata.jpeg;
import com.twelvemonkeys.imageio.color.ColorProfiles;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.psd.PSD;
@@ -43,6 +42,7 @@ import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
@@ -353,8 +353,8 @@ public final class JPEGSegmentUtil {
Directory psd = new PSDReader().read(stream);
Entry iccEntry = psd.getEntryById(PSD.RES_ICC_PROFILE);
if (iccEntry != null) {
ICC_Profile profile = ColorProfiles.createProfile((byte[]) iccEntry.getValue());
System.err.println("ICC Profile: " + profile);
ICC_ColorSpace colorSpace = new ICC_ColorSpace(ICC_Profile.getInstance((byte[]) iccEntry.getValue()));
System.err.println("colorSpace: " + colorSpace);
}
System.err.println("PSD: " + psd);
System.err.println(TIFFReader.HexDump.dump(segment.data));
@@ -173,14 +173,10 @@ public final class TIFFEntry extends AbstractEntry {
return "TileByteCounts";
case TIFF.TAG_COPYRIGHT:
return "Copyright";
case TIFF.TAG_YCBCR_COEFFICIENTS:
return "YCbCrCoefficients";
case TIFF.TAG_YCBCR_SUB_SAMPLING:
return "YCbCrSubSampling";
case TIFF.TAG_YCBCR_POSITIONING:
return "YCbCrPositioning";
case TIFF.TAG_REFERENCE_BLACK_WHITE:
return "ReferenceBlackWhite";
case TIFF.TAG_COLOR_MAP:
return "ColorMap";
case TIFF.TAG_INK_SET:
@@ -80,7 +80,7 @@ public final class TIFFReader extends MetadataReader {
private final Set<Long> parsedIFDs = new TreeSet<>();
private long inputLength;
private long length;
private boolean longOffsets;
private int offsetSize;
@@ -127,7 +127,7 @@ public final class TIFFReader extends MetadataReader {
throw new IIOException(String.format("Wrong TIFF magic in input data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
}
inputLength = input.length();
length = input.length();
return readLinkedIFDs(input);
}
@@ -140,7 +140,7 @@ public final class TIFFReader extends MetadataReader {
// Read linked IFDs
while (ifdOffset != 0) {
try {
if ((inputLength > 0 && ifdOffset >= inputLength) || !isValidOffset(input, ifdOffset) || !parsedIFDs.add(ifdOffset)) {
if ((length > 0 && ifdOffset >= length) || !parsedIFDs.add(ifdOffset)) {
// TODO: Issue warning
if (DEBUG) {
System.err.println("Bad IFD offset: " + ifdOffset);
@@ -218,7 +218,7 @@ public final class TIFFReader extends MetadataReader {
for (long ifdOffset : ifdOffsets) {
try {
if ((inputLength > 0 && ifdOffset >= inputLength) || !isValidOffset(input, ifdOffset) || !parsedIFDs.add(ifdOffset)) {
if ((length > 0 && ifdOffset >= length) || !parsedIFDs.add(ifdOffset)) {
// TODO: Issue warning
if (DEBUG) {
System.err.println("Bad IFD offset: " + ifdOffset);
@@ -330,14 +330,20 @@ public final class TIFFReader extends MetadataReader {
long valueLength = getValueLength(type, count);
Object value;
if (valueLength > 0 && valueLength <= offsetSize) {
value = readValueInLine(pInput, type, count);
pInput.skipBytes(offsetSize - valueLength);
}
else {
long valueOffset = readOffset(pInput); // This is the *value* iff the value size is <= offsetSize
value = readValueAt(pInput, valueOffset, valueLength, type, count);
// Note: This a precaution
if (count >= Integer.MAX_VALUE || length > 0 && length < valueOffset + valueLength) {
value = new EOFException(String.format("TIFF value offset or size too large: %d/%d bytes (length: %d bytes)", valueOffset, valueLength, length));
}
else {
value = readValueAt(pInput, valueOffset, type, count);
}
}
return new TIFFEntry(tagId, type, value);
@@ -359,52 +365,18 @@ public final class TIFFReader extends MetadataReader {
return (int) count;
}
private boolean isValidOffset(final ImageInputStream input, final long pos) throws IOException {
// TODO: If the position returns false, we could limit the length to pos for further reads...
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
long pos = pInput.getStreamPosition();
try {
input.mark();
input.seek(pos);
return input.read() >= 0;
}
catch (IOException e) {
return false;
}
finally {
input.reset();
}
}
private boolean isValidLengthAtOffset(final ImageInputStream input, long offset, long valueLength) throws IOException {
// NOTE: For values smaller than Short.MAX_VALUE, we simply try, and handle the potential EOFException when reading
return (inputLength < 0 || inputLength >= offset + valueLength)
&& (valueLength < Short.MAX_VALUE || isValidOffset(input, offset + valueLength - 1));
}
private Object readValueAt(final ImageInputStream input, final long offset, final long length, final short type, final int count) throws IOException {
long pos = input.getStreamPosition();
try {
input.seek(offset);
// Avoid OOME due to corrupted/malicious data
if (count < Integer.MAX_VALUE && isValidLengthAtOffset(input, offset, length)) {
return readValue(input, type, count, longOffsets);
}
else {
throw new EOFException(String.format("TIFF value offset or size too large: @%08x/%d bytes (input length: %s)", offset, length, inputLength >= 0 ? inputLength + " bytes" : "unknown"));
}
pInput.seek(pOffset);
return readValue(pInput, pType, pCount, longOffsets);
}
catch (EOFException e) {
// TODO: Add warning listener API and report problem to client code
if (DEBUG) {
System.err.println(e);
}
return e;
}
finally {
input.seek(pos);
pInput.seek(pos);
}
}
@@ -292,26 +292,7 @@ public final class TIFFWriter extends MetadataWriter {
private int getCount(final Entry entry) {
Object value = entry.getValue();
if (value instanceof String) {
return computeStringLength((String) value);
}
else if (value instanceof String[]) {
return computeStringLength((String[]) value);
}
else {
return entry.valueCount();
}
}
private int computeStringLength(String... values) {
int sum = 0;
for (String value : values) {
sum += value.getBytes(StandardCharsets.UTF_8).length + 1;
}
return sum;
return value instanceof String ? ((String) value).getBytes(StandardCharsets.UTF_8).length + 1 : entry.valueCount();
}
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
@@ -429,9 +410,7 @@ public final class TIFFWriter extends MetadataWriter {
break;
}
case TIFF.TYPE_ASCII:
writeStrings(stream, (String[]) value);
break;
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
}
@@ -444,7 +423,9 @@ public final class TIFFWriter extends MetadataWriter {
stream.writeByte(((Number) value).intValue());
break;
case TIFF.TYPE_ASCII:
writeStrings(stream, (String) value);
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
stream.write(bytes);
stream.write(0);
break;
case TIFF.TYPE_SHORT:
case TIFF.TYPE_SSHORT:
@@ -481,13 +462,6 @@ public final class TIFFWriter extends MetadataWriter {
}
}
private void writeStrings(ImageOutputStream stream, String... values) throws IOException {
for (String value : values) {
stream.write(value.getBytes(StandardCharsets.UTF_8));
stream.write(0);
}
}
private void writeValueAt(final long dataOffset, final Object value, final short type, final ImageOutputStream stream) throws IOException {
writeOffset(stream, dataOffset);
long position = stream.getStreamPosition();
@@ -30,21 +30,11 @@
package com.twelvemonkeys.imageio.metadata.xmp;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.Validate;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
@@ -53,11 +43,13 @@ import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReader;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.*;
/**
* XMPReader
@@ -75,9 +67,10 @@ public final class XMPReader extends MetadataReader {
public Directory read(final ImageInputStream input) throws IOException {
Validate.notNull(input, "input");
try {
DocumentBuilderFactory factory = createDocumentBuilderFactory();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
try {
// TODO: Consider parsing using SAX?
// TODO: Determine encoding and parse using a Reader...
// TODO: Refactor scanner to return inputstream?
@@ -86,6 +79,9 @@ public final class XMPReader extends MetadataReader {
builder.setErrorHandler(new DefaultHandler());
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding"));
// serializer.serialize(document);
String toolkit = getToolkit(document);
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
@@ -96,33 +92,10 @@ public final class XMPReader extends MetadataReader {
throw new IIOException(e.getMessage(), e);
}
catch (ParserConfigurationException e) {
throw new RuntimeException(e);
throw new RuntimeException(e); // TODO: Or IOException?
}
}
private DocumentBuilderFactory createDocumentBuilderFactory() throws ParserConfigurationException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
// Security: Disable XInclude & expanding entity references ("bombs"), not needed for XMP
factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false);
// Security: Enable "secure processing", to prevent DoS attacks
factory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, true);
// Security: Remove possibility to access external DTDs or Schema, not needed for XMP
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
// Security: Disable loading of external DTD and entities, not needed for XMP
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
return factory;
}
private String getToolkit(Document document) {
NodeList xmpmeta = document.getElementsByTagNameNS(XMP.NS_X, "xmpmeta");
@@ -136,7 +109,7 @@ public final class XMPReader extends MetadataReader {
}
private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes, String toolkit) {
Map<String, List<Entry>> subdirs = new LinkedHashMap<>();
Map<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
for (Node desc : asIterable(pNodes)) {
if (desc.getParentNode() != pParentNode) {
@@ -154,7 +127,7 @@ public final class XMPReader extends MetadataReader {
// Lookup
List<Entry> dir = subdirs.get(node.getNamespaceURI());
if (dir == null) {
dir = new ArrayList<>();
dir = new ArrayList<Entry>();
subdirs.put(node.getNamespaceURI(), dir);
}
@@ -166,7 +139,7 @@ public final class XMPReader extends MetadataReader {
else {
// TODO: This method contains loads of duplication an should be cleaned up...
// Support attribute short-hand syntax
Map<String, List<Entry>> subsubdirs = new LinkedHashMap<>();
Map<String, List<Entry>> subsubdirs = new LinkedHashMap<String, List<Entry>>();
parseAttributesForKnownElements(subsubdirs, node);
@@ -188,7 +161,7 @@ public final class XMPReader extends MetadataReader {
}
}
List<Directory> entries = new ArrayList<>(subdirs.size());
List<Directory> entries = new ArrayList<Directory>(subdirs.size());
// TODO: Should we still allow asking for a subdirectory by item id?
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
@@ -206,7 +179,7 @@ public final class XMPReader extends MetadataReader {
private RDFDescription parseAsResource(Node node) {
// See: http://www.w3.org/TR/REC-rdf-syntax/#section-Syntax-parsetype-resource
List<Entry> entries = new ArrayList<>();
List<Entry> entries = new ArrayList<Entry>();
for (Node child : asIterable(node.getChildNodes())) {
if (child.getNodeType() != Node.ELEMENT_NODE) {
@@ -231,7 +204,7 @@ public final class XMPReader extends MetadataReader {
List<Entry> dir = subdirs.get(attr.getNamespaceURI());
if (dir == null) {
dir = new ArrayList<>();
dir = new ArrayList<Entry>();
subdirs.put(attr.getNamespaceURI(), dir);
}
@@ -243,7 +216,7 @@ public final class XMPReader extends MetadataReader {
for (Node child : asIterable(node.getChildNodes())) {
if (XMP.NS_RDF.equals(child.getNamespaceURI()) && "Alt".equals(child.getLocalName())) {
// Support for <rdf:Alt><rdf:li> -> return a Map<String, Object> keyed on xml:lang
Map<String, Object> alternatives = new LinkedHashMap<>();
Map<String, Object> alternatives = new LinkedHashMap<String, Object>();
for (Node alternative : asIterable(child.getChildNodes())) {
if (XMP.NS_RDF.equals(alternative.getNamespaceURI()) && "li".equals(alternative.getLocalName())) {
NamedNodeMap attributes = alternative.getAttributes();
@@ -257,7 +230,7 @@ public final class XMPReader extends MetadataReader {
else if (XMP.NS_RDF.equals(child.getNamespaceURI()) && ("Seq".equals(child.getLocalName()) || "Bag".equals(child.getLocalName()))) {
// Support for <rdf:Seq><rdf:li> -> return array
// Support for <rdf:Bag><rdf:li> -> return array/unordered collection (how can a serialized collection not have order?)
List<Object> seq = new ArrayList<>();
List<Object> seq = new ArrayList<Object>();
for (Node sequence : asIterable(child.getChildNodes())) {
if (XMP.NS_RDF.equals(sequence.getNamespaceURI()) && "li".equals(sequence.getLocalName())) {
@@ -1,16 +1,16 @@
package com.twelvemonkeys.imageio.metadata.tiff;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Random;
import org.junit.Test;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* HalfTest.
@@ -97,7 +97,7 @@ public class HalfTest {
}
@Test(expected = NullPointerException.class)
public void testParseHalfNull() {
public void testParseHAlfNull() {
Half.parseHalf(null);
}
@@ -47,7 +47,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* TIFFWriterTest
@@ -83,13 +84,11 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
createWriter().write(directory, imageStream);
imageStream.flush();
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
}
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
@@ -130,15 +129,14 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
createWriter().write(directory, imageStream);
imageStream.flush();
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
}
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
@@ -166,15 +164,14 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
createWriter().write(directory, imageStream);
imageStream.flush();
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
}
assertEquals(output.size(), imageStream.getStreamPosition());
byte[] data = output.toByteArray();
@@ -206,13 +203,12 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
createWriter().write(directory, imageStream);
imageStream.flush();
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
}
assertEquals(output.size(), imageStream.getStreamPosition());
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
@@ -226,10 +222,14 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory original = createReader().read(getDataAsIIS());
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
try (ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output)) {
try {
createWriter().write(original, imageOutput);
}
finally {
imageOutput.close();
}
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
@@ -254,28 +254,6 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
assertEquals(98, stream.getStreamPosition());
}
@Test
public void testWriteASCIIArray() throws IOException {
ArrayList<Entry> entries = new ArrayList<>();
String[] strings = new String [] {"Twelve", "Monkeys", "ImageIO"};
entries.add(new TIFFEntry(TIFF.TAG_SOFTWARE, strings));
Directory directory = new IFD(entries);
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
createWriter().write(directory, imageStream);
}
byte[] data = output.toByteArray();
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(data));
assertNotNull(read.getEntryById(TIFF.TAG_SOFTWARE));
assertTrue("value not an string array", read.getEntryById(TIFF.TAG_SOFTWARE).getValue() instanceof String[]);
assertArrayEquals(strings, (String[]) read.getEntryById(TIFF.TAG_SOFTWARE).getValue());
}
@Test
public void testComputeIFDSizeNested() throws IOException {
TIFFEntry artist = new TIFFEntry(TIFF.TAG_SOFTWARE, TIFF.TYPE_ASCII, "TwelveMonkeys ImageIO");
@@ -30,32 +30,26 @@
package com.twelvemonkeys.imageio.metadata.xmp;
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 com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import org.junit.Test;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataReaderAbstractTest;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* XMPReaderTest
@@ -489,69 +483,4 @@ public class XMPReaderTest extends MetadataReaderAbstractTest {
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/PixelYDimension"), hasValue("550"));
assertThat(exif.getEntryById("http://ns.adobe.com/exif/1.0/NativeDigest"), hasValue("36864,40960,40961,37121,37122,40962,40963,37510,40964,36867,36868,33434,33437,34850,34852,34855,34856,37377,37378,37379,37380,37381,37382,37383,37384,37385,37386,37396,41483,41484,41486,41487,41488,41492,41493,41495,41728,41729,41730,41985,41986,41987,41988,41989,41990,41991,41992,41993,41994,41995,41996,42016,0,2,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,20,22,23,24,25,26,27,28,30;A7F21D25E2C562F152B2C4ECC9E534DA"));
}
@Test(timeout = 1500L)
public void testNoExternalRequest() throws Exception {
// TODO: Use dynamic port?
try (HTTPServer server = new HTTPServer(7777)) {
try {
createReader().read(getResourceAsIIS("/xmp/xmp-jpeg-xxe.xml"));
} catch (IOException ioe) {
if (ioe.getMessage().contains("501")) {
throw new AssertionError("Reading should not cause external requests", ioe);
}
// Any other exception is a bug (but might happen if the parser does not support certain features)
throw ioe;
}
}
}
private static class HTTPServer implements AutoCloseable {
private final ServerSocket server;
private final Thread thread;
HTTPServer(int port) throws IOException {
server = new ServerSocket(port, 1);
thread = new Thread(new Runnable() {
@Override public void run() {
serve();
}
});
thread.start();
}
private void serve() {
try {
Socket client = server.accept();
// Get the input stream, don't care about the request
try (InputStream inputStream = client.getInputStream()) {
while (inputStream.available() > 0) {
if (inputStream.read() == -1) {
break;
}
}
// Answer with 501, this will cause the client to throw IOException
try (OutputStream outputStream = client.getOutputStream()) {
outputStream.write("HTTP/1.0 501 Not Implemented\r\n\r\n".getBytes(StandardCharsets.UTF_8));
}
}
}
catch (IOException e) {
if (server.isClosed() && e instanceof SocketException) {
// Socket closed due to server close, all good
return;
}
throw new RuntimeException(e);
}
}
@Override public void close() throws Exception {
server.close();
thread.join(); // It's advised against throwing InterruptedException here, but this is not production code...
}
}
}
@@ -1,35 +0,0 @@
<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?><!DOCTYPE root [<!ENTITY % ext SYSTEM 'http://localhost:7777/xxx'> %ext;]>
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 10.16'>
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
<rdf:Description rdf:about=''
xmlns:xmpMM='http://ns.adobe.com/xap/1.0/mm/'>
<xmpMM:InstanceID>xmp.iid:7EDC21BF-371B-4189-90AF-C83A54A6A190</xmpMM:InstanceID>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end='w'?>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.4-SNAPSHOT</version>
<version>3.8.0-SNAPSHOT</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
@@ -140,9 +140,10 @@ public final class PCXImageReader extends ImageReaderBase {
if (palette != null) {
return ImageTypeSpecifiers.createFromIndexColorModel(palette);
}
// PCX Gray has 1 channel and no palette
return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
else {
// PCX Gray has 1 channel and no palette
return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
}
}
// PCX RGB has channels for 24 bit RGB, will be validated by ImageTypeSpecifier
@@ -213,12 +214,14 @@ public final class PCXImageReader extends ImageReaderBase {
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
for (int y = 0; y < height; y++) {
if (header.getBitsPerPixel() != 1) {
throw new AssertionError();
switch (header.getBitsPerPixel()) {
case 1:
readRowByte(input, srcRegion, xSub, ySub, planeData, 0, planeWidth * header.getChannels(), destRaster, clippedRow, y);
break;
default:
throw new AssertionError();
}
readRowByte(input, srcRegion, xSub, ySub, planeData, 0, planeWidth * header.getChannels(), destRaster, clippedRow, y);
int pixelPos = 0;
for (int planePos = 0; planePos < planeWidth; planePos++) {
BitRotator.bitRotateCW(planeData, planePos, planeWidth, rowDataByte, pixelPos, 1);
@@ -64,10 +64,17 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest<PCXImageReader>
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/pcx/input.pcx"), new Dimension(70, 46)), // RLE encoded RGB, v5
new TestData(getClassLoaderResource("/pcx/rose.pcx"), new Dimension(38, 48)), // RLE encoded, 16 color indexed (1 bps/4 channels), v5
new TestData(getClassLoaderResource("/pcx/animals.pcx"), new Dimension(239, 157)), // RLE encoded, 8 color indexed (1 bps/3 channels), v3
// TODO: Find good test images for the various combinations of bits/sample & channels
new TestData(getClassLoaderResource("/pcx/input.pcx"), new Dimension(70, 46)), // RLE encoded RGB
new TestData(getClassLoaderResource("/pcx/lena.pcx"), new Dimension(512, 512)), // RLE encoded RGB
new TestData(getClassLoaderResource("/pcx/lena2.pcx"), new Dimension(512, 512)), // RLE encoded, 256 color indexed (8 bps/1 channel)
new TestData(getClassLoaderResource("/pcx/lena3.pcx"), new Dimension(512, 512)), // RLE encoded, 16 color indexed (4 bps/1 channel)
new TestData(getClassLoaderResource("/pcx/lena4.pcx"), new Dimension(512, 512)), // RLE encoded, 16 color indexed (1 bps/4 channels)
new TestData(getClassLoaderResource("/pcx/lena5.pcx"), new Dimension(512, 512)), // RLE encoded, 256 color indexed (8 bps/1 channel)
new TestData(getClassLoaderResource("/pcx/lena6.pcx"), new Dimension(512, 512)), // RLE encoded, 8 color indexed (1 bps/3 channels)
new TestData(getClassLoaderResource("/pcx/lena7.pcx"), new Dimension(512, 512)), // RLE encoded, 4 color indexed (1 bps/2 channels)
new TestData(getClassLoaderResource("/pcx/lena8.pcx"), new Dimension(512, 512)), // RLE encoded, 4 color indexed (2 bps/1 channel)
new TestData(getClassLoaderResource("/pcx/lena9.pcx"), new Dimension(512, 512)), // RLE encoded, 2 color indexed (1 bps/1 channel)
new TestData(getClassLoaderResource("/pcx/lena10.pcx"), new Dimension(512, 512)), // RLE encoded, 16 color indexed (4 bps/1 channel) (uses only 8 colors)
new TestData(getClassLoaderResource("/pcx/DARKSTAR.PCX"), new Dimension(88, 52)), // RLE encoded monochrome (1 bps/1 channel)
new TestData(getClassLoaderResource("/pcx/MARBLES.PCX"), new Dimension(1419, 1001)), // RLE encoded RGB
new TestData(getClassLoaderResource("/pcx/no-palette-monochrome.pcx"), new Dimension(128, 152)), // RLE encoded monochrome (1 bps/1 channel)
@@ -142,12 +149,11 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest<PCXImageReader>
}
}
@Override
@Test
public void testReadWithSourceRegionParamEqualImage() throws IOException {
TestData data = getTestData().get(0);
assertReadWithSourceRegionParamEqualImage(new Rectangle(66, 0, 4, 4), data, 0);
assertReadWithSourceRegionParamEqualImage(new Rectangle(32, 20, 4, 4), data, 0);
assertReadWithSourceRegionParamEqualImage(new Rectangle(0, 42, 4, 4), data, 0);
TestData data = getTestData().get(1);
assertReadWithSourceRegionParamEqualImage(new Rectangle(200, 0, 4, 4), data, 0);
assertReadWithSourceRegionParamEqualImage(new Rectangle(100, 100, 4, 4), data, 0);
assertReadWithSourceRegionParamEqualImage(new Rectangle(0, 200, 4, 4), data, 0);
}
}
Binary file not shown.

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