Compare commits

...

64 Commits

Author SHA1 Message Date
Harald Kuhr 447ef6b8eb [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-22 14:49:41 +01:00
Harald Kuhr c19b6ede0a Back to 3.8.2-SNAPSHOT [skip ci] 2022-02-22 14:48:06 +01:00
Harald Kuhr 401c6355a1 Attempt at adding JavaDocs + use "release" profile for release-plugin [skip ci]
(cherry picked from commit d0881c8b5c)
2022-02-22 14:47:28 +01:00
Harald Kuhr be0cf16124 [maven-release-plugin] prepare for next development iteration 2022-02-22 13:31:01 +01:00
Harald Kuhr 47b0cd6e9a [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-22 13:30:56 +01:00
Harald Kuhr b52ab149b3 Back to 3.8.2-SNAPSHOT [skip ci] 2022-02-22 13:28:52 +01:00
Harald Kuhr 900c26a5ac [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-22 13:22:39 +01:00
Harald Kuhr 7233c593ac Adding description to top-level POM [skip ci]
(cherry picked from commit 976928f48c)
2022-02-22 13:20:15 +01:00
Harald Kuhr 8159ba1245 [maven-release-plugin] prepare for next development iteration 2022-02-21 16:18:51 +01:00
Harald Kuhr 92581a077b [maven-release-plugin] prepare release twelvemonkeys-3.8.2 2022-02-21 16:18:44 +01:00
Harald Kuhr f87b4d6748 Thank you, Travis [skip ci]
(cherry picked from commit e1c2f2ee73)
2022-02-21 16:15:35 +01:00
Harald Kuhr 0495f6e266 #661: JUnit test results take 4
(cherry picked from commit 92632fa2a3)
2022-02-21 16:15:33 +01:00
Harald Kuhr bc7c8ba20c #661: JUnit test results take 3
(cherry picked from commit 5a563e315f)
2022-02-21 16:15:31 +01:00
Harald Kuhr 0c270f8343 #661: JUnit test results take 2
(cherry picked from commit c06d47d123)
2022-02-21 16:15:28 +01:00
Harald Kuhr e1db332dca #661: JUnit test results
(cherry picked from commit fea6beb364)
2022-02-21 16:15:26 +01:00
Harald Kuhr 016977e382 PNM: New attempt at making the new header parser work on Windows.
(cherry picked from commit 4b951c06cc)
2022-02-21 16:15:23 +01:00
Harald Kuhr 134eecc59f PNM Windows issue. Temporary roll-back to working version.
(cherry picked from commit a3e6e52c95)
2022-02-21 16:15:23 +01:00
Harald Kuhr 16c78052ee PNM clean-up: Avoid leading/trailing whitespace in comments.
(cherry picked from commit 5347015cbd)
2022-02-21 16:15:20 +01:00
Harald Kuhr b51e8ccf6e PNM clean-up.
(cherry picked from commit 4d190892df)
2022-02-21 16:15:18 +01:00
Harald Kuhr 76a9ff1122 #660: Attempt at making the comment parsing more Windows-friendly...
(cherry picked from commit 60eab81709)
2022-02-21 16:15:17 +01:00
Harald Kuhr e9996f096f #660: Make sure region is within bounds of new test image...
(cherry picked from commit b400b6b157)
2022-02-21 16:15:17 +01:00
Harald Kuhr 93d42e1c24 #660: Farewell, Lena
(cherry picked from commit 499b3ef120)
2022-02-21 16:15:17 +01:00
Harald Kuhr 5824167600 IFF: Simplified aspect.
(cherry picked from commit 92bc9c73f6)
2022-02-21 16:15:14 +01:00
Harald Kuhr 126956ebd0 IFF: XS24 clean-up (again...)
(cherry picked from commit 2a77558cac)
2022-02-21 16:15:12 +01:00
Harald Kuhr d54ceba3ff IFF: XS24 clean-up
(cherry picked from commit 816cad60a8)
2022-02-21 16:15:11 +01:00
Harald Kuhr 11ee7e5e23 IFF: Thumbnail support for XS24 chunk (now without stderr output)
(cherry picked from commit 7167f81c69)
2022-02-21 16:15:11 +01:00
Harald Kuhr 954dffd213 IFF: Thumbnail support for XS24 chunk.
(cherry picked from commit f5cfa0e619)
2022-02-21 16:15:08 +01:00
Harald Kuhr b7b2a61c93 IFF: Read support for TVPaint DEEP and TVPP
+ Bonus: Massive code clean-up/refactor.

(cherry picked from commit 73ad024833)
2022-02-21 16:15:05 +01:00
Harald Kuhr 3cf6a4b836 IFF: More clean-up
(cherry picked from commit 379449b621)
2022-02-21 16:15:04 +01:00
Harald Kuhr a93be99933 IFF: Read support for Impulse (Imagine, Turbo Silver) RGB8 format.
(cherry picked from commit e17faad6fb)
2022-02-21 16:15:04 +01:00
Harald Kuhr b19bd1441f IFF clean-up.
(cherry picked from commit 1271a3d55e)
2022-02-21 16:15:04 +01:00
Harald Kuhr 482af60534 RIP: Sandbox
(cherry picked from commit 1cd594d113)
2022-02-21 16:15:00 +01:00
Harald Kuhr f55a6d30dd A little safer way to skip 6 bytes...
(cherry picked from commit b76f74e79a)
2022-02-21 16:14:56 +01:00
Harald Kuhr e5f6227479 #658: TGAImageReader now allows extension area of size 0
(cherry picked from commit 78817a489b)
2022-02-21 16:14:53 +01:00
Harald Kuhr 01d6fc8b49 #658: TGAImageReaderSpi now recognizes "true color" images with valid palette depth != 0
(cherry picked from commit b8f2a80ca6)
2022-02-21 16:14:51 +01:00
Oliver Schmidtmer f133ea7d61 findCompressionType always uses RLE if leading EOL is missing (#657)
Update of the last read byte is missing since the last update. So if only the first EOL is missing, further EOLs after the lines are not detected.

(cherry picked from commit ac8a36db1c)
2022-02-21 16:14:48 +01:00
Harald Kuhr 3d5cf0eecd #655 Experimental force raster conversion switch.
(cherry picked from commit 7e0d8922da)
2022-02-21 16:14:45 +01:00
Harald Kuhr 975e23c28f Fix for IIOInvalidTreeException: Invalid DHT node #559
(cherry picked from commit 9a6b8c9bfe)
2022-02-21 16:14:42 +01:00
Harald Kuhr be60f307f7 #656 Code clean-up + minor refactorings.
(cherry picked from commit eced5b8efd)
2022-02-21 16:14:39 +01:00
Oliver Schmidtmer 3e783fba92 Support writing ASCII array in TIFF metadata (#656)
* Support writing ASCII array in TIFF metadata

* corrected formatting and extracted string writing to method

(cherry picked from commit 74611e4e52)
2022-02-21 16:14:37 +01:00
Harald Kuhr 35ffe29e03 New CI badge + new maven badges, replaces #653
(cherry picked from commit b8614eca4d)
2022-02-21 16:14:35 +01:00
Harald Kuhr 55155aa61c #636: Correct name for shaded artifact.
(cherry picked from commit efd24456ac)
2022-02-21 16:14:32 +01:00
Gauthier 9e9decd5dd add support for Github Actions, publish snapshots to OSSRH automatically (#633)
* remove oss-parent

* add github workflow

* use java 16 for now

* disable fail fast

* add java 15

* use only java 8 and 11 for now

* snapshot deploy

* snapshot deploy

* oracle jdk

* oracle jdk

* oracle jdk

* kcms matrix

* kcms job name

* only deploy for snapshots

* try not operator

* prepare PR

* restore groupId

* Fixed Travis link + bonus project summary updates

* Readme improvements

* #629: Preliminary WebP animation (ANIM/ANMF) support

* #629: Fixed build

* Make tests pass on JDK 16 and 17 (#635)

* make tests pass on JDK 16 and 17
replace deprecated mockito-all by mockito-core, and updated to latest 3.x
replace deprecated org.mockito.Matchers

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis

* test on java 17

* try to fix warning about maven-source-plugin

Co-authored-by: Harald Kuhr <harald.kuhr@gmail.com>
(cherry picked from commit 191b2371c8)
2022-02-21 16:14:30 +01:00
Harald Kuhr 3c0b78549e #652: Avoid OOME for large values outside TIFF, even if length is unknown
(cherry picked from commit 33419ef291)
2022-02-21 16:14:27 +01:00
Harald Kuhr 46db5a2fbf #648: (Re-)Added support for nested layer groups
(cherry picked from commit 123f0bb7fc)
2022-02-21 16:14:25 +01:00
Harald Kuhr f6a9477279 #648: Removed unnecessary parentheses.
(cherry picked from commit 99b5f28a49)
2022-02-21 16:14:24 +01:00
Harald Kuhr b7192ae857 #648: Simplified logic, code style fixes and clean up.
(cherry picked from commit b30fb4f8c3)
2022-02-21 16:14:24 +01:00
Jack Yun c0b2769e3b Support Group Layer in psd (#648)
(cherry picked from commit dc0bdcbd5b)
2022-02-21 16:14:24 +01:00
Harald Kuhr 6c27ec6b30 Updated with latest versions.
(cherry picked from commit 0cf29c167d)
2022-02-21 16:14:21 +01:00
Harald Kuhr 0c90196357 [maven-release-plugin] prepare for next development iteration 2021-12-27 09:53:44 +01:00
Harald Kuhr 48f82a159f [maven-release-plugin] prepare release twelvemonkeys-3.8.1 2021-12-27 09:53:40 +01:00
Harald Kuhr 7105738811 #651: Fix ExtraSamplesColorModel equals + hashcode to behave nicely with ImageTypeSpecifier comparison.
(cherry picked from commit 98e4b76206)
2021-12-24 13:00:15 +01:00
Harald Kuhr 10aa4ba41e Minor clean-up.
(cherry picked from commit aa4b5db054)
2021-12-24 13:00:15 +01:00
Harald Kuhr 6fb06da4d7 #651: Fix ExtraSamplesColorModel to create correct length elements array.
(cherry picked from commit 433311c10d)
2021-12-24 13:00:15 +01:00
Harald Kuhr a963e1c355 Alternative fix for #650: Allow usage in OSGi environment.
(cherry picked from commit f50178bc78)
2021-12-23 11:02:46 +01:00
Snyk bot 966a9da45d fix: upgrade commons-io:commons-io from 2.9.0 to 2.11.0 (#647)
Snyk has created this PR to upgrade commons-io:commons-io from 2.9.0 to 2.11.0.

See this package in Maven Repository:
https://mvnrepository.com/artifact/commons-io/commons-io/

See this project in Snyk:
https://app.snyk.io/org/haraldk/project/9a1f6304-68e0-49c5-af4f-db1f87bd4f90?utm_source=github&utm_medium=referral&page=upgrade-pr

(cherry picked from commit e016e970e5)
2021-12-16 08:48:35 +01:00
Harald Kuhr 319b2c4e18 Overriding transitive dependency.
(cherry picked from commit 444aeabf21)
2021-12-15 17:01:38 +01:00
Harald Kuhr e9bf7d080c Getting rid of the dependencies too.
(cherry picked from commit 05507a59d6)
2021-12-15 16:29:53 +01:00
Harald Kuhr fb3691e2ee Delete deprecated servlet classes
(cherry picked from commit c4c89a0a25)
2021-12-15 16:24:41 +01:00
Harald Kuhr 25f9cc5c55 Delete deprecated Servlet classes
(cherry picked from commit b0ad6b2a4b)
2021-12-15 16:24:41 +01:00
Harald Kuhr 94777ddc96 #646: Spi now recognizes VP8 encoded images in VP8X ("extended format").
(cherry picked from commit 25c703f4b2)
2021-12-15 16:09:33 +01:00
Oleh Astappiev a4c12d0d64 Create jakartified package on build (#636)
* feat(servlet): create jakartified package on build

* feat(servlet): update README to include Jakarta classifier

(cherry picked from commit 529c59f93f)
2021-12-15 16:09:33 +01:00
Harald Kuhr 08a69886b1 Updated with the latest versions.
(cherry picked from commit 584b1d9b21)
2021-12-15 16:09:33 +01:00
Harald Kuhr ab85ff0ec8 Prepare for next version. 2021-12-15 16:09:06 +01:00
319 changed files with 3007 additions and 42567 deletions
+90
View File
@@ -0,0 +1,90 @@
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
@@ -1,16 +0,0 @@
dist: trusty
language: java
jdk:
- oraclejdk8 # LTS until Mar 2022
- oraclejdk11 # LTS until Sep 2023
- oraclejdk15 # out of support
- oraclejdk16 # out of support
- oraclejdk17 # LTS until Sep 2026
jobs:
include:
# Extra job, testing legacy CMM option
- jdk: oraclejdk8
env: MAVEN_OPTS=-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider
cache:
directories:
- $HOME/.m2
+77 -66
View File
@@ -1,5 +1,6 @@
[![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)
[![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/)
[![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)
@@ -16,36 +17,36 @@ As there is lots of legacy data out there, we see the need for open implementati
## File formats supported
| 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) |
| 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) |
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
@@ -271,12 +272,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.7.0</version>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.7.0</version>
<version>3.8.1</version>
</dependency>
<!--
@@ -286,7 +287,17 @@ 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.7.0</version>
<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>
</dependency>
</dependencies>
```
@@ -295,13 +306,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.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
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
#### Deploying the plugins in a web app
@@ -367,44 +378,44 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
### Links to prebuilt binaries
##### Latest version (3.7.0)
##### Latest version (3.8.1)
Requires Java 7 or later.
Common dependencies
* [common-lang-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.7.0/common-lang-3.7.0.jar)
* [common-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)
* [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)
ImageIO dependencies
* [imageio-core-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.7.0/imageio-core-3.7.0.jar)
* [imageio-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-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 plugins
* [imageio-bmp-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.7.0/imageio-bmp-3.7.0.jar)
* [imageio-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-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 plugins requiring 3rd party libs
* [imageio-batik-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.7.0/imageio-batik-3.7.0.jar)
* [imageio-batik-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.8.1/imageio-batik-3.8.1.jar)
Photoshop Path support for ImageIO
* [imageio-clippath-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.7.0/imageio-clippath-3.7.0.jar)
* [imageio-clippath-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.8.1/imageio-clippath-3.8.1.jar)
Servlet support
* [servlet-3.7.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.7.0/servlet-3.7.0.jar)
* [servlet-3.8.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.8.1/servlet-3.8.1.jar)
##### Old version (3.0.x)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.8.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
+8 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -48,6 +48,13 @@
<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>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
Binary file not shown.

Before

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.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -45,6 +45,7 @@ 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;
@@ -175,15 +176,22 @@ public final class IIOUtil {
* @param providerClassName name of the provider class.
* @param category provider category
*
* @return the provider instance, or {@code null}.
* @return the provider instance, or {@code null} if not found
*/
public static <T> T lookupProviderByName(final ServiceRegistry registry, final String providerClassName, Class<T> category) {
try {
return category.cast(registry.getServiceProviderByClass(Class.forName(providerClassName)));
}
catch (ClassNotFoundException ignore) {
return null;
// 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;
}
}
return null;
}
/**
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</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 pChunkId, int pChunkLength) {
super(pChunkId, pChunkLength);
public AbstractMultiPaletteChunk(int chunkId, int chunkLength) {
super(chunkId, chunkLength);
}
@Override
void readChunk(final DataInput pInput) throws IOException {
void readChunk(final DataInput input) throws IOException {
if (chunkId == IFF.CHUNK_SHAM) {
pInput.readUnsignedShort(); // Version, typically 0, skipped
input.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 = pInput.readUnsignedShort();
int data = input.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 pOutput) {
void writeChunk(DataOutput output) {
throw new UnsupportedOperationException("Method writeChunk not implemented");
}
@@ -30,12 +30,11 @@
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
*
@@ -110,64 +109,65 @@ final class BMHDChunk extends IFFChunk {
int pageWidth;
int pageHeight;
protected BMHDChunk(int pChunkLength) {
super(IFF.CHUNK_BMHD, pChunkLength);
BMHDChunk(int chunkLength) {
super(IFF.CHUNK_BMHD, chunkLength);
}
protected BMHDChunk(int pWidth, int pHeight, int pBitplanes, int pMaskType, int pCompressionType, int pTransparentIndex) {
BMHDChunk(int width, int height, int bitplanes, int maskType, int compressionType, int transparentIndex) {
super(IFF.CHUNK_BMHD, 20);
width = pWidth;
height = pHeight;
this.width = width;
this.height = height;
xPos = 0;
yPos = 0;
bitplanes = pBitplanes;
maskType = pMaskType;
compressionType = pCompressionType;
transparentIndex = pTransparentIndex;
this.bitplanes = bitplanes;
this.maskType = maskType;
this.compressionType = compressionType;
this.transparentIndex = transparentIndex;
xAspect = 1;
yAspect = 1;
pageWidth = Math.min(pWidth, Short.MAX_VALUE); // For some reason, these are signed?
pageHeight = Math.min(pHeight, Short.MAX_VALUE);
pageWidth = Math.min(width, Short.MAX_VALUE); // For some reason, these are signed?
pageHeight = Math.min(height, Short.MAX_VALUE);
}
@Override
void readChunk(final DataInput pInput) throws IOException {
void readChunk(final DataInput input) throws IOException {
if (chunkLength != 20) {
throw new IIOException("Unknown BMHD chunk length: " + chunkLength);
}
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();
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();
}
@Override
void writeChunk(final DataOutput pOutput) throws IOException {
pOutput.writeInt(chunkId);
pOutput.writeInt(chunkLength);
void writeChunk(final DataOutput output) throws IOException {
output.writeInt(chunkId);
output.writeInt(chunkLength);
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);
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);
}
@Override
@@ -33,6 +33,8 @@ package com.twelvemonkeys.imageio.plugins.iff;
import java.io.DataInput;
import java.io.DataOutput;
import static com.twelvemonkeys.lang.Validate.isTrue;
/**
* BODYChunk
*
@@ -40,17 +42,20 @@ import java.io.DataOutput;
* @version $Id: BODYChunk.java,v 1.0 28.feb.2006 01:25:49 haku Exp$
*/
final class BODYChunk extends IFFChunk {
protected BODYChunk(int pChunkLength) {
super(IFF.CHUNK_BODY, pChunkLength);
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;
}
@Override
void readChunk(final DataInput pInput) {
void readChunk(final DataInput input) {
throw new InternalError("BODY chunk should only be read from IFFImageReader");
}
@Override
void writeChunk(final DataOutput pOutput) {
void writeChunk(final DataOutput output) {
throw new InternalError("BODY chunk should only be written from IFFImageWriter");
}
}
@@ -30,12 +30,11 @@
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
*
@@ -49,21 +48,21 @@ final class CAMGChunk extends IFFChunk {
int camg;
public CAMGChunk(int pLength) {
super(IFF.CHUNK_CAMG, pLength);
CAMGChunk(int chunkLength) {
super(IFF.CHUNK_CAMG, chunkLength);
}
@Override
void readChunk(final DataInput pInput) throws IOException {
void readChunk(final DataInput input) throws IOException {
if (chunkLength != 4) {
throw new IIOException("Unknown CAMG chunk length: " + chunkLength);
}
camg = pInput.readInt();
camg = input.readInt();
}
@Override
void writeChunk(final DataOutput pOutput) {
void writeChunk(final DataOutput output) {
throw new InternalError("Not implemented: writeChunk()");
}
@@ -30,16 +30,13 @@
package com.twelvemonkeys.imageio.plugins.iff;
import java.awt.image.BufferedImage;
import javax.imageio.IIOException;
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
*
@@ -60,7 +57,7 @@ final class CMAPChunk extends IFFChunk {
private IndexColorModel model;
protected CMAPChunk(final int pChunkLength) {
CMAPChunk(final int pChunkLength) {
super(IFF.CHUNK_CMAP, pChunkLength);
}
@@ -70,7 +67,7 @@ final class CMAPChunk extends IFFChunk {
}
@Override
void readChunk(final DataInput pInput) throws IOException {
void readChunk(final DataInput input) throws IOException {
int numColors = chunkLength / 3;
reds = new byte[numColors];
@@ -78,9 +75,9 @@ final class CMAPChunk extends IFFChunk {
blues = reds.clone();
for (int i = 0; i < numColors; i++) {
reds[i] = pInput.readByte();
greens[i] = pInput.readByte();
blues[i] = pInput.readByte();
reds[i] = input.readByte();
greens[i] = input.readByte();
blues[i] = input.readByte();
}
// TODO: When reading in a CMAP for 8-bit-per-gun display or
@@ -93,25 +90,25 @@ final class CMAPChunk extends IFFChunk {
// All chunks are WORD aligned (even sized), may need to read pad...
if (chunkLength % 2 != 0) {
pInput.readByte();
input.readByte();
}
}
@Override
void writeChunk(final DataOutput pOutput) throws IOException {
pOutput.writeInt(chunkId);
pOutput.writeInt(chunkLength);
void writeChunk(final DataOutput output) throws IOException {
output.writeInt(chunkId);
output.writeInt(chunkLength);
final int length = model.getMapSize();
for (int i = 0; i < length; i++) {
pOutput.writeByte(model.getRed(i));
pOutput.writeByte(model.getGreen(i));
pOutput.writeByte(model.getBlue(i));
output.writeByte(model.getRed(i));
output.writeByte(model.getGreen(i));
output.writeByte(model.getBlue(i));
}
if (chunkLength % 2 != 0) {
pOutput.writeByte(0); // PAD
output.writeByte(0); // PAD
}
}
@@ -120,25 +117,11 @@ final class CMAPChunk extends IFFChunk {
return super.toString() + " {colorMap=" + model + "}";
}
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 {
public IndexColorModel getIndexColorModel(final Form.ILBMForm header) throws IIOException {
if (model == null) {
int numColors = reds.length; // All arrays are same size
if (isEHB) {
if (header.isEHB()) {
if (numColors == 32) {
reds = Arrays.copyOf(reds, numColors * 2);
blues = Arrays.copyOf(blues, numColors * 2);
@@ -161,8 +144,10 @@ 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.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
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
}
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 {
protected CTBLChunk(int pChunkLength) {
super(IFF.CHUNK_CTBL, pChunkLength);
CTBLChunk(int chunkLength) {
super(IFF.CHUNK_CTBL, chunkLength);
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, Harald Kuhr
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -28,45 +28,75 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
package com.twelvemonkeys.imageio.plugins.iff;
import java.net.URI;
import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
/**
* AbstractCacheRequest
* DGBLChunk
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: AbstractCacheRequest.java#1 $
* @version $Id: DGBLChunk.java,v 1.0 28.feb.2006 02:10:07 haku Exp$
*/
@Deprecated
public abstract class AbstractCacheRequest implements CacheRequest {
private final URI requestURI;
private final String method;
final class DGBLChunk extends IFFChunk {
protected AbstractCacheRequest(final URI pRequestURI, final String pMethod) {
requestURI = Validate.notNull(pRequestURI, "requestURI");
method = Validate.notNull(pMethod, "method");
/*
//
struct DGBL = {
//
// Size of source display
//
UWORD DisplayWidth,DisplayHeight;
//
// Type of compression
//
UWORD Compression;
//
// Pixel aspect, a ration w:h
//
UBYTE xAspect,yAspect;
};
*/
int displayWidth;
int displayHeight;
int compressionType;
int xAspect;
int yAspect;
DGBLChunk(int chunkLength) {
super(IFF.CHUNK_DGBL, chunkLength);
}
public URI getRequestURI() {
return requestURI;
@Override
void readChunk(final DataInput input) throws IOException {
if (chunkLength != 8) {
throw new IIOException("Unknown DBGL chunk length: " + chunkLength);
}
displayWidth = input.readUnsignedShort();
displayHeight = input.readUnsignedShort();
compressionType = input.readUnsignedShort();
xAspect = input.readUnsignedByte();
yAspect = input.readUnsignedByte();
}
public String getMethod() {
return method;
@Override
void writeChunk(final DataOutput output) {
throw new InternalError("Not implemented: writeChunk()");
}
// TODO: Consider overriding equals/hashcode
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append("[URI=").append(requestURI)
.append(", parameters=").append(getParameters())
.append(", headers=").append(getHeaders())
.append("]").toString();
return super.toString() +
"{displayWidth=" + displayWidth +
", displayHeight=" + displayHeight +
", compression=" + compressionType +
", xAspect=" + xAspect +
", yAspect=" + yAspect +
'}';
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, Harald Kuhr
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -28,51 +28,53 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.servlet.cache;
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.http.HttpServletResponse;
/**
* ServletCacheResponse
* DLOCChunk.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: ServletCacheResponse.java#2 $
* @author last modified by $Author: haraldk$
* @version $Id: DLOCChunk.java,v 1.0 31/01/2022 haraldk Exp$
*/
@Deprecated
public final class ServletCacheResponse extends AbstractCacheResponse {
private HttpServletResponse response;
final class DLOCChunk extends IFFChunk {
int width;
int height;
int x;
int y;
public ServletCacheResponse(HttpServletResponse pResponse) {
response = pResponse;
}
public OutputStream getOutputStream() throws IOException {
return response.getOutputStream();
DLOCChunk(final int chunkLength) {
super(IFF.CHUNK_DLOC, chunkLength);
}
@Override
public void setStatus(int pStatusCode) {
response.setStatus(pStatusCode);
super.setStatus(pStatusCode);
void readChunk(final DataInput input) throws IOException {
if (chunkLength != 8) {
throw new IIOException("Unknown DLOC chunk length: " + chunkLength);
}
width = input.readUnsignedShort();
height = input.readUnsignedShort();
x = input.readShort();
y = input.readShort();
}
@Override
public void addHeader(String pHeaderName, String pHeaderValue) {
response.addHeader(pHeaderName, pHeaderValue);
super.addHeader(pHeaderName, pHeaderValue);
void writeChunk(final DataOutput output) {
throw new InternalError("Not implemented: writeChunk()");
}
@Override
public void setHeader(String pHeaderName, String pHeaderValue) {
response.setHeader(pHeaderName, pHeaderValue);
super.setHeader(pHeaderName, pHeaderValue);
}
HttpServletResponse getResponse() {
return response;
public String toString() {
return super.toString() +
"{width=" + width +
", height=" + height +
", x=" + x +
", y=" + y + '}';
}
}
@@ -0,0 +1,103 @@
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 +
'}';
}
}
}
@@ -0,0 +1,421 @@
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;
protected GRABChunk(int pChunkLength) {
super(IFF.CHUNK_GRAB, pChunkLength);
GRABChunk(int chunkLength) {
super(IFF.CHUNK_GRAB, chunkLength);
}
protected GRABChunk(Point2D pPoint) {
GRABChunk(Point2D point) {
super(IFF.CHUNK_GRAB, 4);
point = pPoint;
this.point = point;
}
void readChunk(DataInput pInput) throws IOException {
void readChunk(DataInput input) throws IOException {
if (chunkLength != 4) {
throw new IIOException("Unknown GRAB chunk size: " + chunkLength);
}
point = new Point(pInput.readShort(), pInput.readShort());
point = new Point(input.readShort(), input.readShort());
}
void writeChunk(DataOutput pOutput) throws IOException {
pOutput.writeShort((int) point.getX());
pOutput.writeShort((int) point.getY());
void writeChunk(DataOutput output) throws IOException {
output.writeShort((int) point.getX());
output.writeShort((int) point.getY());
}
public String toString() {
@@ -44,31 +44,31 @@ final class GenericChunk extends IFFChunk {
byte[] data;
protected GenericChunk(int pChunkId, int pChunkLength) {
super(pChunkId, pChunkLength);
data = new byte[chunkLength];
GenericChunk(int chunkId, int chunkLength) {
super(chunkId, chunkLength);
data = new byte[this.chunkLength];
}
protected GenericChunk(int pChunkId, byte[] pChunkData) {
super(pChunkId, pChunkData.length);
data = pChunkData;
GenericChunk(int chunkId, byte[] chunkData) {
super(chunkId, chunkData.length);
data = chunkData;
}
@Override
void readChunk(final DataInput pInput) throws IOException {
pInput.readFully(data, 0, data.length);
void readChunk(final DataInput input) throws IOException {
input.readFully(data, 0, data.length);
skipData(pInput, chunkLength, data.length);
skipData(input, chunkLength, data.length);
}
@Override
void writeChunk(final DataOutput pOutput) throws IOException {
pOutput.writeInt(chunkId);
pOutput.writeInt(chunkLength);
pOutput.write(data, 0, data.length);
void writeChunk(final DataOutput output) throws IOException {
output.writeInt(chunkId);
output.writeInt(chunkLength);
output.write(data, 0, data.length);
if (data.length % 2 != 0) {
pOutput.writeByte(0); // PAD
output.writeByte(0); // PAD
}
}
@@ -49,6 +49,8 @@ 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) */
@@ -92,7 +94,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';
@@ -129,6 +131,18 @@ 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 pChunkId, int pChunkLength) {
chunkId = pChunkId;
chunkLength = pChunkLength;
protected IFFChunk(int chunkId, int chunkLength) {
this.chunkId = chunkId;
this.chunkLength = chunkLength;
}
abstract void readChunk(DataInput pInput) throws IOException;
abstract void readChunk(DataInput input) throws IOException;
abstract void writeChunk(DataOutput pOutput) throws IOException;
abstract void writeChunk(DataOutput output) throws IOException;
protected static void skipData(final DataInput pInput, final int chunkLength, final int dataReadSoFar) throws IOException {
protected static void skipData(final DataInput input, final int chunkLength, final int dataReadSoFar) throws IOException {
int toSkip = chunkLength - dataReadSoFar;
while (toSkip > 0) {
toSkip -= pInput.skipBytes(toSkip);
toSkip -= input.skipBytes(toSkip);
}
// Read pad
if (chunkLength % 2 != 0) {
pInput.readByte();
input.readByte();
}
}
@@ -13,31 +13,29 @@ import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
final class IFFImageMetadata extends AbstractMetadata {
private final int formType;
private final BMHDChunk header;
private final Form header;
private final IndexColorModel colorMap;
private final CAMGChunk viewPort;
private final List<GenericChunk> meta;
IFFImageMetadata(int formType, BMHDChunk header, IndexColorModel colorMap, CAMGChunk viewPort, List<GenericChunk> meta) {
this.formType = isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s");
IFFImageMetadata(Form header, IndexColorModel colorMap) {
this.header = notNull(header, "header");
isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s");
this.colorMap = colorMap;
this.viewPort = viewPort;
this.meta = meta;
this.meta = header.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;
}
}
@@ -48,7 +46,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");
@@ -62,6 +60,7 @@ final class IFFImageMetadata extends AbstractMetadata {
case 6:
case 7:
case 24:
case 25:
case 32:
csType.setAttribute("name", "RGB");
break;
@@ -72,10 +71,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 == 32) {
else if (header.bitplanes() == 25 || header.bitplanes() == 32) {
numChannels.setAttribute("value", Integer.toString(4));
}
else {
@@ -102,9 +101,16 @@ 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: Background color is the color of the transparent index in the color model?
// TODO: TVPP TVPaint Project files have a MIXR chunk with a background color
// and also a BGP1 (background pen 1?) and BGP2 chunks
// if (extensions != null && extensions.getBackgroundColor() != 0) {
// Color background = new Color(extensions.getBackgroundColor(), true);
//
@@ -121,7 +127,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
}
@@ -144,7 +150,10 @@ final class IFFImageMetadata extends AbstractMetadata {
// PlanarConfiguration
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
switch (formType) {
switch (header.formType) {
case TYPE_DEEP:
case TYPE_TVPP:
case TYPE_RGB8:
case TYPE_PBM:
planarConfiguration.setAttribute("value", "PixelInterleaved");
break;
@@ -152,7 +161,7 @@ final class IFFImageMetadata extends AbstractMetadata {
planarConfiguration.setAttribute("value", "PlaneInterleaved");
break;
default:
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(formType));
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType));
break;
}
data.appendChild(planarConfiguration);
@@ -163,7 +172,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);
@@ -171,7 +180,6 @@ final class IFFImageMetadata extends AbstractMetadata {
// SampleMSB not in format
return data;
}
private String bitsPerSampleValue(int bitplanes) {
@@ -187,16 +195,22 @@ 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("Ubknown bit count: " + bitplanes);
throw new IllegalArgumentException("Unknown bit count: " + bitplanes);
}
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
if (viewPort == null) {
if (header.aspect() == 0) {
return null;
}
@@ -204,7 +218,7 @@ final class IFFImageMetadata extends AbstractMetadata {
// PixelAspectRatio
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", String.valueOf((viewPort.isHires() ? 2f : 1f) / (viewPort.isLaced() ? 2f : 1f)));
pixelAspectRatio.setAttribute("value", String.valueOf(header.aspect()));
dimension.appendChild(pixelAspectRatio);
// TODO: HorizontalScreenSize?
@@ -241,20 +255,19 @@ final class IFFImageMetadata extends AbstractMetadata {
}
return text;
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes != 32) {
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) {
return null;
}
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
if (header.bitplanes == 32) {
if (header.bitplanes() == 25 || header.bitplanes() == 32) {
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", "nonpremultiplied");
alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied");
transparency.appendChild(alpha);
}
@@ -30,13 +30,12 @@
package com.twelvemonkeys.imageio.plugins.iff;
import java.io.IOException;
import java.util.Locale;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import java.io.IOException;
import java.util.Locale;
/**
* IFFImageReaderSpi
@@ -54,41 +53,41 @@ public final class IFFImageReaderSpi extends ImageReaderSpiBase {
}
@Override
public boolean canDecodeInput(Object pSource) throws IOException {
return pSource instanceof ImageInputStream && canDecode((ImageInputStream) pSource);
public boolean canDecodeInput(final Object source) throws IOException {
return source instanceof ImageInputStream && canDecode((ImageInputStream) source);
}
private static boolean canDecode(ImageInputStream pInput) throws IOException {
pInput.mark();
private static boolean canDecode(final ImageInputStream input) throws IOException {
input.mark();
try {
// Is it IFF
if (pInput.readInt() == IFF.CHUNK_FORM) {
pInput.readInt();// Skip length field
if (input.readInt() == IFF.CHUNK_FORM) {
input.readInt();// Skip length field
int type = pInput.readInt();
// Is it ILBM or PBM
if (type == IFF.TYPE_ILBM || type == IFF.TYPE_PBM) {
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
return true;
}
}
}
finally {
pInput.reset();
input.reset();
}
return false;
}
@Override
public ImageReader createReaderInstance(Object pExtension) throws IOException {
public ImageReader createReaderInstance(final Object extension) {
return new IFFImageReader(this);
}
@Override
public String getDescription(Locale pLocale) {
public String getDescription(Locale locale) {
return "Commodore Amiga/Electronic Arts Image Interchange Format (IFF) image reader";
}
}
@@ -30,31 +30,22 @@
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
@@ -68,8 +59,8 @@ import com.twelvemonkeys.io.enc.PackBitsEncoder;
*/
public final class IFFImageWriter extends ImageWriterBase {
IFFImageWriter(ImageWriterSpi pProvider) {
super(pProvider);
IFFImageWriter(ImageWriterSpi provider) {
super(provider);
}
@Override
@@ -83,23 +74,29 @@ public final class IFFImageWriter extends ImageWriterBase {
}
@Override
public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException {
public ImageWriteParam getDefaultWriteParam() {
return new IFFWriteParam(getLocale());
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
assertOutput();
if (pImage.hasRaster()) {
if (image.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, pImage.getRenderedImage(), pParam);
//System.out.println("Image data: " + imageData.size());
packImageData(imageData, renderedImage, compress);
// Write metadata
writeMeta(pImage.getRenderedImage(), imageData.size());
writeMeta(renderedImage, imageData.size(), compress);
// Write image data
writeBody(imageData);
@@ -107,34 +104,31 @@ public final class IFFImageWriter extends ImageWriterBase {
processImageComplete();
}
private void writeBody(ByteArrayOutputStream pImageData) throws IOException {
private void writeBody(ByteArrayOutputStream imageData) throws IOException {
imageOutput.writeInt(IFF.CHUNK_BODY);
imageOutput.writeInt(pImageData.size());
imageOutput.writeInt(imageData.size());
// NOTE: This is much faster than imageOutput.write(pImageData.toByteArray())
// NOTE: This is much faster than imageOutput.write(imageData.toByteArray())
// as the data array is not duplicated
try (OutputStream adapter = IIOUtil.createStreamAdapter(imageOutput)) {
pImageData.writeTo(adapter);
imageData.writeTo(adapter);
}
if (pImageData.size() % 2 == 0) {
if (imageData.size() % 2 == 0) {
imageOutput.writeByte(0); // PAD
}
imageOutput.flush();
}
private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException {
// TODO: Allow param to dictate uncompressed
// TODO: Allow param to dictate type PBM?
private void packImageData(OutputStream outputStream, RenderedImage image, final boolean compress) throws IOException {
// TODO: Subsample/AOI
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 OutputStream output = compress ? new EncoderStream(outputStream, new PackBitsEncoder(), true) : outputStream;
final ColorModel model = image.getColorModel();
final Raster raster = image.getData();
final int width = pImage.getWidth();
final int height = pImage.getHeight();
final int width = image.getWidth();
final int height = image.getHeight();
// Store each row of pixels
// 0. Loop pr channel
@@ -142,7 +136,6 @@ 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;
@@ -167,10 +160,6 @@ 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
}
}
}
@@ -182,17 +171,16 @@ public final class IFFImageWriter extends ImageWriterBase {
output.flush();
}
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
private void writeMeta(RenderedImage image, int bodyLength, boolean compress) 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 = pImage.getColorModel();
ColorModel cm = image.getColorModel();
IndexColorModel icm = null;
// Bitmap header BMHD chunk, 8 + 20 bytes
// By default, don't compress narrow images
int compression = shouldCompress(pImage) ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE;
int compression = compress ? BMHDChunk.COMPRESSION_BYTE_RUN : BMHDChunk.COMPRESSION_NONE;
BMHDChunk header;
if (cm instanceof IndexColorModel) {
@@ -200,12 +188,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(pImage.getWidth(), pImage.getHeight(), icm.getPixelSize(),
header = new BMHDChunk(image.getWidth(), image.getHeight(), icm.getPixelSize(),
trans, compression, transPixel);
}
else {
//System.out.println(cm.getClass().getName());
header = new BMHDChunk(pImage.getWidth(), pImage.getHeight(), cm.getPixelSize(),
header = new BMHDChunk(image.getWidth(), image.getHeight(), cm.getPixelSize(),
BMHDChunk.MASK_NONE, compression, 0);
}
@@ -217,7 +205,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 + pBodyLength;
int size = 4 + 8 + anno.chunkLength + 28 + 8 + bodyLength;
if (cmap != null) {
size += 8 + cmap.chunkLength;
}
@@ -231,21 +219,30 @@ public final class IFFImageWriter extends ImageWriterBase {
header.writeChunk(imageOutput);
if (cmap != null) {
//System.out.println("CMAP written");
cmap.writeChunk(imageOutput);
}
}
private boolean shouldCompress(RenderedImage pImage) {
return pImage.getWidth() >= 32;
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;
}
public static void main(String[] pArgs) throws IOException {
BufferedImage image = ImageIO.read(new File(pArgs[0]));
public static void main(String[] args) throws IOException {
BufferedImage image = ImageIO.read(new File(args[0]));
ImageWriter writer = new IFFImageWriter(new IFFImageWriterSpi());
writer.setOutput(ImageIO.createImageOutputStream(new File(pArgs[1])));
writer.setOutput(ImageIO.createImageOutputStream(new File(args[1])));
//writer.addIIOWriteProgressListener(new ProgressListenerBase() {
// int mCurrPct = 0;
//
@@ -30,13 +30,11 @@
package com.twelvemonkeys.imageio.plugins.iff;
import java.io.IOException;
import java.util.Locale;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import java.util.Locale;
/**
* IFFImageWriterSpi
@@ -53,19 +51,19 @@ public class IFFImageWriterSpi extends ImageWriterSpiBase {
super(new IFFProviderInfo());
}
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
public boolean canEncodeImage(final ImageTypeSpecifier type) {
// 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 pExtension) throws IOException {
public ImageWriter createWriterInstance(Object extension) {
return new IFFImageWriter(this);
}
@Override
public String getDescription(Locale pLocale) {
public String getDescription(Locale locale) {
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 {
protected IFFProviderInfo() {
IFFProviderInfo() {
super(
IFFProviderInfo.class,
new String[] {"iff", "IFF"},
new String[] {"iff", "lbm", "ham", "ham8", "ilbm"},
new String[] {"iff", "lbm", "ham", "ham8", "ilbm", "rgb8", "deep"},
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 << 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
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
};
}
@@ -75,16 +75,16 @@ final class IFFUtil {
* Bits from the source are rotated 90 degrees clockwise written to the
* 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
* @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
*/
static void bitRotateCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
static void bitRotateCW(final byte[] src, int srcPos, int srcStep,
final byte[] dst, int dstPos, int dstStep) {
int idx = srcPos;
int lonyb;
int hinyb;
@@ -92,41 +92,41 @@ final class IFFUtil {
long hi = 0;
for (int i = 0; i < 8; i++) {
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lonyb = src[idx] & 0xF;
hinyb = (src[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= RTABLE[i][hinyb];
idx += pSrcStep;
idx += srcStep;
}
idx = pDstPos;
idx = dstPos;
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;
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;
}
}
}
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);
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);
}
}
}
@@ -137,16 +137,16 @@ final class IFFUtil {
* Rotate bits counterclockwise.
* The IFFImageWriter uses this to convert pixel bits from chunky to planar.
*
* @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
* @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
*/
static void bitRotateCCW(final int[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
static void bitRotateCCW(final int[] src, int srcPos, @SuppressWarnings("SameParameterValue") int srcStep,
final byte[] dst, int dstPos, int dstStep) {
int idx = srcPos;
int lonyb;
int hinyb;
@@ -154,48 +154,49 @@ final class IFFUtil {
long hi = 0;
for (int i = 7; i >= 0; i--) {
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lonyb = src[idx] & 0xF;
hinyb = (src[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= RTABLE[i][hinyb];
idx += pSrcStep;
idx += srcStep;
}
idx = pDstPos;
idx = dstPos;
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);
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);
idx += pDstStep;
idx += dstStep;
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);
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);
}
/**
* Rotate bits counterclockwise.
* The IFFImageWriter uses this to convert pixel bits from chunky to planar.
*
* @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
* @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
*/
static void bitRotateCCW(final byte[] pSrc, int pSrcPos, int pSrcStep,
final byte[] pDst, int pDstPos, int pDstStep) {
int idx = pSrcPos;
@SuppressWarnings("unused")
static void bitRotateCCW(final byte[] src, int srcPos, int srcStep,
final byte[] dst, int dstPos, int dstStep) {
int idx = srcPos;
int lonyb;
int hinyb;
@@ -203,57 +204,57 @@ final class IFFUtil {
long hi = 0;
for (int i = 7; i >= 0; i--) {
lonyb = pSrc[idx] & 0xF;
hinyb = (pSrc[idx] >> 4) & 0xF;
lonyb = src[idx] & 0xF;
hinyb = (src[idx] >> 4) & 0xF;
lo |= RTABLE[i][lonyb];
hi |= IFFUtil.RTABLE[i][hinyb];
idx += pSrcStep;
idx += srcStep;
}
idx = pDstPos;
idx = dstPos;
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);
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);
idx += pDstStep;
idx += dstStep;
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);
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);
}
/**
* Converts a byte array to an int.
*
* @param pBytes a byte array of length 4
* @param bytes a byte array of length 4
* @return the bytes converted to an int
*
* @throws ArrayIndexOutOfBoundsException if length is < 4
*/
static int toInt(final byte[] pBytes) {
return (pBytes[0] & 0xff) << 24 | (pBytes[1] & 0xff) << 16
| (pBytes[2] & 0xff) << 8 | (pBytes[3] & 0xff);
static int toInt(final byte[] bytes) {
return (bytes[0] & 0xff) << 24 | (bytes[1] & 0xff) << 16
| (bytes[2] & 0xff) << 8 | (bytes[3] & 0xff);
}
/**
* Converts an int to a four letter String.
*
* @param pChunkId the chunk identifier
* @param chunkId the chunk identifier
* @return a String
*/
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))});
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))});
}
}
@@ -0,0 +1,25 @@
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.printf("warning - palette change register out of range\n");
System.err.println("warning - palette change register out of range");
System.err.printf(" change structure %d index=%d (max %d)\n", i, index, getMapSize() - 1);
System.err.printf(" ignoring it... colors might get messed up from here\n");
System.err.println(" ignoring it... colors might get messed up from here");
}
else if (index != MP_REG_IGNORE) {
updateRGB(index, ((changes[i].r & 0xff) << 16) | ((changes[i].g & 0xff) << 8) | (changes[i].b & 0xff));
@@ -72,42 +72,43 @@ final class PCHGChunk extends AbstractMultiPaletteChunk {
private int totalChanges;
private int minReg;
public PCHGChunk(int pChunkLength) {
super(IFF.CHUNK_PCHG, pChunkLength);
PCHGChunk(int chunkLength) {
super(IFF.CHUNK_PCHG, chunkLength);
}
@Override
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();
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();
byte[] data;
switch (compression) {
case PCHG_COMP_NONE:
data = new byte[chunkLength - 20];
pInput.readFully(data);
input.readFully(data);
break;
case PCHG_COMP_HUFFMAN:
// NOTE: Huffman decompression is completely untested, due to lack of source data (read: Probably broken).
int compInfoSize = pInput.readInt();
int originalDataSize = pInput.readInt();
int compInfoSize = input.readInt();
int originalDataSize = input.readInt();
short[] compTree = new short[compInfoSize / 2];
for (int i = 0; i < compTree.length; i++) {
compTree[i] = pInput.readShort();
compTree[i] = input.readShort();
}
byte[] compData = new byte[chunkLength - 20 - 8 - compInfoSize];
pInput.readFully(compData);
input.readFully(compData);
data = new byte[originalDataSize];
@@ -1,94 +1,87 @@
/*
* 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.servlet.fileupload;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
/**
* An {@code UploadedFile} implementation, based on
* <a href="http://jakarta.apache.org/commons/fileupload/">Jakarta Commons FileUpload</a>.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: UploadedFileImpl.java#1 $
*/
@Deprecated
class UploadedFileImpl implements UploadedFile {
private final FileItem item;
public UploadedFileImpl(FileItem pItem) {
if (pItem == null) {
throw new IllegalArgumentException("fileitem == null");
}
item = pItem;
}
public String getContentType() {
return item.getContentType();
}
public InputStream getInputStream() throws IOException {
return item.getInputStream();
}
public String getName() {
return item.getName();
}
public long length() {
return item.getSize();
}
public void writeTo(File pFile) throws IOException {
try {
item.write(pFile);
}
catch(RuntimeException e) {
throw e;
}
catch (IOException e) {
throw e;
}
catch (FileUploadException e) {
// We deliberately change this exception to an IOException, as it really is
throw (IOException) new IOException(e.getMessage()).initCause(e);
}
catch (Exception e) {
// Should not really happen, ever
throw new RuntimeException(e.getMessage(), e);
}
}
}
/*
* 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.plugins.iff;
import com.twelvemonkeys.io.enc.DecodeException;
import com.twelvemonkeys.io.enc.Decoder;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
/**
* Decoder implementation for Impulse FORM RGB8 RLE compression (type 4).
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: RGB8RLEDecoder.java,v 1.0 28/01/2022 haraldk Exp$
*
* @see <a href="https://wiki.amigaos.net/wiki/RGBN_and_RGB8_IFF_Image_Data">RGBN and RGB8 IFF Image Data</a>
*/
final class RGB8RLEDecoder implements Decoder {
public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
while (buffer.remaining() >= 127 * 4) {
int r = stream.read();
int g = stream.read();
int b = stream.read();
int a = stream.read();
if (a < 0) {
// Normal EOF
if (r == -1) {
break;
}
// Partial pixel read...
throw new EOFException();
}
// Get "genlock" (transparency) bit + count
boolean alpha = (a & 0x80) != 0;
int count = a & 0x7f;
a = alpha ? 0 : (byte) 0xff; // convert to full transparent/opaque;
if (count == 0) {
throw new DecodeException("Multi-byte counts not supported");
}
for (int i = 0; i < count; i++) {
buffer.put((byte) r);
buffer.put((byte) g);
buffer.put((byte) b);
buffer.put((byte) a);
}
}
return buffer.position();
}
}
@@ -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 {
protected SHAMChunk(int pChunkLength) {
super(IFF.CHUNK_SHAM, pChunkLength);
SHAMChunk(int chunkLength) {
super(IFF.CHUNK_SHAM, chunkLength);
}
@Override
@@ -0,0 +1,65 @@
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,26 +1,24 @@
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() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
final IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
final IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Standard metadata format
assertTrue(metadata.isStandardMetadataFormatSupported());
@@ -49,10 +47,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardChromaGray() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
@@ -75,10 +74,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardChromaRGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
@@ -101,16 +101,17 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardChromaPalette() {
BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1);
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));
byte[] bw = {0, (byte) 0xff};
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(4, chroma.getLength());
assertEquals(5, chroma.getLength());
IIOMetadataNode colorSpaceType = (IIOMetadataNode) chroma.getFirstChild();
assertEquals("ColorSpaceType", colorSpaceType.getNodeName());
@@ -138,14 +139,21 @@ public class IFFImageMetadataTest {
assertEquals(rgb, item0.getAttribute("blue"));
}
// TODO: BackgroundIndex == 1??
// BackgroundIndex == 1
IIOMetadataNode backgroundIndex = (IIOMetadataNode) palette.getNextSibling();
assertEquals("BackgroundIndex", backgroundIndex.getNodeName());
assertEquals("1", backgroundIndex.getAttribute("value"));
// No more elements
assertNull(backgroundIndex.getNextSibling());
}
@Test
public void testStandardCompressionRLE() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode compression = metadata.getStandardCompressionNode();
assertNotNull(compression);
@@ -164,19 +172,21 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardCompressionNone() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
}
@Test
public void testStandardDataILBM_Gray() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -199,10 +209,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataILBM_RGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -225,10 +236,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataILBM_RGBA() {
BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -251,12 +263,13 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataILBM_Palette() {
public void testStandardDataILBM_Palette() throws IIOException {
for (int i = 1; i <= 8; i++) {
BMHDChunk header = new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(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(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, rgb.length, rgb, rgb, rgb, 0), null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -280,10 +293,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataPBM_Gray() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -306,10 +320,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDataPBM_RGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_PBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
@@ -333,40 +348,57 @@ public class IFFImageMetadataTest {
@Test
public void testStandardDimensionNoViewport() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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;
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNull(dimension);
}
@Test
public void testStandardDimensionNormal() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, new CAMGChunk(4), Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName());
assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
// 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());
assertNull(pixelAspectRatio.getNextSibling()); // No more children
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
}
@Test
public void testStandardDimensionHires() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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;
CAMGChunk viewPort = new CAMGChunk(4);
viewPort.camg = 0x8000;
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader)
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
@@ -381,13 +413,19 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDimensionInterlaced() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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;
CAMGChunk viewPort = new CAMGChunk(4);
viewPort.camg = 0x4;
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader)
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
@@ -402,13 +440,14 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDimensionHiresInterlaced() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
public void testStandardDimensionHiresInterlaced() throws IIOException {
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(IFF.TYPE_ILBM, header, null, viewPort, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
@@ -423,10 +462,11 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardDocument() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode document = metadata.getStandardDocumentNode();
assertNotNull(document);
@@ -441,13 +481,15 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardText() {
BMHDChunk header = new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
public void testStandardText() throws IIOException {
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
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);
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)));
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode text = metadata.getStandardTextNode();
assertNotNull(text);
@@ -457,26 +499,28 @@ public class IFFImageMetadataTest {
for (int i = 0; i < texts.length; i++) {
IIOMetadataNode textEntry = (IIOMetadataNode) text.item(i);
assertEquals("TextEntry", textEntry.getNodeName());
assertEquals(IFFUtil.toChunkStr(meta.get(i).chunkId), textEntry.getAttribute("keyword"));
assertEquals(IFFUtil.toChunkStr(chunks[i]), textEntry.getAttribute("keyword"));
assertEquals(texts[i], textEntry.getAttribute("value"));
}
}
@Test
public void testStandardTransparencyRGB() {
BMHDChunk header = new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNull(transparency); // No transparency, just defaults
}
@Test
public void testStandardTransparencyRGBA() {
BMHDChunk header = new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0);
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));
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, null, null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
@@ -491,11 +535,12 @@ public class IFFImageMetadataTest {
}
@Test
public void testStandardTransparencyPalette() {
BMHDChunk header = new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1);
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));
byte[] bw = {0, (byte) 0xff};
IFFImageMetadata metadata = new IFFImageMetadata(IFF.TYPE_ILBM, header, new IndexColorModel(header.bitplanes, bw.length, bw, bw, bw, header.transparentIndex), null, Collections.<GenericChunk>emptyList());
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
@@ -508,4 +553,131 @@ 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,7 +88,16 @@ 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))
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))
);
}
@@ -99,7 +108,7 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
@Override
protected List<String> getSuffixes() {
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm");
return Arrays.asList("iff", "ilbm", "ham", "ham8", "lbm", "rgb8", "deep");
}
@Override
@@ -134,9 +143,14 @@ public class IFFImageReaderTest extends ImageReaderAbstractTest<IFFImageReader>
for (int i = 0; i < 32; i++) {
// Make sure the color model is really EHB
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);
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);
}
}
}
}
@@ -0,0 +1,114 @@
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.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</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.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
@@ -35,6 +35,7 @@ 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;
@@ -158,21 +159,25 @@ class JPEGImage10Metadata extends AbstractMetadata {
case JPEG.DHT:
HuffmanTable huffmanTable = (HuffmanTable) segment;
IIOMetadataNode dht = new IIOMetadataNode("dht");
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);
}
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()));
}
}
markerSequence.appendChild(dht);
break;
case JPEG.DQT:
@@ -270,6 +275,18 @@ 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");
@@ -1,345 +0,0 @@
/*
* 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);
}
}
}
}
}
@@ -104,6 +104,7 @@ 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;
@@ -335,7 +336,7 @@ 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 (bogusAdobeDCT
else if (FORCE_RASTER_CONVERSION || bogusAdobeDCT
|| profile != null && !ColorProfiles.isCS_sRGB(profile)
|| (long) sof.lines * sof.samplesPerLine > Integer.MAX_VALUE
|| delegateCSTypeMismatch(jfif, adobeDCT, sof, sourceCSType)) {
@@ -31,6 +31,7 @@
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;
@@ -42,6 +43,7 @@ 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;
@@ -50,14 +52,13 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
* JPEGImage10MetadataCleanerTest.
* JPEGImage10MetadataTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: JPEGImage10MetadataCleanerTest.java,v 1.0 08/08/16 harald.kuhr Exp$
* @author last modified by $Author: haraldk$
* @version $Id: JPEGImage10MetadataTest.java,v 1.0 12/01/2022 haraldk Exp$
*/
public class JPEGImage10MetadataCleanerTest {
public class JPEGImage10MetadataTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
@@ -69,14 +70,14 @@ public class JPEGImage10MetadataCleanerTest {
return lookupProviderByName(IIORegistry.getDefaultInstance(), "com.sun.imageio.plugins.jpeg.JPEGImageReaderSpi", ImageReaderSpi.class);
}
// Unit/regression test for #276
// Unit/regression test for #276, #559
@Test
public void cleanMetadataMoreThan4DHTSegments() throws Exception {
public void splitMoreThan4DHTSegments() throws Exception {
List<String> testData = Arrays.asList("/jpeg/5dhtsegments.jpg", "/jpeg/6dhtsegments.jpg");
for (String data : testData) {
try (ImageInputStream origInput = ImageIO.createImageInputStream(getClass().getResource(data));
ImageInputStream input = ImageIO.createImageInputStream(getClass().getResource(data))) {
try (ImageInputStream origInput = ImageIO.createImageInputStream(getClassResource(data));
ImageInputStream input = ImageIO.createImageInputStream(getClassResource(data))) {
ImageReader origReader = SPI.delegateProvider.createReaderInstance();
origReader.setInput(origInput);
@@ -87,9 +88,7 @@ public class JPEGImage10MetadataCleanerTest {
IIOMetadata original = origReader.getImageMetadata(0);
IIOMetadataNode origTree = (IIOMetadataNode) original.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
JPEGImage10MetadataCleaner cleaner = new JPEGImage10MetadataCleaner((JPEGImageReader) reader);
IIOMetadata cleaned = cleaner.cleanMetadata(origReader.getImageMetadata(0));
JPEGImage10Metadata cleaned = (JPEGImage10Metadata) reader.getImageMetadata(0);
IIOMetadataNode cleanTree = (IIOMetadataNode) cleaned.getAsTree(JPEGImage10Metadata.JAVAX_IMAGEIO_JPEG_IMAGE_1_0);
NodeList origDHT = origTree.getElementsByTagName("dht");
@@ -124,4 +123,8 @@ public class JPEGImage10MetadataCleanerTest {
}
}
}
private URL getClassResource(String name) {
return getClass().getResource(name);
}
}
@@ -30,34 +30,15 @@
package com.twelvemonkeys.imageio.plugins.jpeg;
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 com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import java.awt.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
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;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.junit.Test;
import org.w3c.dom.NodeList;
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.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
@@ -66,14 +47,22 @@ 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.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
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;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* JPEGImageWriterTest
@@ -161,6 +150,20 @@ 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.0</version>
<version>3.8.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -80,7 +80,7 @@ public final class TIFFReader extends MetadataReader {
private final Set<Long> parsedIFDs = new TreeSet<>();
private long length;
private long inputLength;
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));
}
length = input.length();
inputLength = input.length();
return readLinkedIFDs(input);
}
@@ -140,7 +140,7 @@ public final class TIFFReader extends MetadataReader {
// Read linked IFDs
while (ifdOffset != 0) {
try {
if ((length > 0 && ifdOffset >= length) || !parsedIFDs.add(ifdOffset)) {
if ((inputLength > 0 && ifdOffset >= inputLength) || !isValidOffset(input, ifdOffset) || !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 ((length > 0 && ifdOffset >= length) || !parsedIFDs.add(ifdOffset)) {
if ((inputLength > 0 && ifdOffset >= inputLength) || !isValidOffset(input, ifdOffset) || !parsedIFDs.add(ifdOffset)) {
// TODO: Issue warning
if (DEBUG) {
System.err.println("Bad IFD offset: " + ifdOffset);
@@ -330,20 +330,14 @@ 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
// 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);
}
value = readValueAt(pInput, valueOffset, valueLength, type, count);
}
return new TIFFEntry(tagId, type, value);
@@ -365,18 +359,52 @@ public final class TIFFReader extends MetadataReader {
return (int) count;
}
private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
long pos = pInput.getStreamPosition();
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...
try {
pInput.seek(pOffset);
return readValue(pInput, pType, pCount, longOffsets);
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"));
}
}
catch (EOFException e) {
// TODO: Add warning listener API and report problem to client code
if (DEBUG) {
System.err.println(e);
}
return e;
}
finally {
pInput.seek(pos);
input.seek(pos);
}
}
@@ -292,7 +292,26 @@ public final class TIFFWriter extends MetadataWriter {
private int getCount(final Entry entry) {
Object value = entry.getValue();
return value instanceof String ? ((String) value).getBytes(StandardCharsets.UTF_8).length + 1 : entry.valueCount();
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;
}
private void writeValueInline(final Object value, final short type, final ImageOutputStream stream) throws IOException {
@@ -410,7 +429,9 @@ public final class TIFFWriter extends MetadataWriter {
break;
}
case TIFF.TYPE_ASCII:
writeStrings(stream, (String[]) value);
break;
default:
throw new IllegalArgumentException("Unsupported TIFF type: " + type);
}
@@ -423,9 +444,7 @@ public final class TIFFWriter extends MetadataWriter {
stream.writeByte(((Number) value).intValue());
break;
case TIFF.TYPE_ASCII:
byte[] bytes = ((String) value).getBytes(StandardCharsets.UTF_8);
stream.write(bytes);
stream.write(0);
writeStrings(stream, (String) value);
break;
case TIFF.TYPE_SHORT:
case TIFF.TYPE_SSHORT:
@@ -462,6 +481,13 @@ 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();
@@ -47,8 +47,7 @@ import java.util.ArrayList;
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.*;
/**
* TIFFWriterTest
@@ -84,11 +83,13 @@ 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();
assertEquals(output.size(), imageStream.getStreamPosition());
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
createWriter().write(directory, imageStream);
imageStream.flush();
assertEquals(output.size(), imageStream.getStreamPosition());
}
byte[] data = output.toByteArray();
@@ -129,14 +130,15 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
imageStream.setByteOrder(ByteOrder.BIG_ENDIAN); // BE = Motorola
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
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();
@@ -164,14 +166,15 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new AbstractDirectory(entries) {};
ByteArrayOutputStream output = new FastByteArrayOutputStream(1024);
ImageOutputStream imageStream = ImageIO.createImageOutputStream(output);
imageStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); // LE = Intel
try (ImageOutputStream imageStream = ImageIO.createImageOutputStream(output)) {
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();
@@ -203,12 +206,13 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory directory = new IFD(Collections.<Entry>singletonList(subIFD));
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());
}
Directory read = new TIFFReader().read(new ByteArrayImageInputStream(output.toByteArray()));
@@ -222,14 +226,10 @@ public class TIFFWriterTest extends MetadataWriterAbstractTest {
Directory original = createReader().read(getDataAsIIS());
ByteArrayOutputStream output = new FastByteArrayOutputStream(256);
ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output);
try {
try (ImageOutputStream imageOutput = ImageIO.createImageOutputStream(output)) {
createWriter().write(original, imageOutput);
}
finally {
imageOutput.close();
}
Directory read = createReader().read(new ByteArrayImageInputStream(output.toByteArray()));
@@ -254,6 +254,28 @@ 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");
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
@@ -140,10 +140,9 @@ public final class PCXImageReader extends ImageReaderBase {
if (palette != null) {
return ImageTypeSpecifiers.createFromIndexColorModel(palette);
}
else {
// PCX Gray has 1 channel and no palette
return ImageTypeSpecifiers.createGrayscale(8, DataBuffer.TYPE_BYTE);
}
// 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
@@ -64,17 +64,10 @@ 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
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/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/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)
@@ -149,11 +142,12 @@ public class PCXImageReaderTest extends ImageReaderAbstractTest<PCXImageReader>
}
}
@Override
@Test
public void testReadWithSourceRegionParamEqualImage() throws IOException {
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);
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);
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
@@ -35,6 +35,7 @@ import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Locale;
public final class PAMImageReaderSpi extends ImageReaderSpiBase {
@@ -53,13 +54,16 @@ public final class PAMImageReaderSpi extends ImageReaderSpiBase {
}
ImageInputStream stream = (ImageInputStream) source;
ByteOrder order = stream.getByteOrder();
stream.mark();
try {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
return stream.readShort() == PNM.PAM && stream.readInt() != PNM.XV_THUMBNAIL_MAGIC;
}
finally {
stream.reset();
stream.setByteOrder(order);
}
}
@@ -30,8 +30,10 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import javax.imageio.IIOException;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -70,45 +72,74 @@ final class PNMHeaderParser extends HeaderParser {
List<String> comments = new ArrayList<>();
StringBuilder tokenBuffer = new StringBuilder();
while (width == 0 || height == 0 || maxSample == 0) {
String line = input.readLine();
tokenBuffer.delete(0, tokenBuffer.length());
if (line == null) {
throw new IIOException("Unexpeced end of stream");
}
while (tokenBuffer.length() < 16) { // Limit reads if we should read across into the binary part...
byte read = input.readByte();
int commentStart = line.indexOf('#');
if (commentStart >= 0) {
String comment = line.substring(commentStart + 1).trim();
if (!comment.isEmpty()) {
comments.add(comment);
if (read == '#') {
// Read rest of the line as comment
String comment = readLineUTF8(input).trim();
if (!comment.isEmpty()) {
comments.add(comment);
}
break;
}
else if (Character.isWhitespace((char) read)) {
if (tokenBuffer.length() > 0) {
break;
}
}
else {
tokenBuffer.append((char) read);
}
line = line.substring(0, commentStart);
}
line = line.trim();
String token = tokenBuffer.toString().trim();
if (!line.isEmpty()) {
if (!token.isEmpty()) {
// We have tokens...
String[] tokens = line.split("\\s");
for (String token : tokens) {
if (width == 0) {
width = Integer.parseInt(token);
}
else if (height == 0) {
height = Integer.parseInt(token);
}
else if (maxSample == 0) {
maxSample = Integer.parseInt(token);
}
else {
throw new IIOException("Unknown PNM token: " + token);
}
if (width == 0) {
width = Integer.parseInt(token);
}
else if (height == 0) {
height = Integer.parseInt(token);
}
else {
maxSample = Integer.parseInt(token);
}
}
}
return new PNMHeader(fileType, tupleType, width, height, tupleType.getSamplesPerPixel(), maxSample, comments);
}
// Similar to DataInput.readLine, except it uses UTF8 encoding
private static String readLineUTF8(final ImageInputStream input) throws IOException {
ByteArrayOutputStream buffer = new FastByteArrayOutputStream(128);
int value;
do {
switch (value = input.read()) {
case '\r':
// Check for CR + LF pattern and skip, otherwise fall through
if (input.read() != '\n') {
input.seek(input.getStreamPosition() - 1);
}
case '\n':
case -1:
value = -1;
break;
default:
buffer.write(value);
}
} while (value != -1);
return buffer.toString("UTF8");
}
}
@@ -49,12 +49,14 @@ import java.io.DataInput;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import static com.twelvemonkeys.imageio.util.RasterUtils.asByteRaster;
public final class PNMImageReader extends ImageReaderBase {
// TODO: Allow reading unknown tuple types as Raster!
@@ -73,6 +75,7 @@ public final class PNMImageReader extends ImageReaderBase {
private void readHeader() throws IOException {
if (header == null) {
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
header = HeaderParser.parse(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition());
@@ -122,27 +125,19 @@ public final class PNMImageReader extends ImageReaderBase {
case GRAYSCALE_ALPHA:
case BLACKANDWHITE:
case GRAYSCALE:
// PGM: Linear or non-linear gray?
ColorSpace gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
return ImageTypeSpecifiers.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
}
if (header.getMaxSample() <= PNM.MAX_VAL_16BIT) {
return hasAlpha ? ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType, false)
: ImageTypeSpecifiers.createGrayscale(bitsPerSample, transferType);
}
// PGM: Linear or non-linear gray?
ColorSpace gray = ColorSpace.getInstance(ColorSpace.CS_GRAY);
return ImageTypeSpecifiers.createInterleaved(gray, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
case RGB:
case RGB_ALPHA:
// Using sRGB seems sufficient for PPM, as it is very close to ITU-R Recommendation BT.709 (same gamut and white point CIE D65)
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
return ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
}
return ImageTypeSpecifiers.createInterleaved(sRGB, createBandOffsets(samplesPerPixel), transferType, hasAlpha, false);
case CMYK:
@@ -185,10 +180,9 @@ public final class PNMImageReader extends ImageReaderBase {
case RGB_ALPHA:
if (header.getTransferType() == DataBuffer.TYPE_BYTE) {
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR));
// TODO: Why does ColorConvertOp choke on these (Ok, because it misinterprets the alpha channel for a color component, but how do we make it work)?
// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB));
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE));
// specifiers.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE));
}
break;
@@ -215,11 +209,6 @@ public final class PNMImageReader extends ImageReaderBase {
Rectangle destRegion = new Rectangle();
computeRegions(param, width, height, destination, srcRegion, destRegion);
WritableRaster destRaster = clipToRect(destination.getRaster(), destRegion, param != null
? param.getDestinationBands()
: null);
checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
WritableRaster rowRaster = rawType.createBufferedImage(width, 1).getRaster();
// Clip to source region
Raster clippedRow = clipRowToRect(rowRaster, srcRegion,
@@ -247,10 +236,10 @@ public final class PNMImageReader extends ImageReaderBase {
throw new AssertionError("Unsupported transfer type: " + transferType);
}
ColorConvertOp colorConvert = null;
if (!destination.getColorModel().isCompatibleRaster(rowRaster)) {
colorConvert = new ColorConvertOp(rawType.getColorModel().getColorSpace(), destination.getColorModel().getColorSpace(), null);
}
WritableRaster destRaster = transferType == DataBuffer.TYPE_BYTE ? asByteRaster(destination.getRaster())
: destination.getRaster();
destRaster = clipToRect(destRaster, destRegion, param != null ? param.getDestinationBands() : null);
checkReadParamBandSettings(param, rawType.getNumBands(), destRaster.getNumBands());
int xSub = param == null ? 1 : param.getSourceXSubsampling();
int ySub = param == null ? 1 : param.getSourceYSubsampling();
@@ -262,7 +251,7 @@ public final class PNMImageReader extends ImageReaderBase {
for (int y = 0; y < height; y++) {
switch (transferType) {
case DataBuffer.TYPE_BYTE:
readRowByte(destRaster, clippedRow, colorConvert, rowDataByte, header.getBitsPerSample(), samplesPerPixel, input, y, srcRegion, xSub, ySub);
readRowByte(destRaster, clippedRow, rowDataByte, header.getBitsPerSample(), samplesPerPixel, input, y, srcRegion, xSub, ySub);
break;
case DataBuffer.TYPE_USHORT:
readRowUShort(destRaster, clippedRow, rowDataUShort, samplesPerPixel, input, y, srcRegion, xSub, ySub);
@@ -287,6 +276,10 @@ public final class PNMImageReader extends ImageReaderBase {
}
}
if (destination.isAlphaPremultiplied()) {
rawType.getColorModel().coerceData(destRaster, true);
}
processImageComplete();
return destination;
@@ -338,7 +331,6 @@ public final class PNMImageReader extends ImageReaderBase {
private void readRowByte(final WritableRaster destRaster,
Raster rowRaster,
final ColorConvertOp colorConvert,
final byte[] rowDataByte,
final int bitsPerSample, final int samplesPerPixel,
final DataInput input, final int y,
@@ -357,12 +349,7 @@ public final class PNMImageReader extends ImageReaderBase {
normalize(rowDataByte, 0, rowDataByte.length / xSub);
int destY = (y - srcRegion.y) / ySub;
if (colorConvert != null) {
colorConvert.filter(rowRaster, destRaster.createWritableChild(0, destY, rowRaster.getWidth(), 1, 0, 0, null));
}
else {
destRaster.setDataElements(0, destY, rowRaster);
}
destRaster.setDataElements(0, destY, rowRaster);
}
private void readRowUShort(final WritableRaster destRaster,
@@ -57,7 +57,7 @@ public final class PNMImageReaderSpi extends ImageReaderSpiBase {
stream.mark();
try {
short magic = stream.readShort();
short magic = (short) ((stream.readByte() & 0xFF) << 8 | (stream.readByte() & 0xFF));
switch (magic) {
case PNM.PBM_PLAIN:
@@ -58,17 +58,13 @@ final class Plain16BitDecoder extends InputStream {
}
// Each number is one byte. Skip whitespace.
if (currentLine == null || !currentLine.hasNext()) {
while (currentLine == null || !currentLine.hasNext()) {
String line = reader.readLine();
if (line == null) {
return -1;
}
currentLine = new StringTokenIterator(line);
if (!currentLine.hasNext()) {
return -1;
}
}
int next = Integer.parseInt(currentLine.next()) & 0xffff;
@@ -50,17 +50,13 @@ final class Plain8BitDecoder extends InputStream {
@Override
public int read() throws IOException {
// Each number is one byte. Skip whitespace.
if (currentLine == null || !currentLine.hasNext()) {
while (currentLine == null || !currentLine.hasNext()) {
String line = reader.readLine();
if (line == null) {
return -1;
}
currentLine = new StringTokenIterator(line);
if (!currentLine.hasNext()) {
return -1;
}
}
return Integer.parseInt(currentLine.next()) & 0xff;
@@ -33,7 +33,9 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
@@ -54,7 +56,7 @@ public class PAMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/pam/lena.pam"), new Dimension(128, 128)), // P7 RGB
new TestData(getClassLoaderResource("/pam/snail2.pam"), new Dimension(256, 256)), // P7 RGB
new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension(4, 2)) // P7 RGB_ALPHA
);
}
@@ -102,6 +104,33 @@ public class PAMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
assertImageDataEquals("Images differ from reference", expected, reader.read(0));
}
@Test
public void testTypes() throws IOException {
ImageReader reader = createReader();
TestData data = new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension(4, 2));
reader.setInput(data.getInputStream());
int[] types = new int[] {BufferedImage.TYPE_INT_ARGB, BufferedImage.TYPE_4BYTE_ABGR, BufferedImage.TYPE_INT_ARGB_PRE};
for (int type : types) {
BufferedImage expected = new BufferedImage(4, 2, type);
expected.setRGB(0, 0, new Color(0, 0, 255).getRGB());
expected.setRGB(1, 0, new Color(0, 255, 0).getRGB());
expected.setRGB(2, 0, new Color(255, 0, 0).getRGB());
expected.setRGB(3, 0, new Color(255, 255, 255).getRGB());
expected.setRGB(0, 1, new Color(0, 0, 255, 127).getRGB());
expected.setRGB(1, 1, new Color(0, 255, 0, 127).getRGB());
expected.setRGB(2, 1, new Color(255, 0, 0, 127).getRGB());
expected.setRGB(3, 1, new Color(255, 255, 255, 127).getRGB());
ImageReadParam param = reader.getDefaultReadParam();
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(type));
assertImageDataEquals("Images differ from reference for type: " + type, expected, reader.read(0, param));
}
}
@Test
public void testXVThumbNotIncorrectlyRecognizedAsPAM() throws IOException {
assertTrue("Should recognize PAM format", provider.canDecodeInput(new TestData(getClassLoaderResource("/pam/rgba.pam"), new Dimension()).getInputStream())); // Sanity
@@ -33,7 +33,9 @@ import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import org.junit.Test;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.image.BufferedImage;
@@ -50,10 +52,12 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
@Override
protected List<TestData> getTestData() {
return Arrays.asList(
new TestData(getClassLoaderResource("/ppm/lena.ppm"), new Dimension(128, 128)), // P6 (PPM RAW)
new TestData(getClassLoaderResource("/ppm/snail2.pnm"), new Dimension(256, 256)), // P6 (PPM RAW)
new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2)), // P3 (PPM PLAIN)
new TestData(getClassLoaderResource("/pbm/j.pbm"), new Dimension(6, 10)), // P1 (PBM PLAIN)
new TestData(getClassLoaderResource("/pbm/circle2.pnm"), new Dimension(200, 200)), // P4 (PBM RAW)
new TestData(getClassLoaderResource("/pgm/feep.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN)
new TestData(getClassLoaderResource("/pgm/rays2.pnm"), new Dimension(200, 200)), // P4 (PGM RAW)
new TestData(getClassLoaderResource("/pgm/feep16.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN, 16 bits/sample)
new TestData(getClassLoaderResource("/pgm/house.l.pgm"), new Dimension(367, 241)), // P5 (PGM RAW)
new TestData(getClassLoaderResource("/ppm/lighthouse_rgb48.ppm"), new Dimension(768, 512)), // P6 (PPM RAW, 16 bits/sample)
@@ -64,10 +68,12 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
@Override
protected List<TestData> getTestDataForAffineTransformOpCompatibility() {
return Arrays.asList(
new TestData(getClassLoaderResource("/ppm/lena.ppm"), new Dimension(128, 128)), // P6 (PPM RAW)
new TestData(getClassLoaderResource("/ppm/snail2.pnm"), new Dimension(256, 256)), // P6 (PPM RAW)
new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2)), // P3 (PPM PLAIN)
new TestData(getClassLoaderResource("/pbm/j.pbm"), new Dimension(6, 10)), // P1 (PBM PLAIN)
new TestData(getClassLoaderResource("/pbm/circle2.pnm"), new Dimension(200, 200)), // P4 (PBM RAW)
new TestData(getClassLoaderResource("/pgm/feep.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN)
new TestData(getClassLoaderResource("/pgm/rays2.pnm"), new Dimension(200, 200)), // P4 (PGM RAW)
new TestData(getClassLoaderResource("/pgm/feep16.pgm"), new Dimension(24, 7)), // P2 (PGM PLAIN, 16 bits/sample)
new TestData(getClassLoaderResource("/pgm/house.l.pgm"), new Dimension(367, 241)), // P5 (PGM RAW)
new TestData(getClassLoaderResource("/ppm/lighthouse_rgb48.ppm"), new Dimension(768, 512)) // P6 (PPM RAW, 16 bits/sample)
@@ -116,4 +122,29 @@ public class PNMImageReaderTest extends ImageReaderAbstractTest<PNMImageReader>
assertImageDataEquals("Images differ from reference", expected, reader.read(0));
}
@Test
public void testTypes() throws IOException {
ImageReader reader = createReader();
TestData data = new TestData(getClassLoaderResource("/ppm/colors.ppm"), new Dimension(3, 2));
reader.setInput(data.getInputStream());
int[] types = new int[] {BufferedImage.TYPE_INT_RGB, BufferedImage.TYPE_INT_BGR, BufferedImage.TYPE_3BYTE_BGR};
for (int type : types) {
BufferedImage expected = new BufferedImage(3, 2, type);
expected.setRGB(0, 0, new Color(255, 0, 0).getRGB());
expected.setRGB(1, 0, new Color(0, 255, 0).getRGB());
expected.setRGB(2, 0, new Color(0, 0, 255).getRGB());
expected.setRGB(0, 1, new Color(255, 255, 0).getRGB());
expected.setRGB(1, 1, new Color(255, 255, 255).getRGB());
expected.setRGB(2, 1, new Color(0, 0, 0).getRGB());
ImageReadParam param = reader.getDefaultReadParam();
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(type));
assertImageDataEquals("Images differ from reference for type: " + type, expected, reader.read(0, param));
}
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.8.0</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -708,4 +708,5 @@ interface PSD extends com.twelvemonkeys.imageio.metadata.psd.PSD {
int PxSD = 'P' << 24 | 'x' << 16 | 'S' << 8 | 'D';
int luni = 'l' << 24 | 'u' << 16 | 'n' << 8 | 'i';
int lyid = 'l' << 24 | 'y' << 16 | 'i' << 8 | 'd';
int lsct = 'l' << 24 | 's' << 16 | 'c' << 8 | 't';
}
@@ -946,14 +946,8 @@ public final class PSDImageReader extends ImageReaderBase {
metadata.layerCount = layerCount;
if (pParseData && metadata.layerInfo == null) {
PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layerCount)];
for (int i = 0; i < layerInfos.length; i++) {
layerInfos[i] = new PSDLayerInfo(header.largeFormat, imageInput);
}
metadata.layerInfo = Arrays.asList(layerInfos);
metadata.layerInfo = readLayerInfo(Math.abs(layerCount));
metadata.layersStart = imageInput.getStreamPosition();
}
long read = imageInput.getStreamPosition() - pos;
@@ -965,7 +959,6 @@ public final class PSDImageReader extends ImageReaderBase {
metadata.layerInfo = Collections.emptyList();
}
// Global LayerMaskInfo (18 bytes or more..?)
// 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad)
long globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB!
@@ -996,6 +989,36 @@ public final class PSDImageReader extends ImageReaderBase {
}
}
private List<PSDLayerInfo> readLayerInfo(int layerCount) throws IOException {
PSDLayerInfo[] layerInfos = new PSDLayerInfo[layerCount];
Stack<List<PSDLayerInfo>> groupStack = new Stack<>();
List<PSDLayerInfo> currentGroup = Collections.emptyList();
for (int i = 0; i < layerInfos.length; i++) {
PSDLayerInfo layerInfo = new PSDLayerInfo(header.largeFormat, imageInput);
layerInfos[i] = layerInfo;
if (layerInfo.isDivider) {
groupStack.push(currentGroup);
currentGroup = new ArrayList<>();
}
else if (layerInfo.isGroup) {
for (PSDLayerInfo info : currentGroup) {
info.groupId = layerInfo.getLayerId();
}
currentGroup = groupStack.pop();
}
if (currentGroup != Collections.EMPTY_LIST) {
currentGroup.add(layerInfo);
}
}
return Arrays.asList(layerInfos);
}
private BufferedImage readLayerData(final int layerIndex, final ImageReadParam param) throws IOException {
final int width = getLayerWidth(layerIndex);
final int height = getLayerHeight(layerIndex);
@@ -1010,7 +1033,7 @@ public final class PSDImageReader extends ImageReaderBase {
// Even if raw/imageType has no alpha, the layers may still have alpha...
ImageTypeSpecifier imageType = getRawImageTypeForLayer(layerIndex);
BufferedImage layer = getDestination(param, getImageTypes(layerIndex + 1), Math.max(1, width), Math.max(1, height));
BufferedImage layer = getDestination(param, getImageTypes(layerIndex + 1), width, height);
imageInput.seek(findLayerStartPos(layerIndex));
@@ -52,10 +52,15 @@ final class PSDLayerInfo {
final PSDLayerBlendMode blendMode;
final PSDLayerMaskData layerMaskData;
final PSDChannelSourceDestinationRange[] ranges;
private final String layerName;
private String unicodeLayerName;
private int layerId;
private final String layerName;
private final String unicodeLayerName;
private final int layerId;
int groupId = -1;
final boolean isGroup;
final boolean isDivider;
PSDLayerInfo(final boolean largeFormat, final ImageInputStream pInput) throws IOException {
top = pInput.readInt();
@@ -64,7 +69,7 @@ final class PSDLayerInfo {
right = pInput.readInt();
int channels = pInput.readUnsignedShort();
channelInfo = new PSDChannelInfo[channels];
for (int i = 0; i < channels; i++) {
short channelId = pInput.readShort();
@@ -108,6 +113,10 @@ final class PSDLayerInfo {
layerNameSize += skip;
}
int layerId = -1;
String unicodeLayerName = null;
int sectionDividerSettingType = 0;
// Parse "Additional layer data"
long additionalLayerInfoStart = pInput.getStreamPosition();
long expectedEnd = additionalLayerInfoStart + extraDataSize - layerMaskDataSize - 4 - layerBlendingDataSize - 4 - layerNameSize;
@@ -142,21 +151,37 @@ final class PSDLayerInfo {
if (resourceLength != 4) {
throw new IIOException(String.format("Expected layerId length == 4: %d", resourceLength));
}
layerId = pInput.readInt();
break;
case PSD.lsct:
if (resourceLength < 4) {
throw new IIOException(String.format("Expected sectionDividerSetting length >= 4: %d", resourceLength));
}
sectionDividerSettingType = pInput.readInt();
// length >= 12: '8BIM' + 4byte blend mode, length >= 16, 4byte sub type
pInput.skipBytes(resourceLength - 4);
break;
default:
// TODO: Parse more data...
pInput.skipBytes(resourceLength);
break;
}
// Re-align in case we got the length incorrect
// Re-align in case we got the resource length incorrect
if (pInput.getStreamPosition() != resourceStart + resourceLength) {
pInput.seek(resourceStart + resourceLength);
}
}
this.layerId = layerId;
this.unicodeLayerName = unicodeLayerName;
isGroup = sectionDividerSettingType == 1 || sectionDividerSettingType == 2;
isDivider = sectionDividerSettingType == 3;
// Re-align in case we got the length incorrect
if (pInput.getStreamPosition() != expectedEnd) {
pInput.seek(expectedEnd);
@@ -187,6 +212,17 @@ final class PSDLayerInfo {
}
builder.append(", ranges: ").append(Arrays.toString(ranges));
builder.append(", layer name: \"").append(getLayerName()).append("\"");
builder.append(", layer id: ").append(getLayerId());
if (groupId != -1) {
builder.append(", group id: ").append(groupId);
}
if (isGroup) {
builder.append(", isGroup");
}
if (isDivider) {
builder.append(", isDivider");
}
builder.append("]");
return builder.toString();

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