Compare commits

...

28 Commits

Author SHA1 Message Date
dependabot[bot] c27fb6c7f5 Bump github/codeql-action from 4.35.3 to 4.35.4 in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.3 to 4.35.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/e46ed2cbd01164d986452f91f178727624ae40d7...68bde559dea0fdcac2102bfdf6230c5f70eb485e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-14 13:52:48 +02:00
dependabot[bot] 9a3269cae9 Bump github/codeql-action from 4.35.2 to 4.35.3 in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.2 to 4.35.3.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/95e58e9a2cdfd71adc6e0353d5c52f41a045d225...e46ed2cbd01164d986452f91f178727624ae40d7)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-08 10:58:48 +02:00
Harald Kuhr d9eb7f6ad6 Better name output :-) 2026-05-05 10:06:41 +02:00
Harald Kuhr d0d0b1afa3 Fixed nested tests 2026-05-05 08:55:09 +02:00
Harald Kuhr 132105b97a Add @Nested to inner test-classes 2026-05-05 08:55:09 +02:00
dependabot[bot] ae7e3d8c9b Bump junit.jupiter.version from 5.14.3 to 5.14.4
Bumps `junit.jupiter.version` from 5.14.3 to 5.14.4.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.14.3 to 5.14.4
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.14.3...r5.14.4)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.14.3 to 5.14.4
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.14.3...r5.14.4)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.14.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.14.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 14:59:29 +02:00
dependabot[bot] 43ddc84986 Bump commons-io:commons-io from 2.21.0 to 2.22.0
Bumps commons-io:commons-io from 2.21.0 to 2.22.0.

---
updated-dependencies:
- dependency-name: commons-io:commons-io
  dependency-version: 2.22.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 16:22:11 +02:00
dependabot[bot] e682696fac Bump github/codeql-action from 4.35.1 to 4.35.2 in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.35.1 to 4.35.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/c10b8064de6f491fea524254123dbe5e09572f13...95e58e9a2cdfd71adc6e0353d5c52f41a045d225)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-22 13:18:18 +02:00
dependabot[bot] 5f190ea57d Bump actions/upload-artifact from 7.0.0 to 7.0.1 in /.github/workflows
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/bbbca2ddaa5d8feaa63e36b76fdaad77386f024f...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-17 11:00:08 +02:00
dependabot[bot] 3ca37e4b8c Bump mikepenz/action-junit-report in /.github/workflows
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 6.3.1 to 6.4.0.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/49b2ca06f62aa7ef83ae6769a2179271e160d8e4...bccf2e31636835cf0874589931c4116687171386)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 14:25:27 +02:00
dependabot[bot] a30a032a9c Bump github/codeql-action from 4.32.6 to 4.35.1 in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.6 to 4.35.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/0d579ffd059c29b07949a3cce3983f0780820c98...c10b8064de6f491fea524254123dbe5e09572f13)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.35.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-06 12:53:18 +02:00
dependabot[bot] 77ebba4f7d Bump github/codeql-action from 4.32.4 to 4.32.6 in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.4 to 4.32.6.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...0d579ffd059c29b07949a3cce3983f0780820c98)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-19 15:33:58 +01:00
Harald Kuhr 456749ded8 Using new sequence support in DDSImageWriter
+ some minor bonus clean-up
2026-03-13 14:46:19 +01:00
Harald Kuhr 47a26651b4 Using new sequence support in exising writers. 2026-03-13 14:46:19 +01:00
Harald Kuhr 10183ef830 New class for simpler sequence write support. 2026-03-13 14:46:19 +01:00
Harald Kuhr 263fb75d1d DDS cleanup (#1262)
* Refactorings and code clean-up
* Major rework/standardization:
 * DDSEncoderType, DX10DXGIFormat merged with DDSType for a single way to describe a DDS format
 * Added constants for DXGI formats
 * DDSImageWriteParam is now mutable and supports standard way of setting compression type
 * DDSImageMetadata now supports more of the format
 Performance:
 * DDSReader now use seek() to jump to correct mipmap instead of reading all bytes
 * DDSImageWriter now uses getTile(0, 0) instead of getData() for better performance
* Fix JavaDoc 🎉
* Sonar issues + roll back accidental check-in
* More clean-up: Removed optional flags from param, header size validation, metadata now reports compresion as lossy
* More clean-up: Now keeps stream byte order consistent (LE), support for Raster, more tests
* Mipmap support using ImageIO sequence API
* Added raster write test
+ fixed a small issue for PAM
* Sonar issues
2026-03-11 21:09:26 +01:00
dependabot[bot] e61ec45737 Bump org.apache.maven.plugins:maven-shade-plugin from 3.6.1 to 3.6.2
Bumps [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.6.1 to 3.6.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.6.1...maven-shade-plugin-3.6.2)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-shade-plugin
  dependency-version: 3.6.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 16:03:03 +01:00
dependabot[bot] 6e063f263c Bump actions/upload-artifact from 6.0.0 to 7.0.0 in /.github/workflows
---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 16:02:33 +01:00
dependabot[bot] e2cf529000 Bump org.apache.maven.plugins:maven-resources-plugin from 3.4.0 to 3.5.0
Bumps [org.apache.maven.plugins:maven-resources-plugin](https://github.com/apache/maven-resources-plugin) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/apache/maven-resources-plugin/releases)
- [Commits](https://github.com/apache/maven-resources-plugin/compare/v3.4.0...maven-resources-plugin-3.5.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-resources-plugin
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 14:21:22 +01:00
dependabot[bot] 9d1c418d8d Bump mikepenz/action-junit-report in /.github/workflows
Bumps [mikepenz/action-junit-report](https://github.com/mikepenz/action-junit-report) from 6.2.0 to 6.3.1.
- [Release notes](https://github.com/mikepenz/action-junit-report/releases)
- [Commits](https://github.com/mikepenz/action-junit-report/compare/74626db7353a25a20a72816467ebf035f674c5f8...49b2ca06f62aa7ef83ae6769a2179271e160d8e4)

---
updated-dependencies:
- dependency-name: mikepenz/action-junit-report
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 14:20:41 +01:00
KhanhTypo a7a4445ce8 [DDS] Adding Block Compression 1 -> 5 Encoding Support (#1237)
* dds dxt10 support, with some certain supported DXGI Formats only.
* expand the supporting range for some DX10 DXGI Format in the DXGI_FORMAT enumeration
* readability and maintainability fixes, adding DXT10 test cases.
* java.awt.* -> java.awt.Dimension
* DDS header & BC1 writer
* BC4 Writer
* BC3 Writer
* BC1-5 writer support
* remove unused methods
* code fixes
* BC4 fix to resolve unwanted blocky effect.
* CI test fixes
* change bitflag setter functions
* temporary disable formats that does not have an encoder yet.
* resolving SonaQube issues.
2026-03-04 10:35:40 +01:00
Harald Kuhr 4c1b268325 Javadoc cleanup (#1255)
* #1234: Fixed JavaDoc for Java 21 + JavaDoc verification step
* #1234: Simplify build matrix
* #1234: Javadoc in parallell
2026-03-04 10:35:04 +01:00
dependabot[bot] e3c3f640a4 Bump github/codeql-action from 4.32.2 to 4.32.4 in /.github/workflows
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.2 to 4.32.4.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2...89a39a4e59826350b863aa6b6252a07ad50cf83e)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.32.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 10:01:45 +01:00
dependabot[bot] ecc938a666 Bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.4 to 3.5.5
Bumps [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) from 3.5.4 to 3.5.5.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.4...surefire-3.5.5)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-plugin
  dependency-version: 3.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 19:23:46 +01:00
dependabot[bot] 25fdfb1947 Bump org.apache.maven.plugins:maven-surefire-report-plugin
Bumps [org.apache.maven.plugins:maven-surefire-report-plugin](https://github.com/apache/maven-surefire) from 3.5.4 to 3.5.5.
- [Release notes](https://github.com/apache/maven-surefire/releases)
- [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.5.4...surefire-3.5.5)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-surefire-report-plugin
  dependency-version: 3.5.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 19:23:19 +01:00
dependabot[bot] 0c05918d8a Bump junit.jupiter.version from 5.14.2 to 5.14.3
Bumps `junit.jupiter.version` from 5.14.2 to 5.14.3.

Updates `org.junit.jupiter:junit-jupiter-api` from 5.14.2 to 5.14.3
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.14.2...r5.14.3)

Updates `org.junit.jupiter:junit-jupiter-params` from 5.14.2 to 5.14.3
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.14.2...r5.14.3)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.14.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.14.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 10:26:02 +01:00
Harald Kuhr 4de2a38dd8 New version + snapshot fix 2026-02-23 10:05:29 +01:00
Harald Kuhr 5b4fa64dc0 [maven-release-plugin] prepare for next development iteration 2026-02-22 16:13:49 +01:00
83 changed files with 2795 additions and 579 deletions
+28 -13
View File
@@ -17,7 +17,10 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
java: [ 11, 17, 21, 25 ]
java: [ 8, 11, 17, 21, 25 ]
exclude:
- os: macos-latest
java: 8
runs-on: ${{ matrix.os }}
permissions:
checks: write
@@ -32,19 +35,15 @@ jobs:
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@74626db7353a25a20a72816467ebf035f674c5f8 # v5
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v5
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
check_name: Unit Test Results for OpenJDK ${{ matrix.java }} on ${{ matrix.os }}
test-jdk8:
name: Test OpenJDK 8 on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-14 ]
runs-on: ${{ matrix.os }}
test-jdk8-macos:
name: Test OpenJDK 8 on macos-14
runs-on: macos-14
permissions:
checks: write
steps:
@@ -58,7 +57,7 @@ jobs:
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@74626db7353a25a20a72816467ebf035f674c5f8 # v5
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v5
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -92,16 +91,32 @@ jobs:
- name: Run Tests
run: mvn --batch-mode --no-transfer-progress test
- name: Publish Test Report
uses: mikepenz/action-junit-report@74626db7353a25a20a72816467ebf035f674c5f8 # v5
uses: mikepenz/action-junit-report@bccf2e31636835cf0874589931c4116687171386 # v5
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
check_name: Unit Test Results for Oracle JDK 8 with KCMS=${{ matrix.kcms }}
javadoc:
name: Build JavaDoc on OpenJDK ${{ matrix.java }}
runs-on: ubuntu-latest
strategy:
matrix:
java: [8, 11, 25 ] # We only need a few versions here
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
java-package: jdk
cache: 'maven'
- name: Create JavaDoc
run: mvn --batch-mode --no-transfer-progress -DskipTests package javadoc:javadoc
release:
name: Deploy
needs: [ test, test-jdk8, test-oracle ]
# Temporarily disable deploy
needs: [ test, test-jdk8-macos, test-oracle, javadoc ]
if: github.ref == 'refs/heads/master' # only perform on latest master
runs-on: ubuntu-latest
steps:
+3 -3
View File
@@ -37,7 +37,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
uses: github/codeql-action/init@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -51,7 +51,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
uses: github/codeql-action/autobuild@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -64,6 +64,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
uses: github/codeql-action/analyze@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
category: "/language:${{matrix.language}}"
+2 -2
View File
@@ -49,7 +49,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: SARIF file
path: results.sarif
@@ -57,6 +57,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v4.35.4
with:
sarif_file: results.sarif
+38 -38
View File
@@ -4,7 +4,7 @@
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7900/badge)](https://www.bestpractices.dev/projects/7900)
[![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/)
[![Maven Snapshot](https://img.shields.io/maven-metadata/v?metadataUrl=https%3A%2F%2Fcentral.sonatype.com%2Frepository%2Fmaven-snapshots%2Fcom%2Ftwelvemonkeys%2Fimageio%2Fimageio%2Fmaven-metadata.xml&label=development&server=https%3A%2F%2Foss.sonatype.org&color=slateblue)](https://central.sonatype.com/repository/maven-snapshots/com/twelvemonkeys/imageio/imageio/maven-metadata.xml)
[![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)
@@ -316,12 +316,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.12.0</version>
<version>3.13.1</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.12.0</version>
<version>3.13.1</version>
</dependency>
<!--
@@ -331,7 +331,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.12.0</version>
<version>3.13.1</version>
</dependency>
<!--
@@ -340,7 +340,7 @@ To depend on the JPEG and TIFF plugin using Maven, add the following to your POM
<dependency>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<version>3.12.0</version>
<version>3.13.1</version>
<classifier>jakarta</classifier>
</dependency>
</dependencies>
@@ -350,13 +350,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.12.0.jar
twelvemonkeys-common-io-3.12.0.jar
twelvemonkeys-common-image-3.12.0.jar
twelvemonkeys-imageio-core-3.12.0.jar
twelvemonkeys-imageio-metadata-3.12.0.jar
twelvemonkeys-imageio-jpeg-3.12.0.jar
twelvemonkeys-imageio-tiff-3.12.0.jar
twelvemonkeys-common-lang-3.13.1.jar
twelvemonkeys-common-io-3.13.1.jar
twelvemonkeys-common-image-3.13.1.jar
twelvemonkeys-imageio-core-3.13.1.jar
twelvemonkeys-imageio-metadata-3.13.1.jar
twelvemonkeys-imageio-jpeg-3.13.1.jar
twelvemonkeys-imageio-tiff-3.13.1.jar
#### Deploying the plugins in a web app
@@ -432,46 +432,46 @@ Other "fat" JAR bundlers will probably have similar mechanisms to merge entries
### Links to prebuilt binaries
##### Latest version (3.12.0)
##### Latest version (3.13.1)
The latest version that will run on Java 7 is 3.9.4. Later versions will require Java 8 or later.
Common dependencies
* [common-lang-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.12.0/common-lang-3.12.0.jar)
* [common-io-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.12.0/common-io-3.12.0.jar)
* [common-image-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.12.0/common-image-3.12.0.jar)
* [common-lang-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-lang/3.13.1/common-lang-3.13.1.jar)
* [common-io-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-io/3.13.1/common-io-3.13.1.jar)
* [common-image-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/common/common-image/3.13.1/common-image-3.13.1.jar)
ImageIO dependencies
* [imageio-core-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.12.0/imageio-core-3.12.0.jar)
* [imageio-metadata-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.12.0/imageio-metadata-3.12.0.jar)
* [imageio-core-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-core/3.13.1/imageio-core-3.13.1.jar)
* [imageio-metadata-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-metadata/3.13.1/imageio-metadata-3.13.1.jar)
ImageIO plugins
* [imageio-bmp-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.12.0/imageio-bmp-3.12.0.jar)
* [imageio-dds-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-dds/3.12.0/imageio-dds-3.12.0.jar)
* [imageio-hdr-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.12.0/imageio-hdr-3.12.0.jar)
* [imageio-icns-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.12.0/imageio-icns-3.12.0.jar)
* [imageio-iff-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.12.0/imageio-iff-3.12.0.jar)
* [imageio-jpeg-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.12.0/imageio-jpeg-3.12.0.jar)
* [imageio-pcx-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.12.0/imageio-pcx-3.12.0.jar)
* [imageio-pict-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.12.0/imageio-pict-3.12.0.jar)
* [imageio-pnm-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.12.0/imageio-pnm-3.12.0.jar)
* [imageio-psd-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.12.0/imageio-psd-3.12.0.jar)
* [imageio-sgi-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.12.0/imageio-sgi-3.12.0.jar)
* [imageio-tga-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.12.0/imageio-tga-3.12.0.jar)
* [imageio-thumbsdb-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.12.0/imageio-thumbsdb-3.12.0.jar)
* [imageio-tiff-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.12.0/imageio-tiff-3.12.0.jar)
* [imageio-webp-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.12.0/imageio-webp-3.12.0.jar)
* [imageio-xwd-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.12.0/imageio-xwd-3.12.0.jar)
* [imageio-bmp-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-bmp/3.13.1/imageio-bmp-3.13.1.jar)
* [imageio-dds-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-dds/3.13.1/imageio-dds-3.13.1.jar)
* [imageio-hdr-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-hdr/3.13.1/imageio-hdr-3.13.1.jar)
* [imageio-icns-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-icns/3.13.1/imageio-icns-3.13.1.jar)
* [imageio-iff-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-iff/3.13.1/imageio-iff-3.13.1.jar)
* [imageio-jpeg-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-jpeg/3.13.1/imageio-jpeg-3.13.1.jar)
* [imageio-pcx-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pcx/3.13.1/imageio-pcx-3.13.1.jar)
* [imageio-pict-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pict/3.13.1/imageio-pict-3.13.1.jar)
* [imageio-pnm-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-pnm/3.13.1/imageio-pnm-3.13.1.jar)
* [imageio-psd-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-psd/3.13.1/imageio-psd-3.13.1.jar)
* [imageio-sgi-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-sgi/3.13.1/imageio-sgi-3.13.1.jar)
* [imageio-tga-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tga/3.13.1/imageio-tga-3.13.1.jar)
* [imageio-thumbsdb-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-thumbsdb/3.13.1/imageio-thumbsdb-3.13.1.jar)
* [imageio-tiff-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-tiff/3.13.1/imageio-tiff-3.13.1.jar)
* [imageio-webp-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-webp/3.13.1/imageio-webp-3.13.1.jar)
* [imageio-xwd-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-xwd/3.13.1/imageio-xwd-3.13.1.jar)
ImageIO plugins requiring 3rd party libs
* [imageio-batik-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.12.0/imageio-batik-3.12.0.jar)
* [imageio-batik-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-batik/3.13.1/imageio-batik-3.13.1.jar)
Photoshop Path support for ImageIO
* [imageio-clippath-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.12.0/imageio-clippath-3.12.0.jar)
* [imageio-clippath-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/imageio/imageio-clippath/3.13.1/imageio-clippath-3.13.1.jar)
Servlet support
* [servlet-3.12.0.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.12.0/servlet-3.12.0.jar) for legacy Java EE (javax.servlet)
* [servlet-3.12.0-jakarta.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.12.0/servlet-3.12.0-jakrta.jar) for Jakarta EE (jakarta.servlet)
* [servlet-3.13.1.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.13.1/servlet-3.13.1.jar) for legacy Java EE (javax.servlet)
* [servlet-3.13.1-jakarta.jar](https://search.maven.org/remotecontent?filepath=com/twelvemonkeys/servlet/servlet/3.13.1/servlet-3.13.1-jakrta.jar) for Jakarta EE (jakarta.servlet)
## License
+2 -2
View File
@@ -5,13 +5,13 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
<artifactId>bom</artifactId>
<packaging>pom</packaging>
<name>TwelveMonkeys :: BOM</name>
<name>TwelveMonkeys » BOM</name>
<description>
TwelveMonkeys "Bill of Materials" (BOM).
</description>
+2 -2
View File
@@ -4,11 +4,11 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: Image</name>
<name>TwelveMonkeys » Common » Image</name>
<description>
TwelveMonkeys Common image support classes.
</description>
@@ -47,10 +47,10 @@ import java.util.Random;
* |3|5|1|
* - - -->
* </p>
* <table border="1" cellpadding="4" cellspacing="0">
* <table border="1">
* <caption>Floyd-Steinberg error-diffusion weights</caption>
* <tr><td bgcolor="#000000">&nbsp;</td><td class="TableHeadingColor"
* align="center">x</td><td>7/16</td></tr>
* <tr><td style="background:#000000">&nbsp;</td><td class="TableHeadingColor"
* style="text-align:center">x</td><td>7/16</td></tr>
* <tr><td>3/16</td><td>5/16</td><td>1/16</td></tr>
* </table>
* <p>
@@ -162,7 +162,7 @@ public final class ImageUtil {
/**
* The sharpen kernel. Uses the following 3 by 3 matrix:
* <table border="1" cellspacing="0">
* <table border="1">
* <caption>Sharpen Kernel Matrix</caption>
* <tr><td>0.0</td><td>-0.3</td><td>0.0</td></tr>
* <tr><td>-0.3</td><td>2.2</td><td>-0.3</td></tr>
@@ -1078,7 +1078,7 @@ public final class ImageUtil {
/**
* Sharpens an image using a convolution matrix.
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
* <table border="1" cellspacing="0">
* <table border="1">
* <caption>Sharpen Kernel Matrix</caption>
* <tr><td>0.0</td><td>-0.3</td><td>0.0</td></tr>
* <tr><td>-0.3</td><td>2.2</td><td>-0.3</td></tr>
@@ -1100,7 +1100,7 @@ public final class ImageUtil {
/**
* Sharpens an image using a convolution matrix.
* The sharpen kernel used, is defined by the following 3 by 3 matrix:
* <table border="1" cellspacing="0">
* <table border="1">
* <caption>Sharpen Kernel Matrix</caption>
* <tr><td>0.0</td><td>-{@code pAmount}</td><td>0.0</td></tr>
* <tr><td>-{@code pAmount}</td>
+2 -2
View File
@@ -4,11 +4,11 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: IO</name>
<name>TwelveMonkeys » Common » IO</name>
<description>
TwelveMonkeys Common I/O support classes.
</description>
+2 -2
View File
@@ -4,11 +4,11 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
<name>TwelveMonkeys :: Common :: Language support</name>
<name>TwelveMonkeys » Common » Language support</name>
<description>
TwelveMonkeys Common language support classes.
</description>
@@ -1765,12 +1765,11 @@ public final class StringUtil {
* expression.
* <p>
* An invocation of this method of the form
* <tt>matches(<i>str</i>, <i>regex</i>)</tt> yields exactly the
* {@code matches(str, regex)} yields exactly the
* same result as the expression
* </p>
* <blockquote><tt> {@link Pattern}.
* {@link Pattern#matches(String, CharSequence) matches}
* (<i>regex</i>, <i>str</i>)</tt></blockquote>
* <blockquote>{@link Pattern}.
* {@link Pattern#matches(String, CharSequence) matches(regex, str)}</blockquote>
*
* @param pString the string
* @param pRegex the regular expression to which this string is to be matched
@@ -1789,16 +1788,14 @@ public final class StringUtil {
* regular expression with the given pReplacement.
* <p>
* An invocation of this method of the form
* <tt>
* replaceFirst(<i>str</i>, <i>regex</i>, <i>repl</i>)
* </tt>
* {@code replaceFirst(str, regex, repl)}
* yields exactly the same result as the expression:
* </p>
* <blockquote><tt>
* {@link Pattern}.{@link Pattern#compile(String) compile}(<i>regex</i>).
* {@link Pattern#matcher matcher}(<i>str</i>).
* {@link java.util.regex.Matcher#replaceFirst replaceFirst}(<i>repl</i>)
* </tt></blockquote>
* <blockquote>
* {@link Pattern#compile(String) Pattern.compile(regex)}
* {@link Pattern#matcher .matcher(str)}
* {@link java.util.regex.Matcher#replaceFirst .replaceFirst(repl)}
* </blockquote>
*
* @param pString the string
* @param pRegex the regular expression to which this string is to be matched
@@ -1817,14 +1814,14 @@ public final class StringUtil {
* regular expression with the given pReplacement.
* <p>
* An invocation of this method of the form
* <tt>replaceAll(<i>str</i>, <i>pRegex</i>, <i>repl</i>)</tt>
* {@code replaceAll(str, pRegex, repl)}
* yields exactly the same result as the expression
* </p>
* <blockquote><tt>
* {@link Pattern}.{@link Pattern#compile(String) compile}(<i>pRegex</i>).
* {@link Pattern#matcher matcher}(<i>str</i>{@code ).
* {@link java.util.regex.Matcher#replaceAll replaceAll}(}<i>repl</i>{@code )}
* </tt></blockquote>
* <blockquote>
* {@link Pattern#compile(String) Pattern.compile(pRegex)}
* {@link Pattern#matcher .matcher(str)}
* {@link java.util.regex.Matcher#replaceAll .replaceAll(repl)}
* </blockquote>
*
* @param pString the string
* @param pRegex the regular expression to which this string is to be matched
@@ -1862,12 +1859,12 @@ public final class StringUtil {
* </p>
* <p>
* An invocation of this method of the form
* <tt>split(<i>str</i>, <i>regex</i>, <i>n</i>)</tt>
* {@code split(str, regex, n)}
* yields the same result as the expression:
* </p>
* <blockquote>{@link Pattern}.
* {@link Pattern#compile(String) compile}<tt>(<i>regex</i>).
* {@link Pattern#split(CharSequence,int) split}(<i>str</i>, <i>n</i>)</tt>
* {@link Pattern#compile(String) compile(regex)}.
* {@link Pattern#split(CharSequence,int) split(str, n)}
* </blockquote>
*
* @param pString the string
@@ -284,20 +284,6 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
return false;
}
/*
// NOTE: Extra cautions is taken, to only remove the entry if it
// equals the entry in the map
Object key = ((Entry) o).getKey();
Entry entry = (Entry) entries.get(key);
// Same entry?
if (entry != null && entry.equals(o)) {
return AbstractWrappedMap.this.remove(key) != null;
}
return false;
*/
//noinspection unchecked
return AbstractDecoratedMap.this.removeEntry((Entry) o) != null;
}
@@ -322,7 +308,7 @@ abstract class AbstractDecoratedMap<K, V> extends AbstractMap<K, V> implements M
return containsKey(o);
}
public boolean remove(Object o) {
return AbstractDecoratedMap.this.remove(o) != null;
return AbstractDecoratedMap.this.removeEntry(getEntry((K) o)) != null;
}
public void clear() {
AbstractDecoratedMap.this.clear();
@@ -137,43 +137,43 @@ public final class BeanMap extends AbstractMap<String, Object> implements Serial
}
private class BeanIterator implements Iterator<Entry<String, Object>> {
private final Iterator<PropertyDescriptor> mIterator;
private final Iterator<PropertyDescriptor> iterator;
public BeanIterator(final Iterator<PropertyDescriptor> pIterator) {
mIterator = pIterator;
iterator = pIterator;
}
public boolean hasNext() {
return mIterator.hasNext();
return iterator.hasNext();
}
public BeanEntry next() {
return new BeanEntry(mIterator.next());
return new BeanEntry(iterator.next());
}
public void remove() {
mIterator.remove();
iterator.remove();
}
}
private class BeanEntry implements Entry<String, Object> {
private final PropertyDescriptor mDescriptor;
private final PropertyDescriptor descriptor;
public BeanEntry(final PropertyDescriptor pDescriptor) {
this.mDescriptor = pDescriptor;
this.descriptor = pDescriptor;
}
public String getKey() {
return mDescriptor.getName();
return descriptor.getName();
}
public Object getValue() {
return unwrap(new Wrapped() {
public Object run() throws IllegalAccessException, InvocationTargetException {
final Method method = mDescriptor.getReadMethod();
final Method method = descriptor.getReadMethod();
// A write-only bean.
if (method == null) {
throw new UnsupportedOperationException("No getter: " + mDescriptor.getName());
throw new UnsupportedOperationException("No getter: " + descriptor.getName());
}
return method.invoke(bean);
@@ -184,10 +184,10 @@ public final class BeanMap extends AbstractMap<String, Object> implements Serial
public Object setValue(final Object pValue) {
return unwrap(new Wrapped() {
public Object run() throws IllegalAccessException, InvocationTargetException {
final Method method = mDescriptor.getWriteMethod();
final Method method = descriptor.getWriteMethod();
// A read-only bean.
if (method == null) {
throw new UnsupportedOperationException("No write method for property: " + mDescriptor.getName());
throw new UnsupportedOperationException("No write method for property: " + descriptor.getName());
}
final Object old = getValue();
@@ -71,7 +71,6 @@ public abstract class ObjectAbstractTest {
// TODO: What more can we test?
}
// TODO: assert that either BOTH or NONE of equals/hashcode is overridden
@Test
public void testEqualsHashCode(){
Object obj = makeObject();
@@ -328,5 +327,4 @@ public abstract class ObjectAbstractTest {
return new Cloneable() {};
}
}
}
@@ -30,6 +30,8 @@
package com.twelvemonkeys.util;
import org.junit.jupiter.api.Nested;
import java.beans.IntrospectionException;
import java.io.Serializable;
import java.util.Map;
@@ -172,4 +174,16 @@ public class BeanMapTest extends MapAbstractTest {
}
static class NullBean implements Serializable { }
@Nested
public class TestBeanMapEntrySet extends TestMapEntrySet {
}
@Nested
public class TestBeanMapKeySet extends TestMapKeySet {
}
@Nested
public class TestBeanMapValues extends TestMapValues {
}
}
@@ -436,24 +436,24 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
*/
public Object[] getFullNonNullElements() {
return new Object[] {
new String(""),
new String("One"),
new Integer(2),
"Three",
new Integer(4),
"",
"One",
new Double(5),
new Float(6),
2,
"Three",
4,
"One",
5.0,
6F,
"Seven",
"Eight",
new String("Nine"),
new Integer(10),
new Short((short)11),
new Long(12),
"Nine",
10,
(short) 11,
12L,
"Thirteen",
"14",
"15",
new Byte((byte)16)
(byte) 16
};
}
@@ -1149,7 +1149,7 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
public void testUnsupportedRemove() {
if (isRemoveSupported()) return;
resetEmpty();
resetFull();
try {
collection.clear();
fail("clear should raise UnsupportedOperationException");
@@ -1159,7 +1159,7 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
verifyAll();
try {
collection.remove(null);
collection.remove(getFullElements()[0]);
fail("remove should raise UnsupportedOperationException");
} catch (UnsupportedOperationException e) {
// expected
@@ -1167,7 +1167,7 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
verifyAll();
try {
collection.removeAll(null);
collection.removeAll(Arrays.asList(getFullElements()));
fail("removeAll should raise UnsupportedOperationException");
} catch (UnsupportedOperationException e) {
// expected
@@ -1175,7 +1175,7 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
verifyAll();
try {
collection.retainAll(null);
collection.retainAll(Collections.emptySet());
fail("removeAll should raise UnsupportedOperationException");
} catch (UnsupportedOperationException e) {
// expected
@@ -1192,7 +1192,6 @@ public abstract class CollectionAbstractTest extends ObjectAbstractTest {
// expected
}
verifyAll();
}
@@ -212,5 +212,17 @@ public class LRUMapTest extends LinkedMapTest {
list.add(pEntry.getKey());
}
}
@Nested
public class TestLRUMapEntrySet extends TestMapEntrySet {
}
@Nested
public class TestLRUMapKeySet extends TestMapKeySet {
}
@Nested
public class TestLRUMapValues extends TestMapValues {
}
}
@@ -201,4 +201,16 @@ public class LinkedMapTest extends MapAbstractTest {
public void tearDown() throws Exception {
labRat = null;
}
@Nested
public class TestLinkedMapEntrySet extends TestMapEntrySet {
}
@Nested
public class TestLinkedMapKeySet extends TestMapKeySet {
}
@Nested
public class TestLinkedMapValues extends TestMapValues {
}
}
@@ -1255,7 +1255,7 @@ public abstract class MapAbstractTest extends ObjectAbstractTest {
}
*/
public class TestMapEntrySet extends SetAbstractTest {
protected abstract class TestMapEntrySet extends SetAbstractTest {
// Have to implement manually; entrySet doesn't support addAll
public Object[] getFullElements() {
@@ -1429,7 +1429,7 @@ public abstract class MapAbstractTest extends ObjectAbstractTest {
}
*/
public class TestMapKeySet extends SetAbstractTest {
protected abstract class TestMapKeySet extends SetAbstractTest {
public Object[] getFullElements() {
return getSampleKeys();
}
@@ -1495,7 +1495,7 @@ public abstract class MapAbstractTest extends ObjectAbstractTest {
}
*/
public class TestMapValues extends CollectionAbstractTest {
protected abstract class TestMapValues extends CollectionAbstractTest {
public Object[] getFullElements() {
return getSampleValues();
}
@@ -668,5 +668,17 @@ public class TimeoutMapTest extends MapAbstractTest {
assertFalse(timeoutMap.containsKey("xyz"));
assertNull(timeoutMap.get("xyz"));
}
@Nested
public class TestTimeoutMapEntrySet extends TestMapEntrySet {
}
@Nested
public class TestTimeoutMapKeySet extends TestMapKeySet {
}
@Nested
public class TestTimeoutMapValues extends TestMapValues {
}
}
+3 -3
View File
@@ -4,12 +4,12 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<name>TwelveMonkeys :: Common</name>
<name>TwelveMonkeys » Common</name>
<description>
The TwelveMonkeys Common library. Contains common utility classes.
</description>
@@ -21,7 +21,7 @@
</modules>
<properties>
<junit.jupiter.version>5.14.2</junit.jupiter.version>
<junit.jupiter.version>5.14.4</junit.jupiter.version>
</properties>
<dependencyManagement>
+3 -3
View File
@@ -4,17 +4,17 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
<name>TwelveMonkeys :: Contrib</name>
<name>TwelveMonkeys » Contrib</name>
<description>
Contributions to TwelveMonkeys and code that doesn't fit anywhere else.
</description>
<properties>
<junit.jupiter.version>5.14.2</junit.jupiter.version>
<junit.jupiter.version>5.14.4</junit.jupiter.version>
</properties>
+3 -3
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
<name>TwelveMonkeys » ImageIO » Batik Plugin</name>
<description>
<![CDATA[
ImageIO wrapper for the Batik SVG Toolkit, enabling Scalable Vector Graphics (SVG) support.
@@ -63,7 +63,7 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.21.0</version>
<version>2.22.0</version>
<scope>provided</scope>
</dependency>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
<name>TwelveMonkeys » ImageIO » BMP plugin</name>
<description>ImageIO plugin for Microsoft Device Independent Bitmap (BMP/DIB) format.</description>
<properties>
@@ -32,6 +32,7 @@ package com.twelvemonkeys.imageio.plugins.bmp;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
@@ -64,7 +65,7 @@ public final class ICOImageWriter extends DIBImageWriter {
private static final int ICO_MAX_DIMENSION = 256;
private static final int INITIAL_ENTRY_COUNT = 8;
private int sequenceIndex = -1;
private final SequenceSupport sequence = new SequenceSupport();
private ImageWriter pngDelegate;
@@ -74,7 +75,7 @@ public final class ICOImageWriter extends DIBImageWriter {
@Override
protected void resetMembers() {
sequenceIndex = -1;
sequence.reset();
if (pngDelegate != null) {
pngDelegate.dispose();
@@ -107,16 +108,12 @@ public final class ICOImageWriter extends DIBImageWriter {
@Override
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
assertOutput();
if (sequenceIndex >= 0) {
throw new IllegalStateException("writeSequence already started");
}
sequence.start();
writeICOHeader();
// Count: Needs to be updated for each new image
imageOutput.writeShort(0);
sequenceIndex = 0;
// TODO: Allow passing the initial size of the directory in the stream metadata?
// - as this is much more efficient than growing...
@@ -130,27 +127,19 @@ public final class ICOImageWriter extends DIBImageWriter {
@Override
public void endWriteSequence() {
assertOutput();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
sequenceIndex = -1;
sequence.end();
}
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
assertOutput();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
int imageIndex = sequence.advance();
if (image.hasRaster()) {
throw new UnsupportedOperationException("Raster not supported");
}
if (sequenceIndex >= INITIAL_ENTRY_COUNT) {
if (imageIndex >= INITIAL_ENTRY_COUNT) {
growIfNecessary();
}
@@ -172,7 +161,7 @@ public final class ICOImageWriter extends DIBImageWriter {
// Uncompressed, RLE4/RLE8 or PNG compressed
boolean pngCompression = param != null && "BI_PNG".equals(param.getCompressionType());
processImageStarted(sequenceIndex);
processImageStarted(imageIndex);
if (pngCompression) {
// NOTE: Embedding a PNG in a ICO is slightly different than a BMP with BI_PNG compression,
@@ -198,17 +187,15 @@ public final class ICOImageWriter extends DIBImageWriter {
// Update count
imageOutput.seek(4);
imageOutput.writeShort(sequenceIndex + 1);
imageOutput.writeShort(imageIndex + 1);
// Write entry
int entryPosition = 6 + sequenceIndex * ENTRY_SIZE;
int entryPosition = 6 + imageIndex * ENTRY_SIZE;
imageOutput.seek(entryPosition);
long size = nextPosition - imageOffset;
writeEntry(width, height, colorModel, (int) size, (int) imageOffset);
sequenceIndex++;
imageOutput.seek(nextPosition);
}
@@ -265,7 +252,7 @@ public final class ICOImageWriter extends DIBImageWriter {
pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() {
@Override
public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
processWarningOccurred(sequenceIndex, warning);
processWarningOccurred(sequence.current(), warning);
}
});
}
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
<name>TwelveMonkeys » ImageIO » Photoshop Path Support</name>
<description>
Photoshop Clipping Path Support.
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
<name>TwelveMonkeys » ImageIO » Core</name>
<description>
TwelveMonkeys ImageIO core support classes.
</description>
@@ -23,6 +23,7 @@ import static com.twelvemonkeys.lang.Validate.notNull;
* {@link ImageTypeSpecifier}.
* Other values or overrides may be specified using the builder.
*
* @see <a href="https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/metadata/doc-files/standard_metadata.html">Standard (Plug-in Neutral) Metadata Format Specification</a>
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public class StandardImageMetadataSupport extends AbstractMetadata {
@@ -79,11 +80,11 @@ public class StandardImageMetadataSupport extends AbstractMetadata {
textEntries = builder.textEntries;
}
public static Builder builder(ImageTypeSpecifier type) {
protected static Builder builder(ImageTypeSpecifier type) {
return new Builder(type);
}
public static class Builder {
protected static class Builder {
private final ImageTypeSpecifier type;
private ColorSpaceType colorSpaceType;
private boolean blackIsZero = true;
@@ -35,6 +35,8 @@ import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOParam;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageWriteParam;
import javax.imageio.spi.IIOServiceProvider;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
@@ -45,7 +47,9 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
@@ -68,7 +72,7 @@ public final class IIOUtil {
*/
public static InputStream createStreamAdapter(final ImageInputStream pStream) {
// TODO: Include stream start pos?
// TODO: Skip buffering for known in-memory implementations?
// TODO: Skip buffering for known in-memory implementations? pStream.isCachedMemory
return new BufferedInputStream(new IIOInputStreamAdapter(pStream));
}
@@ -82,7 +86,7 @@ public final class IIOUtil {
*/
public static InputStream createStreamAdapter(final ImageInputStream pStream, final long pLength) {
// TODO: Include stream start pos?
// TODO: Skip buffering for known in-memory implementations?
// TODO: Skip buffering for known in-memory implementations? pStream.isCachedMemory
return new BufferedInputStream(new IIOInputStreamAdapter(pStream, pLength));
}
@@ -359,4 +363,115 @@ public final class IIOUtil {
System.arraycopy(srcRow, srcPos + x, destRow, destPos + x / samplePeriod, pixelStride);
}
}
/**
* Copies all the standard param values from source to destination.
* <p>
* Typical use (in some imaginary {@code FooImageWriter} class):
* </p>
*
* <pre>
* ImageWriteParam param = ...
* FooImageWriteparam fooParam = param instanceof FooImageWriteParam
* ? (FooImageWriteParam) param
* : copyStandardParams(param, getDefaultWriteParam());
* </pre>
*
* May also be useful for {@code ImageReader}s that delegate reading to other plugins
* (like a TIFF plugin delegating JPEG format decoding to a {@code JPEGImageReader}).
*
* @param source the source parameter, may be {@code null}
* @param destination the destination parameter
* @return destination
*
* @param <T> the plugin specific subclass of {@code IIOParam}
*
* @throws NullPointerException if destination is {@code null}
*/
public static <T extends IIOParam> T copyStandardParams(IIOParam source, T destination) {
Objects.requireNonNull(destination);
Validate.isTrue(source != destination, "source must be different from destination");
if (source != null) {
copyIIOParams(source, destination);
// TODO: API & usage... Is it ever useful to copy from a read to a write param or vice versa?
// If not, maybe throw an IllegalArgumentException instead
if (source instanceof ImageReadParam && destination instanceof ImageReadParam) {
copyImageReadParams((ImageReadParam) source, (ImageReadParam) destination);
}
if (source instanceof ImageWriteParam && destination instanceof ImageWriteParam) {
copyImageWriteParams((ImageWriteParam) source, (ImageWriteParam) destination);
}
}
return destination;
}
private static void copyImageWriteParams(ImageWriteParam source, ImageWriteParam destination) {
// TODO: Usage... It's very unlikely that compression settings of one plugin is compatible with another...
// Is the the below useful?
// Also, is it okay to just silently ignore settings from one format that isn't compatible with another?
// Quirky API, we can't query for compression mode, unless source.canWriteCompressed is true...
if (source.canWriteCompressed() && destination.canWriteCompressed()) {
int compressionMode = source.getCompressionMode();
destination.setCompressionMode(compressionMode);
if (compressionMode == ImageWriteParam.MODE_EXPLICIT
&& source.getCompressionType() != null
&& Arrays.asList(destination.getCompressionTypes()).contains(source.getCompressionType())) {
destination.setCompressionType(source.getCompressionType());
destination.setCompressionQuality(source.getCompressionQuality());
}
}
if (source.canWriteProgressive() && destination.canWriteProgressive()) {
destination.setProgressiveMode(source.getProgressiveMode());
}
if (source.canWriteTiles() && destination.canWriteTiles()) {
int tilingMode = source.getTilingMode();
destination.setTilingMode(tilingMode);
if (tilingMode == ImageWriteParam.MODE_EXPLICIT) {
// TODO: What if source can offset (and has offsets) and dest can't? Is it ok to just ignore the setting?
boolean canWriteOffsetTiles = source.canOffsetTiles() && destination.canOffsetTiles();
destination.setTiling(
source.getTileWidth(), source.getTileHeight(),
canWriteOffsetTiles ? source.getTileGridXOffset() : 0,
canWriteOffsetTiles ? source.getTileGridYOffset() : 0
);
}
}
}
private static void copyImageReadParams(ImageReadParam source, ImageReadParam destination) {
destination.setDestination(source.getDestination());
destination.setDestinationBands(source.getDestinationBands());
if (destination.canSetSourceRenderSize()) {
destination.setSourceRenderSize(source.getSourceRenderSize());
}
destination.setSourceProgressivePasses(
source.getSourceMinProgressivePass(),
source.getSourceMaxProgressivePass()
);
}
private static void copyIIOParams(IIOParam source, IIOParam destination) {
destination.setController(source.getController());
destination.setSourceSubsampling(
source.getSourceXSubsampling(), source.getSourceYSubsampling(),
source.getSubsamplingXOffset(), source.getSubsamplingYOffset()
);
destination.setSourceRegion(source.getSourceRegion());
destination.setSourceBands(source.getSourceBands());
destination.setDestinationOffset(source.getDestinationOffset());
destination.setDestinationType(source.getDestinationType());
}
}
@@ -0,0 +1,115 @@
/*
* Copyright (c) 2026, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.util;
import javax.imageio.IIOImage;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
/**
* A tiny utility class that keeps state for sequences.
* For use by {@code ImageWriter} implementations that supports sequence (multiple images in same stream).
*
* @see ImageWriter#canWriteSequence()
*/
public final class SequenceSupport {
// Initial state, no sequence running
private int index = -1;
/**
* Resets the sequence to initial state, regardless of the current sequence state.
*/
public void reset() {
index = -1;
}
/**
* Starts a new sequence.
*
* @throws IllegalStateException if a sequence is already running.
* @see ImageWriter#prepareWriteSequence(IIOMetadata)
*/
public void start() {
if (index >= 0) {
throw new IllegalStateException("prepareWriteSequence already invoked");
}
index = 0;
}
/**
* Advances the current sequence.
*
* @return the current sequence index.
* @throws IllegalStateException if a sequence is not running.
* @see ImageWriter#writeToSequence(IIOImage, ImageWriteParam)
*/
public int advance() {
if (index < 0) {
throw new IllegalStateException("prepareWriteSequence not invoked");
}
return index++;
}
/**
* Gets the current sequence index.
*
* @return the current sequence index, or {@code -1} if a sequence is not running.
*/
public int current() {
// This method does not throw IllegalStateException, to allow
// ImageWriters to use the index in cases that may or may not
// happen "inside" a sequence.
// I'm not entirely sure if this is a good idea...
return index;
}
/**
* Ends the current sequence.
* The sequence is reset to initial state, and a new sequence may be started.
*
* @return the current (last) sequence index
* @throws IllegalStateException if a sequence is not running.
* @see ImageWriter#endWriteSequence()
*/
public int end() {
if (index < 0) {
throw new IllegalStateException("prepareWriteSequence not invoked");
}
int last = index;
index = -1;
return last;
}
}
@@ -1,8 +1,19 @@
package com.twelvemonkeys.imageio.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.plugins.bmp.BMPImageWriteParam;
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
/**
* IIOUtilTest
*/
@@ -204,4 +215,222 @@ public class IIOUtilTest {
private int divCeil(int numerator, int denominator) {
return (numerator + denominator - 1) / denominator;
}
@Test
void copyStandardParamsDestinationNull() {
ImageReadParam param = new ImageReadParam();
assertThrows(NullPointerException.class, () -> IIOUtil.copyStandardParams(null, null));
assertThrows(NullPointerException.class, () -> IIOUtil.copyStandardParams(param, null));
}
@Test
void copyStandardParamsSame() {
ImageReadParam param = new ImageReadParam();
assertThrows(IllegalArgumentException.class, () -> IIOUtil.copyStandardParams(param, param));
}
@Test
void copyStandardParamsSourceNull() {
ImageReadParam param = new ImageReadParam() {
@Override
public void setSourceRegion(Rectangle sourceRegion) {
fail("Should not be invoked");
}
};
assertSame(param, IIOUtil.copyStandardParams(null, param));
}
@Test
void copyStandardParamsImageReadParam() {
int sourceXSubsampling = 3;
int sourceYSubsampling = 4;
int subsamplingXOffset = 1;
int subsamplingYOffset = 2;
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
int[] sourceBands = { 0, 1, 2 };
Point destinationOffset = new Point(7, 9);
int[] destinationBands = { 2, 1, 0 };
ImageReadParam sourceParam = new ImageReadParam();
sourceParam.setSourceRegion(sourceRegion);
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
sourceParam.setSourceBands(sourceBands);
sourceParam.setDestinationOffset(destinationOffset);
sourceParam.setDestinationBands(destinationBands);
JPEGImageReadParam jpegParam = IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam());
assertEquals(sourceRegion, jpegParam.getSourceRegion());
assertEquals(sourceXSubsampling, jpegParam.getSourceXSubsampling());
assertEquals(sourceYSubsampling, jpegParam.getSourceYSubsampling());
assertEquals(subsamplingXOffset, jpegParam.getSubsamplingXOffset());
assertEquals(subsamplingYOffset, jpegParam.getSubsamplingYOffset());
assertArrayEquals(sourceBands, jpegParam.getSourceBands());
assertEquals(destinationOffset, jpegParam.getDestinationOffset());
assertArrayEquals(destinationBands, jpegParam.getDestinationBands());
}
@Test
void copyStandardParamsImageReadParamDestination() {
// Destination and destination type is mutually exclusive
BufferedImage destination = new BufferedImage(2, 2, BufferedImage.TYPE_INT_ARGB);
ImageReadParam sourceParam = new ImageReadParam();
sourceParam.setDestination(destination);
assertEquals(destination, IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam()).getDestination());
}
@Test
void copyStandardParamsImageReadParamDestinationType() {
// Destination and destination type is mutually exclusive
ImageTypeSpecifier destinationType = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY);
ImageReadParam sourceParam = new ImageReadParam();
sourceParam.setDestinationType(destinationType);
assertEquals(destinationType, IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam()).getDestinationType());
}
@Test
void copyStandardParamsReadToWrite() {
int sourceXSubsampling = 3;
int sourceYSubsampling = 4;
int subsamplingXOffset = 1;
int subsamplingYOffset = 2;
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
int[] sourceBands = { 0, 1, 2 };
Point destinationOffset = new Point(7, 9);
ImageWriteParam sourceParam = new ImageWriteParam(null);
sourceParam.setSourceRegion(sourceRegion);
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
sourceParam.setSourceBands(sourceBands);
sourceParam.setDestinationOffset(destinationOffset);
JPEGImageReadParam jpegParam = IIOUtil.copyStandardParams(sourceParam, new JPEGImageReadParam());
assertEquals(sourceRegion, jpegParam.getSourceRegion());
assertEquals(sourceXSubsampling, jpegParam.getSourceXSubsampling());
assertEquals(sourceYSubsampling, jpegParam.getSourceYSubsampling());
assertEquals(subsamplingXOffset, jpegParam.getSubsamplingXOffset());
assertEquals(subsamplingYOffset, jpegParam.getSubsamplingYOffset());
assertArrayEquals(sourceBands, jpegParam.getSourceBands());
assertEquals(destinationOffset, jpegParam.getDestinationOffset());
assertNull(jpegParam.getDestinationBands()); // Only in read param
}
@Test
void copyStandardParamsImageWriteParam() {
int sourceXSubsampling = 3;
int sourceYSubsampling = 4;
int subsamplingXOffset = 1;
int subsamplingYOffset = 2;
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
int[] sourceBands = { 0, 1, 2 };
Point destinationOffset = new Point(7, 9);
ImageWriteParam sourceParam = new ImageWriteParam(null);
sourceParam.setSourceRegion(sourceRegion);
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
sourceParam.setSourceBands(sourceBands);
sourceParam.setDestinationOffset(destinationOffset);
BMPImageWriteParam fooParam = IIOUtil.copyStandardParams(sourceParam, new BMPImageWriteParam());
assertEquals(sourceRegion, fooParam.getSourceRegion());
assertEquals(sourceXSubsampling, fooParam.getSourceXSubsampling());
assertEquals(sourceYSubsampling, fooParam.getSourceYSubsampling());
assertEquals(subsamplingXOffset, fooParam.getSubsamplingXOffset());
assertEquals(subsamplingYOffset, fooParam.getSubsamplingYOffset());
assertArrayEquals(sourceBands, fooParam.getSourceBands());
assertEquals(destinationOffset, fooParam.getDestinationOffset());
}
@Test
void copyStandardParamsImageWriteParamEverything() {
int sourceXSubsampling = 3;
int sourceYSubsampling = 4;
int subsamplingXOffset = 1;
int subsamplingYOffset = 2;
Rectangle sourceRegion = new Rectangle(1, 2, 42, 43);
int[] sourceBands = { 0, 1, 2 };
Point destinationOffset = new Point(7, 9);
String compressionType = "Foo";
float quality = 0.42f;
ImageWriteParam sourceParam = new ImageWriteParam() {
{
canWriteProgressive = true;
canWriteTiles = true;
canOffsetTiles = true;
canWriteCompressed = true;
compressionTypes = new String[] { "Foo", "Bar" };
}
};
sourceParam.setSourceRegion(sourceRegion);
sourceParam.setSourceSubsampling(sourceXSubsampling, sourceYSubsampling, subsamplingXOffset, subsamplingYOffset);
sourceParam.setSourceBands(sourceBands);
sourceParam.setDestinationOffset(destinationOffset);
sourceParam.setProgressiveMode(ImageWriteParam.MODE_DEFAULT); // Default is COPY_FROM_METADATA...
sourceParam.setTilingMode(ImageWriteParam.MODE_EXPLICIT);
sourceParam.setTiling(1, 2, 3, 4);
sourceParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
sourceParam.setCompressionType(compressionType);
sourceParam.setCompressionQuality(quality);
FooImageWriteParam fooParam = IIOUtil.copyStandardParams(sourceParam, new FooImageWriteParam());
assertEquals(sourceRegion, fooParam.getSourceRegion());
assertEquals(sourceXSubsampling, fooParam.getSourceXSubsampling());
assertEquals(sourceYSubsampling, fooParam.getSourceYSubsampling());
assertEquals(subsamplingXOffset, fooParam.getSubsamplingXOffset());
assertEquals(subsamplingYOffset, fooParam.getSubsamplingYOffset());
assertArrayEquals(sourceBands, fooParam.getSourceBands());
assertEquals(destinationOffset, fooParam.getDestinationOffset());
assertEquals(ImageWriteParam.MODE_DEFAULT, fooParam.getProgressiveMode());
assertEquals(ImageWriteParam.MODE_EXPLICIT, fooParam.getTilingMode());
assertEquals(1, fooParam.getTileWidth());
assertEquals(2, fooParam.getTileHeight());
assertEquals(3, fooParam.getTileGridXOffset());
assertEquals(4, fooParam.getTileGridYOffset());
assertEquals(ImageWriteParam.MODE_EXPLICIT, fooParam.getCompressionMode());
assertEquals(compressionType, fooParam.getCompressionType());
assertEquals(quality, fooParam.getCompressionQuality());
}
// A basic param that supports "everything"
static class FooImageWriteParam extends ImageWriteParam {
FooImageWriteParam() {
canWriteProgressive = true;
canWriteTiles = true;
canOffsetTiles = true;
canWriteCompressed = true;
compressionType = "Unset";
compressionTypes = new String[] { "Bar", "Foo" };
}
}
}
@@ -34,6 +34,7 @@ import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.mockito.InOrder;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
@@ -84,6 +85,7 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
protected static BufferedImage drawSomething(final BufferedImage image) {
Graphics2D g = image.createGraphics();
try {
int width = image.getWidth();
int height = image.getHeight();
@@ -131,18 +133,54 @@ public abstract class ImageWriterAbstractTest<T extends ImageWriter> {
public void testWrite() throws IOException {
ImageWriter writer = createWriter();
for (RenderedImage testData : getTestData()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try {
for (RenderedImage testData : getTestData()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
writer.write(drawSomething((BufferedImage) testData));
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
writer.write(drawSomething((BufferedImage) testData));
}
catch (IOException e) {
throw new AssertionError(e.getMessage(), e);
}
assertTrue(buffer.size() > 0, "No image data written");
}
catch (IOException e) {
throw new AssertionError(e.getMessage(), e);
}
finally {
writer.dispose();
}
}
@Test
public void testWriteRaster() throws IOException {
ImageWriter writer = createWriter();
try {
if (!writer.canWriteRasters()) {
return;
}
assertTrue(buffer.size() > 0, "No image data written");
ImageWriteParam param = writer.getDefaultWriteParam();
for (RenderedImage testData : getTestData()) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
writer.write(null, new IIOImage(testData.getTile(0, 0), null, null), param);
}
catch (IOException e) {
throw new AssertionError(e.getMessage(), e);
}
assertTrue(buffer.size() > 0, "No image data written");
}
}
finally {
writer.dispose();
}
}
@@ -0,0 +1,89 @@
package com.twelvemonkeys.imageio.util;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
class SequenceSupportTest {
@Test
void happyCase() {
SequenceSupport sequence = new SequenceSupport();
sequence.start();
assertEquals(0, sequence.current());
for (int i = 0; i < Byte.MAX_VALUE; i++) {
assertEquals(i, sequence.advance());
assertEquals(i + 1, sequence.current());
}
assertEquals(127, sequence.end());
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::advance);
}
@Test
void reset() {
SequenceSupport sequence = new SequenceSupport();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
sequence.start();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
sequence.start();
sequence.advance();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
sequence.start();
sequence.end();
sequence.reset();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
}
@Test
void startEnd() {
SequenceSupport sequence = new SequenceSupport();
sequence.start();
sequence.end();
assertEquals(-1, sequence.current());
assertThrows(IllegalStateException.class, sequence::end);
}
@Test
void startAlreadyStarted() {
SequenceSupport sequence = new SequenceSupport();
sequence.start();
assertThrows(IllegalStateException.class, sequence::start);
}
@Test
void advanceNotStarted() {
SequenceSupport sequence = new SequenceSupport();
assertThrows(IllegalStateException.class, sequence::advance);
}
@Test
void currentNotStarted() {
SequenceSupport sequence = new SequenceSupport();
assertEquals(-1, sequence.current());
}
@Test
void endNotStarted() {
SequenceSupport sequence = new SequenceSupport();
assertThrows(IllegalStateException.class, sequence::end);
}
}
+4 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-dds</artifactId>
<name>TwelveMonkeys :: ImageIO :: DDS plugin</name>
<name>TwelveMonkeys » ImageIO » DDS plugin</name>
<description>
ImageIO plugin for Microsoft Direct DrawSurface (DDS).
</description>
@@ -43,6 +43,8 @@
<Provide-Capability>
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageReaderSpi
osgi.serviceloader;
osgi.serviceloader=javax.imageio.spi.ImageWriterSpi
</Provide-Capability>
</instructions>
</configuration>
@@ -0,0 +1,11 @@
package com.twelvemonkeys.imageio.plugins.dds;
enum BlockCompression {
BC1,
BC2,
BC3,
BC4,
BC5,
// BC6H,
// BC7
}
@@ -30,25 +30,35 @@
package com.twelvemonkeys.imageio.plugins.dds;
@SuppressWarnings("unused")
interface DDS {
int MAGIC = ('D' << 24) + ('D' << 16) + ('S' << 8) + ' '; //Big-Endian
int MAGIC = 'D' + ('D' << 8) + ('S' << 16) + (' ' << 24); // Little-Endian
int HEADER_SIZE = 124;
int PIXELFORMAT_SIZE = 32;
// Header Flags
int FLAG_CAPS = 0x1; // Required in every .dds file.
int FLAG_HEIGHT = 0x2; // Required in every .dds file.
int FLAG_WIDTH = 0x4; // Required in every .dds file.
int FLAG_PITCH = 0x8; // Required when pitch is provided for an uncompressed texture.
int FLAG_PIXELFORMAT = 0x1000; // Required in every .dds file.
int FLAG_MIPMAPCOUNT = 0x20000; // Required in a mipmapped texture.
int FLAG_LINEARSIZE = 0x80000; // Required when pitch is provided for a compressed texture.
int FLAG_DEPTH = 0x800000; // Required in a depth texture.
int FLAG_CAPS = 1; // Required in every .dds file.
int FLAG_HEIGHT = 1 << 1; // Required in every .dds file.
int FLAG_WIDTH = 1 << 2; // Required in every .dds file.
int FLAG_PIXELFORMAT = 1 << 12; // Required in every .dds file.
int FLAG_PITCH = 1 << 3; // Required when pitch is provided for an uncompressed texture.
int FLAG_MIPMAPCOUNT = 1 << 17; // Required in a mipmapped texture.
int FLAG_LINEARSIZE = 1 << 19; // Required when pitch is provided for a compressed texture.
int FLAG_DEPTH = 1 << 23; // Required in a depth texture.
// Pixel Format Flags
int PIXEL_FORMAT_FLAG_ALPHAPIXELS = 0x1;
int PIXEL_FORMAT_FLAG_ALPHA = 0x2;
int PIXEL_FORMAT_FLAG_FOURCC = 0x04;
int PIXEL_FORMAT_FLAG_RGB = 0x40;
//DX10 Resource Dimensions
int D3D10_RESOURCE_DIMENSION_TEXTURE2D = 3;
//dwCaps
int DDSCAPS_COMPLEX = 0x8;
int DDSCAPS_MIPMAP = 0x400000;
int DDSCAPS_TEXTURE = 0x1000;
}
@@ -30,15 +30,29 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A1R5G5B5_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A4R4G4B4_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A8B8G8R8_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.A8R8G8B8_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.R5G6B5_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.R8G8B8_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X1R5G5B5_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X4R4G4B4_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X8B8G8R8_MASKS;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.X8R8G8B8_MASKS;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.awt.Dimension;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.Arrays;
/**
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header">DDS_HEADER structure</a>
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide">Programming Guide for DDS</a>
*/
final class DDSHeader {
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide
private int flags;
private int mipMapCount;
@@ -52,20 +66,12 @@ final class DDSHeader {
private int blueMask;
private int alphaMask;
DXT10Header dxt10Header;
@SuppressWarnings("unused")
static DDSHeader read(final ImageInputStream imageInput) throws IOException {
DDSHeader header = new DDSHeader();
// Read MAGIC bytes [0,3]
imageInput.setByteOrder(ByteOrder.BIG_ENDIAN);
int magic = imageInput.readInt();
if (magic != DDS.MAGIC) {
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
}
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
// DDS_HEADER structure
// https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header
int dwSize = imageInput.readInt(); // [4,7]
if (dwSize != DDS.HEADER_SIZE) {
throw new IIOException(String.format("Invalid DDS header size (expected %d): %d", DDS.HEADER_SIZE, dwSize));
@@ -73,11 +79,9 @@ final class DDSHeader {
// Verify flags
header.flags = imageInput.readInt(); // [8,11]
if (!header.getFlag(DDS.FLAG_CAPS
| DDS.FLAG_HEIGHT
| DDS.FLAG_WIDTH
| DDS.FLAG_PIXELFORMAT)) {
throw new IIOException("Required DDS Flag missing in header: " + Integer.toBinaryString(header.flags));
if (!header.hasFlag(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT)) {
// NOTE: The Microsoft DDS documentation mention that readers should not rely on these flags...
throw new IIOException("Required DDS flag missing in header: " + Integer.toBinaryString(header.flags));
}
// Read Height & Width
@@ -97,6 +101,9 @@ final class DDSHeader {
// DDS_PIXELFORMAT structure
int px_dwSize = imageInput.readInt(); // [76,79]
if (px_dwSize != DDS.PIXELFORMAT_SIZE) {
throw new IIOException(String.format("Invalid DDS pixel format structure size (expected %d): %d", DDS.PIXELFORMAT_SIZE, dwSize));
}
header.pixelFormatFlags = imageInput.readInt(); // [80,83]
header.fourCC = imageInput.readInt(); // [84,87]
@@ -113,6 +120,11 @@ final class DDSHeader {
int dwReserved2 = imageInput.readInt(); // [124,127]
if (header.fourCC == DDSType.DXT10.fourCC()) {
// If DXT10, the DXT10 header will follow immediately
header.dxt10Header = DXT10Header.read(imageInput);
}
return header;
}
@@ -128,8 +140,8 @@ final class DDSHeader {
}
}
private boolean getFlag(int mask) {
return (flags & mask) != 0;
private boolean hasFlag(int mask) {
return (flags & mask) == mask;
}
int getWidth(int imageIndex) {
@@ -146,31 +158,101 @@ final class DDSHeader {
return mipMapCount;
}
int getBitCount() {
return bitCount;
DDSType getType() throws IIOException {
if (dxt10Header != null) {
return dxt10Header.getType();
}
return getRawType();
}
int getFourCC() {
return fourCC;
DDSType getRawType() throws IIOException {
if ((pixelFormatFlags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
// DXT
return DDSType.fromFourCC(fourCC);
}
else if ((pixelFormatFlags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
// RGB
int alphaMask = ((pixelFormatFlags & 0x01) != 0) ? this.alphaMask : 0; // 0x01 alpha
if (bitCount == 16) {
if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
// A1R5G5B5
return DDSType.A1R5G5B5;
}
else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
// X1R5G5B5
return DDSType.X1R5G5B5;
}
else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
// A4R4G4B4
return DDSType.A4R4G4B4;
}
else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
// X4R4G4B4
return DDSType.X4R4G4B4;
}
else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
// R5G6B5
return DDSType.R5G6B5;
}
throw new IIOException("Unsupported 16bit RGB image.");
}
else if (bitCount == 24) {
if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
// R8G8B8
return DDSType.R8G8B8;
}
throw new IIOException("Unsupported 24bit RGB image.");
}
else if (bitCount == 32) {
if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
// A8B8G8R8
return DDSType.A8B8G8R8;
}
else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
// X8B8G8R8
return DDSType.X8B8G8R8;
}
else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
// A8R8G8B8
return DDSType.A8R8G8B8;
}
else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
// X8R8G8B8
return DDSType.X8R8G8B8;
}
throw new IIOException("Unsupported 32bit RGB image.");
}
throw new IIOException("Unsupported bit count: " + bitCount);
}
throw new IIOException("Unsupported YUV or LUMINANCE image.");
}
int getPixelFormatFlags() {
return pixelFormatFlags;
@Override
public String toString() {
return "DDSHeader{" +
"flags=" + Integer.toBinaryString(flags) +
", mipMapCount=" + mipMapCount +
", dimensions=" + Arrays.toString(Arrays.stream(dimensions)
.map(DDSHeader::dimensionToString)
.toArray(String[]::new)) +
", pixelFormatFlags=" + Integer.toBinaryString(pixelFormatFlags) +
", fourCC=" + fourCC +
", bitCount=" + bitCount +
", redMask=" + redMask +
", greenMask=" + greenMask +
", blueMask=" + blueMask +
", alphaMask=" + alphaMask +
'}';
}
int getRedMask() {
return redMask;
}
int getGreenMask() {
return greenMask;
}
int getBlueMask() {
return blueMask;
}
int getAlphaMask() {
return alphaMask;
private static String dimensionToString(Dimension dimension) {
return String.format("%dx%d", dimension.width, dimension.height);
}
}
@@ -0,0 +1,477 @@
package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.stream.ImageOutputStream;
import java.awt.Color;
import java.awt.image.Raster;
import java.io.IOException;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.ARGB_ORDER;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.BIT5;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.BIT6;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.RGB_16_ORDER;
/**
* A designated class to encode image data to binary.
*
* @see <a href="https://www.ludicon.com/castano/blog/2009/03/gpu-dxt-decompression/">GPU DXT Decompression</a>.
* @see <a href="https://sv-journal.org/2014-1/06/en/index.php">TEXTURE COMPRESSION TECHNIQUES</a>.
* @see <a href="https://mrelusive.com/publications/papers/Real-Time-Dxt-Compression.pdf">Real-Time DXT Compression by J.M.P. van Waveren</a>
* @see <a href="https://registry.khronos.org/DataFormat/specs/1.4/dataformat.1.4.pdf">Khronos Data Format Specification v1.4 by Andrew Garrard</a>
*/
class DDSImageDataEncoder {
private DDSImageDataEncoder() {}
//A cap for alpha value for BC1 where if alpha value is smaller than this, the 4x4 block will enable alpha mode.
private static final int BC1_ALPHA_CAP = 124;
private static final int BC4_CHANNEL_RED = 0; //default for BC4.
private static final int BC4_CHANNEL_ALPHA = 3; //BC3 reuses algorithm from BC4 but uses alpha channelIndex for sampling.
private static final int BC4_CHANNEL_GREEN = 1; //same re-usage as BC3 but for green channel BC5 uses
static void writeImageData(ImageOutputStream imageOutput, Raster raster, BlockCompression compression) throws IOException {
// TODO: Support compression == null for uncompressed RGB(A/X) data?
switch (compression) {
case BC1:
new BlockCompressor1(false).encode(imageOutput, raster);
break;
case BC2:
new BlockCompressor2().encode(imageOutput, raster);
break;
case BC3:
new BlockCompressor3().encode(imageOutput, raster);
break;
case BC4:
new BlockCompressor4(BC4_CHANNEL_RED).encode(imageOutput, raster);
break;
case BC5:
new BlockCompressor5().encode(imageOutput, raster);
break;
default:
throw new IllegalArgumentException("DDS block compression is not supported yet: " + compression);
}
}
private static class BlockCompressor1 extends BlockCompressorBase {
private final boolean forceOpaque;
//color0,1 : space 565
//color2,3 : space 888
private final int[] palettes;
private final MutableColor[] color32s;
private BlockCompressor1(boolean forceOpaque) {
super();
this.forceOpaque = forceOpaque;
palettes = new int[4];
color32s = new MutableColor[16];
for (int i = 0; i < 16; i++) {
color32s[i] = new MutableColor();
}
}
//pack 32 bits of the colors to a single int value.
private static int color888ToInt(int r, int g, int b, int a) {
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
void startEncodeBlock(ImageOutputStream imageOutput, int[] sampled) throws IOException {
boolean alphaMode = getBlockEndpoints(sampled, palettes);
imageOutput.writeShort((short) palettes[0]);
imageOutput.writeShort((short) palettes[1]);
//simulating color2,3
interpolate(alphaMode, palettes);
//indices encoding start.
int indices = encodeBlockIndices(alphaMode, sampled, palettes);
imageOutput.writeInt(indices);
}
//all palettes now in 8:8:8 space
int encodeBlockIndices(boolean alphaMode, int[] sampled, int[] palettes) {
int i = 0;
int colorPos = 0;
int indices = 0;
Color c0 = convertTo888(palettes[0]);
Color c1 = convertTo888(palettes[1]);
Color c2 = color888ToObject(palettes[2]);
Color c3 = color888ToObject(palettes[3]);
while (i < 64) {
Color c = setColorFor(colorPos, sampled[i++], sampled[i++], sampled[i++]);
byte index;
int a = sampled[i++];
if (alphaMode && isAlphaBelowCap(a)) {
index = 0b11;
} else {
double distance0 = calculateDistance(c, c0);
double distance1 = calculateDistance(c, c1);
double distance2 = calculateDistance(c, c2);
double distance3 = calculateDistance(c, c3);
index = getClosest(distance0, distance1, distance2, distance3);
}
indices |= (index << (colorPos * 2));
colorPos++;
}
return indices;
}
private Color setColorFor(int index, int r, int g, int b) {
color32s[index].setColor(r, g, b);
return color32s[index];
}
//color space 888
private static double calculateDistance(Color color1, Color color0) {
float r = Math.abs(color0.getRed() - color1.getRed());
float g = Math.abs(color0.getGreen() - color1.getGreen());
float b = Math.abs(color0.getBlue() - color1.getBlue());
return Math.sqrt(r * r + g * g + b * b);
}
private static byte getClosest(double d0, double d1, double d2, double d3) {
double min = Math.min(d0, Math.min(d1, Math.min(d2, d3)));
if (min == d0) return 0b00;
if (min == d1) return 0b01;
if (min == d2) return 0b10;
return 0b11;
}
//this method, we work in 888 space
@SuppressWarnings("DuplicatedCode")
//just in case intellij warns for 'duplication'
void interpolate(boolean alphaMode, int[] palettes) {
Color rgb0 = convertTo888(palettes[0]);
Color rgb1 = convertTo888(palettes[1]);
int rgb2;
int rgb3;
if (alphaMode) {
//alpha mode
int r2 = (rgb0.getRed() + rgb1.getRed()) / 2;
int g2 = (rgb0.getGreen() + rgb1.getGreen()) / 2;
int b2 = (rgb0.getBlue() + rgb1.getBlue()) / 2;
rgb2 = color888ToInt(r2, g2, b2, 0xff);
rgb3 = 0;
} else {
//opaque mode
int r2 = (2 * rgb0.getRed() + rgb1.getRed()) / 3;
int g2 = (2 * rgb0.getGreen() + rgb1.getGreen()) / 3;
int b2 = (2 * rgb0.getBlue() + rgb1.getBlue()) / 3;
rgb2 = color888ToInt(r2, g2, b2, 0xff);
int r3 = (rgb0.getRed() + 2 * rgb1.getRed()) / 3;
int g3 = (rgb0.getGreen() + 2 * rgb1.getGreen()) / 3;
int b3 = (rgb0.getBlue() + 2 * rgb1.getBlue()) / 3;
rgb3 = color888ToInt(r3, g3, b3, 0xff);
}
palettes[2] = rgb2;
palettes[3] = rgb3;
}
//this method, we work in 888 space, return color0&1 in 565 space
boolean getBlockEndpoints(int[] sampledColors, int[] paletteBuffer) {
if (sampledColors.length != 64)
throw new IllegalStateException("Unintended behaviour, expecting sampled colors of block to be 64, got " + sampledColors.length);
int minR = 0xff;
int minG = 0xff;
int minB = 0xff;
int maxR = 0;
int maxG = 0;
int maxB = 0;
boolean alphaMode = false;
int i = 0;
while (i < 64) {
int r = sampledColors[i++];
int g = sampledColors[i++];
int b = sampledColors[i++];
int a = sampledColors[i++];
if (!forceOpaque && isAlphaBelowCap(a)) {
alphaMode = true;
continue;
}
minR = Math.min(minR, r);
minG = Math.min(minG, g);
minB = Math.min(minB, b);
maxR = Math.max(maxR, r);
maxG = Math.max(maxG, g);
maxB = Math.max(maxB, b);
}
int color0 = convertTo565(maxR, maxG, maxB);
int color1 = convertTo565(minR, minG, minB);
if ((alphaMode && color0 > color1) || (!alphaMode && color0 < color1)) {
paletteBuffer[0] = color1;
paletteBuffer[1] = color0;
} else {
paletteBuffer[0] = color0;
paletteBuffer[1] = color1;
}
return alphaMode;
}
//Reference [3] Page 7
boolean getBlockEndpoints2(int[] sampled, int[] paletteBuffer) {
int maxDistance = -1;
boolean alphaMode = false;
for (int i = 0; i < 60; i += 4) {
for (int j = i + 4; j < 64; j += 4) {
if (!forceOpaque && isAlphaBelowCap(Math.min(sampled[i + 3], sampled[j + 3]))) {
alphaMode = true;
continue;
}
int distance = getColorDistance(sampled[i], sampled[i + 1], sampled[i + 2], sampled[j], sampled[j + 1], sampled[j + 2]);
if (distance > maxDistance) {
maxDistance = distance;
paletteBuffer[0] = convertTo565(sampled[i], sampled[i + 1], sampled[i + 2]);
paletteBuffer[1] = convertTo565(sampled[j], sampled[j + 1], sampled[j + 2]);
}
}
}
if ((alphaMode && paletteBuffer[0] > paletteBuffer[1]) || (!alphaMode && paletteBuffer[1] > paletteBuffer[0])) {
int a = paletteBuffer[0];
paletteBuffer[0] = paletteBuffer[1];
paletteBuffer[1] = a;
}
return alphaMode;
}
private static int getColorDistance(int r1, int g1, int b1, int r2, int g2, int b2) {
int r3 = r1 - r2;
int g3 = g1 - g2;
int b3 = b1 - b2;
return r3 * r3 + g3 * g3 + b3 * b3;
}
private static Color convertTo888(int c565) {
int r8 = BIT5[(c565 & 0xF800) >> 11];
int g8 = BIT6[(c565 & 0x07E0) >> 5];
int b8 = BIT5[(c565 & 0x001F)];
return new Color(r8, g8, b8, 0xff);
}
private static Color color888ToObject(int c888) {
return new Color(
(c888 & 0xFF0000) >> ARGB_ORDER.redShift,
(c888 & 0x00FF00) >> ARGB_ORDER.greenShift,
(c888 & 0x0000FF) >> ARGB_ORDER.blueShift,
(c888) >>> ARGB_ORDER.alphaShift
);
}
}
private static final class BlockCompressor2 extends BlockCompressor1 {
private BlockCompressor2() {
super(true);
}
@Override
void startEncodeBlock(ImageOutputStream imageOutput, int[] sampled) throws IOException {
//write 64 bit alpha first (4 bit alpha per pixel)
long alphaData = 0;
for (int i = 0; i < 16; i++) {
int alpha = sampled[i * 4 + 3] >> 4;
alphaData |= ((long) alpha) << (i * 4);
}
imageOutput.writeLong(alphaData);
super.startEncodeBlock(imageOutput, sampled);
}
}
private static final class BlockCompressor3 extends BlockCompressor1 {
private final BlockCompressor4 bc4;
private BlockCompressor3() {
super(true);
bc4 = new BlockCompressor4(BC4_CHANNEL_ALPHA);
}
@Override
void startEncodeBlock(ImageOutputStream imageOutput, int[] sampled) throws IOException {
bc4.startEncodeBlock(imageOutput, sampled);
super.startEncodeBlock(imageOutput, sampled);
}
}
private static final class BlockCompressor4 extends BlockCompressorBase {
private final int channelIndex;
private final int[] reds;
private BlockCompressor4(int channelIndex) {
super();
this.channelIndex = channelIndex;
this.reds = new int[8];
}
void startEncodeBlock(ImageOutputStream imageOutput, int[] samples) throws IOException {
getColorRange(samples, reds);
interpolate(reds);
long data = calculateIndices(samples, reds);
data |= (((long) (reds[1] & 0xff) << 8) | (reds[0] & 0xff));
imageOutput.writeLong(data);
}
// 6 bytes MSB will be for indices, the LSB is for the 2 red endpoints,
// as we write to file in LE the bytes will be swapped back to the desired order
private long calculateIndices(int[] samples, int[] reds) {
long data = 0;
for (int i = 0; i < 16; i++) {
int index;
int rSample = samples[i * 4 + channelIndex];
index = getNearest(rSample, reds);
data |= ((long) index << (16 + i * 3));
}
return data;
}
private int getNearest(int r, int[] reds) {
int nearest = 0;
int nearestValue = 255;
for (int i = 0; i < 8; i++) {
int v = Math.abs(r - reds[i]);
if (nearestValue > v) {
nearest = i;
nearestValue = v;
}
}
return nearest;
}
private void interpolate(int[] reds) {
int r0 = reds[0];
int r1 = reds[1];
for (int i = 0; i < 8; i++) {
reds[i] = DDSReader.getDXT5Alpha(r0, r1, i);
}
}
//r0 > r1 : use 6 interpolated color values
//r0 <= r1 : use 4
private void getColorRange(int[] samples, int[] red01) {
int r0 = 0;
int r1 = 255;
for (int i = 0; i < 16; i++) {
int r = samples[i * 4 + channelIndex];
r0 = Math.max(r0, r);
r1 = Math.min(r1, r);
}
red01[0] = r0;
red01[1] = r1;
}
}
private static final class BlockCompressor5 extends BlockCompressorBase {
private final BlockCompressor4 bc4r;
private final BlockCompressor4 bc4g;
public BlockCompressor5() {
bc4r = new BlockCompressor4(BC4_CHANNEL_RED);
bc4g = new BlockCompressor4(BC4_CHANNEL_GREEN);
}
@Override
void startEncodeBlock(ImageOutputStream imageOutput, int[] samples) throws IOException {
bc4r.startEncodeBlock(imageOutput, samples);
bc4g.startEncodeBlock(imageOutput, samples);
}
}
//https://rgbcolorpicker.com/565
//pack 32 bits color into a single 5:6:5 16bits value
static int convertTo565(int r8, int g8, int b8) {
int r5 = (r8 >> 3);
int g6 = (g8 >> 2);
int b5 = (b8 >> 3);
return color565ToInt(r5, g6, b5);
}
//pack 16 bits of the colors to a single int value.
private static int color565ToInt(int r5, int g6, int b5) {
return (r5 << RGB_16_ORDER.redShift) | (g6 << RGB_16_ORDER.greenShift) | (b5 << RGB_16_ORDER.blueShift);
}
private abstract static class BlockCompressorBase {
final int[] samples;
BlockCompressorBase() {
this.samples = new int[64];
}
//workaround for 24 dpi (no alpha) -> 32dpi (with alpha default to 0xff)
//as this mess the color0 & color1 up spectacularly bc alpha is not present in 24dpi
private static void adjustSampledBands(Raster raster, int[] samples) {
if (raster.getNumBands() == 4) return;
for (int i = 15; i >= 0; i--) {
int r24Index = i * 3;
int r32Index = i * 4;
samples[r32Index + 3] = 0xFF;
samples[r32Index + 2] = samples[r24Index + 2]; //b24 -> b32
samples[r32Index + 1] = samples[r24Index + 1]; //g24 -> g32
samples[r32Index] = samples[r24Index]; //r24 -> r32
}
}
void encode(ImageOutputStream imageOutput, Raster raster) throws IOException {
int blocksXCount = (raster.getWidth() + 3) / 4;
int blocksYCount = (raster.getHeight() + 3) / 4;
for (int blockY = 0; blockY < blocksYCount; blockY++) {
for (int blockX = 0; blockX < blocksXCount; blockX++) {
raster.getPixels(blockX * 4, blockY * 4, 4, 4, samples);
adjustSampledBands(raster, samples);
startEncodeBlock(imageOutput, samples);
}
}
}
boolean isAlphaBelowCap(int alpha) {
return alpha < BC1_ALPHA_CAP;
}
abstract void startEncodeBlock(ImageOutputStream imageOutput, int[] samples) throws IOException;
}
private static final class MutableColor extends Color {
int mutableValue;
public MutableColor() {
super(0, 0, 0);
this.mutableValue = 0;
}
void setColor(int red, int green, int blue) {
mutableValue = red << ARGB_ORDER.redShift;
mutableValue |= green << ARGB_ORDER.greenShift;
mutableValue |= blue << ARGB_ORDER.blueShift;
}
@Override
public int getRGB() {
return this.mutableValue;
}
//intellij generated
@Override
public boolean equals(Object object) {
if (!(object instanceof MutableColor)) return false;
if (!super.equals(object)) return false;
MutableColor that = (MutableColor) object;
return mutableValue == that.mutableValue;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + mutableValue;
return result;
}
}
}
@@ -30,29 +30,51 @@
package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.ImageTypeSpecifier;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
final class DDSMetadata extends StandardImageMetadataSupport {
DDSMetadata(ImageTypeSpecifier type, DDSHeader header) {
super(builder(type)
.withCompressionTypeName(compressionName(header))
.withFormatVersion("1.0")
import javax.imageio.ImageTypeSpecifier;
final class DDSImageMetadata extends StandardImageMetadataSupport {
DDSImageMetadata(ImageTypeSpecifier specifier, DDSType type) {
super(builder(specifier)
.withCompressionTypeName(compressionName(type))
.withCompressionLossless(!type.isBlockCompression())
.withBitsPerSample(bitsPerSample(type))
.withFormatVersion("1.0")
);
}
private static String compressionName(DDSHeader header) {
// If the fourCC is valid, compression is one of the DXTn versions, otherwise None
int flags = header.getPixelFormatFlags();
if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
// DXTn
DDSType type = DDSType.valueOf(header.getFourCC());
private static String compressionName(DDSType type) {
if (type != null && type.isFourCC()) {
return type.name();
}
return "None";
}
private static int[] bitsPerSample(DDSType type) {
if (type.isBlockCompression()) {
return null; // Use defaults
}
int[] bitsPerSample = new int[4];
for (int i = 0; i < bitsPerSample.length; i++) {
bitsPerSample[i] = countMaskBits(type.rgbaMasks[i]);
}
return bitsPerSample;
}
private static int countMaskBits(int mask) {
// See https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetKernighan
int count;
for (count = 0; mask != 0; count++) {
mask &= mask - 1; // clear the least significant bit set
}
return count;
}
}
@@ -30,18 +30,16 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
@@ -49,6 +47,14 @@ import java.nio.ByteOrder;
import java.util.Collections;
import java.util.Iterator;
import static com.twelvemonkeys.imageio.util.IIOUtil.subsampleRow;
/**
* ImageReader implementation for Microsoft DirectDraw Surface (DDS) format.
*
* @author Paul Allen
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public final class DDSImageReader extends ImageReaderBase {
private DDSHeader header;
@@ -91,7 +97,16 @@ public final class DDSImageReader extends ImageReaderBase {
checkBounds(imageIndex);
readHeader();
// TODO: Implement for the specific formats...
DDSType type = header.getType();
if (!type.isBlockCompression() && type.rgbaMasks[3] == 0) {
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB);
}
// TODO: DXT1 can have 1 bit alpha, usually don't...
// DXT3/5 have alpha
// DXT2/4 ...?
return ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB);
}
@@ -147,14 +162,19 @@ public final class DDSImageReader extends ImageReaderBase {
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
ImageTypeSpecifier imageType = getRawImageType(imageIndex);
return new DDSMetadata(imageType, header);
return new DDSImageMetadata(imageType, header.getType());
}
private void readHeader() throws IOException {
if (header == null) {
imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
header = DDSHeader.read(imageInput);
int magic = imageInput.readInt();
if (magic != DDS.MAGIC) {
throw new IIOException(String.format("Not a DDS file. Expected DDS magic 0x%8x', read 0x%8x", DDS.MAGIC, magic));
}
header = DDSHeader.read(imageInput);
imageInput.flushBefore(imageInput.getStreamPosition());
}
@@ -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 DDSImageReaderSpi extends ImageReaderSpiBase {
@@ -52,10 +53,15 @@ public final class DDSImageReaderSpi extends ImageReaderSpiBase {
ImageInputStream stream = (ImageInputStream) source;
stream.mark();
ByteOrder byteOrder = stream.getByteOrder();
try {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
return stream.readInt() == DDS.MAGIC;
} finally {
}
finally {
stream.setByteOrder(byteOrder);
stream.reset();
}
}
@@ -67,6 +73,6 @@ public final class DDSImageReaderSpi extends ImageReaderSpiBase {
@Override
public String getDescription(Locale locale) {
return "Direct DrawSurface (DDS) Image Reader";
return "DirectDraw Surface (DDS) Image Reader";
}
}
@@ -0,0 +1,62 @@
package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.ImageWriteParam;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public final class DDSImageWriteParam extends ImageWriteParam {
static final DDSType DEFAULT_TYPE = DDSType.DXT5;
private static final String[] COMPRESSION_TYPES = compressionTypes();
private static String[] compressionTypes() {
// TODO: Maybe hardcode subset of values that we actually support writing?
List<String> compressionTypes = Arrays.stream(DDSType.values())
.filter(DDSType::isBlockCompression)
.map(Enum::name)
.collect(Collectors.toList());
compressionTypes.add(0, "None");
return compressionTypes.toArray(new String[0]);
}
private boolean writeDXT10;
DDSImageWriteParam() {
canWriteCompressed = true;
compressionTypes = COMPRESSION_TYPES;
compressionType = DEFAULT_TYPE.name();
}
public void setWriteDX10() {
writeDXT10 = true;
}
public void clearWriteDX10() {
writeDXT10 = false;
}
public boolean isWriteDXT10() {
return writeDXT10;
}
DDSType type() {
if (compressionType == null || compressionType.equals("None")) {
return null;
}
return DDSType.valueOf(compressionType);
}
int getDxgiFormat() {
DDSType type = type();
if (type != null) {
return type.dxgiFormat();
}
return DXGI.DXGI_FORMAT_UNKNOWN;
}
}
@@ -0,0 +1,330 @@
package com.twelvemonkeys.imageio.plugins.dds;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.awt.Dimension;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Paths;
/**
* ImageWriter implementation for Microsoft DirectDraw Surface (DDS) format.
*
* @author KhanTypo
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
class DDSImageWriter extends ImageWriterBase {
private final SequenceSupport mipmapSequence = new SequenceSupport();
private long headerStartPos;
private DDSType mipmapType;
private Dimension mipmapDimension;
protected DDSImageWriter(ImageWriterSpi provider) {
super(provider);
}
@Override
public DDSImageWriteParam getDefaultWriteParam() {
return new DDSImageWriteParam();
}
@Override
protected void resetMembers() {
headerStartPos = 0;
mipmapSequence.reset();
mipmapType = null;
mipmapDimension = null;
}
@Override
public boolean canWriteRasters() {
return true;
}
@Override
public boolean canWriteSequence() {
return true;
}
@Override
public void prepareWriteSequence(IIOMetadata streamMetadata) throws IOException {
assertOutput();
mipmapSequence.start();
imageOutput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
imageOutput.writeInt(DDS.MAGIC);
imageOutput.flush();
headerStartPos = imageOutput.getStreamPosition();
}
@Override
public void endWriteSequence() throws IOException {
int mipmapCount = mipmapSequence.end();
// Go back and update header
updateHeader(mipmapCount);
mipmapType = null;
mipmapDimension = null;
imageOutput.flush();
}
@Override
public void write(IIOMetadata streamMetadata, IIOImage image, ImageWriteParam param) throws IOException {
prepareWriteSequence(streamMetadata);
writeToSequence(image, param);
endWriteSequence();
}
@Override
public void writeToSequence(IIOImage image, ImageWriteParam param) throws IOException {
int mipmapIndex = mipmapSequence.advance();
Raster raster = getRaster(image);
ensureImageChannels(raster);
ensureTextureDimension(raster);
mipmapDimension = new Dimension(raster.getWidth(), raster.getHeight());
DDSImageWriteParam ddsParam = param instanceof DDSImageWriteParam
? ((DDSImageWriteParam) param)
: IIOUtil.copyStandardParams(param, getDefaultWriteParam());
DDSType type = ddsParam.type();
if (mipmapType == null) {
mipmapType = type;
}
else if (type != mipmapType) {
processWarningOccurred(mipmapIndex, "All images in DDS mipmap must use same pixel format and compression");
}
if (mipmapType == null) {
throw new IIOException("Only compressed DDS using DXT1-5 or DXT10 with block compression is currently supported");
}
if (mipmapIndex == 0) {
writeHeader(raster.getWidth(), raster.getHeight(), mipmapType, ddsParam.isWriteDXT10());
if (ddsParam.isWriteDXT10()) {
writeDXT10Header(ddsParam.getDxgiFormat());
}
}
processImageStarted(mipmapIndex);
processImageProgress(0f);
DDSImageDataEncoder.writeImageData(imageOutput, raster, mipmapType.compression);
processImageProgress(100f);
processImageComplete();
}
private static Raster getRaster(IIOImage image) throws IIOException {
if (image.hasRaster()) {
return image.getRaster();
}
else {
RenderedImage renderedImage = image.getRenderedImage();
if (renderedImage.getNumXTiles() != 1 || renderedImage.getNumYTiles() != 1) {
throw new IIOException("Only single tile images supported");
}
return renderedImage.getTile(0, 0);
}
}
/**
* Checking if the image has 3 channels (RGB) or 4 channels (RGBA) and if image has 8 bits/channel.
*
* @see DDSImageWriterSpi#canEncodeImage(ImageTypeSpecifier)
*/
private void ensureImageChannels(Raster data) throws IIOException {
int numBands = data.getNumBands();
if (numBands < 3 || numBands > 4) {
throw new IIOException(
"Only image with 3 channels (RGB) or 4 channels (RGBA) is supported, got " + numBands + " channels");
}
int sampleSize = data.getSampleModel().getSampleSize(0);
if (sampleSize != 8) {
throw new IIOException("Only image with 8 bits/channel is supported, got " + sampleSize);
}
}
/**
* Checking if an image can be evenly divided into blocks of 4x4, ideally a power of 2.
* e.g. 16x16, 32x32, 512x128, 512x512, 1024x512, 1024x1024, 2048x1024...
*/
private void ensureTextureDimension(Raster raster) throws IIOException {
int width = raster.getWidth();
int height = raster.getHeight();
// Should also allow mipmaps 2x2 and 1x1?
if (width % 4 != 0 || height % 4 != 0) {
throw new IIOException(String.format("Image dimensions must be dividable by 4, ideally a power of 2; got %dx%d", width, height));
}
if (mipmapDimension != null && (mipmapDimension.width != width * 2|| mipmapDimension.height != height * 2)) {
throw new IIOException(
String.format("For mipmap, image dimensions must be exactly half of previous (%dx%d); got %dx%d",
mipmapDimension.width, mipmapDimension.height, width, height)
);
}
}
private void writeHeader(int width, int height, DDSType type, boolean writeDXT10) throws IOException {
imageOutput.writeInt(DDS.HEADER_SIZE);
int linearSizeOrPitch = type.isBlockCompression() ? DDS.FLAG_LINEARSIZE : DDS.FLAG_PITCH;
imageOutput.writeInt(DDS.FLAG_CAPS | DDS.FLAG_HEIGHT | DDS.FLAG_WIDTH | DDS.FLAG_PIXELFORMAT | linearSizeOrPitch);
imageOutput.writeInt(height);
imageOutput.writeInt(width);
writePitchOrLinearSize(height, width, type);
//dwDepth
imageOutput.writeInt(0);
//dwMipmapCount
imageOutput.writeInt(1); // Should probably write 0 here for non-mipmap?
//reserved
imageOutput.write(new byte[44]);
//pixFmt
writePixelFormat(type, writeDXT10);
//dwCaps, right now we keep it simple by only using DDSCAP_TEXTURE as it is required.
imageOutput.writeInt(DDS.DDSCAPS_TEXTURE);
//dwCaps2, unused for now as we are not working with cube maps
imageOutput.writeInt(0);
//dwCaps3, dwCaps4, dwReserved2 : 3 unused integers
imageOutput.write(new byte[12]);
}
private void updateHeader(int mipmapCount) throws IOException {
if (mipmapCount == 1) {
// Fast case, nothing to do
return;
}
long streamPosition = imageOutput.getStreamPosition();
imageOutput.seek(headerStartPos + 4); // Seek back to header start, skip 4 byte header size
int flags = imageOutput.readInt();
imageOutput.seek(imageOutput.getStreamPosition() - 4);
imageOutput.writeInt(flags | DDS.FLAG_MIPMAPCOUNT);
imageOutput.seek(imageOutput.getStreamPosition() + 16);
imageOutput.writeInt(mipmapCount);
imageOutput.seek(streamPosition); // Restore pos
}
//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-pixelformat
private void writePixelFormat(DDSType type, boolean writeDXT10) throws IOException {
imageOutput.writeInt(DDS.PIXELFORMAT_SIZE);
writePixelFormatFlags(type, writeDXT10);
writeFourCC(type, writeDXT10);
writeRGBAData(type, writeDXT10);
}
private void writeDXT10Header(int dxgiFormat) throws IOException {
//dxgiFormat
imageOutput.writeInt(dxgiFormat);
//resourceDimension
imageOutput.writeInt(DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D);
//miscFlag
imageOutput.writeInt(0);
//arraySize
imageOutput.writeInt(1);
//miscFlag2
imageOutput.writeInt(0);
}
private void writeRGBAData(DDSType type, boolean writeDXT10) throws IOException {
if (!writeDXT10 && !type.isFourCC()) {
//dwRGBBitCount
imageOutput.writeInt(type.blockSize() * 8); // TODO: Is bitcount always a multiple of 8?
//dwRBitMask
imageOutput.writeInt(type.rgbaMasks[0]);
//dwGBitMask
imageOutput.writeInt(type.rgbaMasks[1]);
//dwBBitMask
imageOutput.writeInt(type.rgbaMasks[2]);
//dwABitMask
imageOutput.writeInt(type.rgbaMasks[3]);
}
else {
//write 5 zero integers as fourCC is used
imageOutput.write(new byte[20]);
}
}
private void writeFourCC(DDSType type, boolean writeDXT10) throws IOException {
if (writeDXT10) {
imageOutput.writeInt(DDSType.DXT10.fourCC());
}
else if (type.isFourCC()) {
imageOutput.writeInt(type.fourCC());
}
else {
// No fourCC, custom format...
imageOutput.writeInt(0);
}
}
private void writePixelFormatFlags(DDSType type, boolean writeDXT10) throws IOException {
if (writeDXT10 || type.isFourCC()) {
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_FOURCC);
}
else {
imageOutput.writeInt(DDS.PIXEL_FORMAT_FLAG_RGB
| (type.rgbaMasks != null && type.rgbaMasks[3] != 0 ? DDS.PIXEL_FORMAT_FLAG_ALPHAPIXELS : 0));
}
}
private void writePitchOrLinearSize(int height, int width, DDSType type) throws IOException {
if (type.isBlockCompression()) {
imageOutput.writeInt(((width + 3) / 4) * ((height + 3) / 4) * type.blockSize());
}
else {
imageOutput.writeInt(width * type.blockSize());
}
}
@Override
public IIOMetadata getDefaultImageMetadata(ImageTypeSpecifier imageType, ImageWriteParam param) {
DDSType type = param instanceof DDSImageWriteParam
? ((DDSImageWriteParam) param).type()
: DDSImageWriteParam.DEFAULT_TYPE;
return new DDSImageMetadata(imageType, type);
}
@Override
public IIOMetadata convertImageMetadata(IIOMetadata inData, ImageTypeSpecifier imageType, ImageWriteParam param) {
// Nothing useful to convert here...
return getDefaultImageMetadata(imageType, param);
}
public static void main(String[] args) throws IOException {
if (args.length != 1) {
throw new IllegalArgumentException("Use 1 input file at a time.");
}
ImageIO.write(ImageIO.read(new File(args[0])), "dds", new MemoryCacheImageOutputStream(Files.newOutputStream(Paths.get("output.dds"))));
}
}
@@ -0,0 +1,34 @@
package com.twelvemonkeys.imageio.plugins.dds;
import com.twelvemonkeys.imageio.spi.ImageWriterSpiBase;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import java.util.Locale;
public final class DDSImageWriterSpi extends ImageWriterSpiBase {
public DDSImageWriterSpi() {
super(new DDSProviderInfo());
}
@Override
public boolean canEncodeImage(ImageTypeSpecifier type) {
int numBands = type.getNumBands();
if (numBands < 3 || numBands > 4) {
return false;
}
return type.getSampleModel().getSampleSize(0) == 8;
}
@Override
public ImageWriter createWriterInstance(Object extension) {
return new DDSImageWriter(this);
}
@Override
public String getDescription(Locale locale) {
return "DirectDraw Surface (DDS) Image Writer";
}
}
@@ -35,16 +35,16 @@ import com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfo;
final class DDSProviderInfo extends ReaderWriterProviderInfo {
DDSProviderInfo() {
super(
DDSProviderInfo.class,
new String[]{"DDS", "dds"},
new String[]{"dds"},
new String[]{"image/vnd-ms.dds"},
"com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
new String[]{"com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi"},
null,
null,
false, null, null, null, null,
true, null, null, null, null
DDSProviderInfo.class,
new String[] { "DDS", "dds" },
new String[] { "dds" },
new String[] { "image/vnd-ms.dds" },
"com.twelvemonkeys.imageio.plugins.dds.DDSImageReader",
new String[] { "com.twelvemonkeys.imageio.plugins.dds.DDSImageReaderSpi" },
"com.twelvemonkeys.imageio.plugins.dds.DDSImageWriter",
new String[] { "com.twelvemonkeys.imageio.plugins.dds.DDSImageWriterSpi" },
false, null, null, null, null,
true, null, null, null, null
);
}
}
@@ -70,11 +70,10 @@ import java.io.IOException;
* <a href="http://3dtech.jp/wiki/index.php?DDSReader">Japanese document</a>
*/
final class DDSReader {
static final Order ARGB_ORDER = new Order(16, 8, 0, 24);
static final Order ARGB_ORDER = new Order(16, 8, 0, 24); // 8 alpha | 8 red | 8 green | 8 blue
static final Order RGB_16_ORDER = new Order(11, 5, 0, -1); // no alpha | 5 red | 6 green | 5 blue
private final DDSHeader header;
private DX10Header dxt10Header;
DDSReader(DDSHeader header) {
this.header = header;
@@ -82,18 +81,20 @@ final class DDSReader {
int[] read(ImageInputStream imageInput, int imageIndex) throws IOException {
// type
DDSType type = getType();
if (type == DDSType.DXT10) {
dxt10Header = DX10Header.read(imageInput);
type = dxt10Header.getDDSType();
}
DDSType type = header.getType();
// offset buffer to index mipmap image
byte[] buffer = null;
for (int i = 0; i <= imageIndex; i++) {
int len = getLength(type, i);
buffer = new byte[len];
imageInput.readFully(buffer);
int len = getBufferLength(type, i);
if (i == imageIndex) {
buffer = new byte[len];
imageInput.readFully(buffer);
}
else {
imageInput.seek(imageInput.getStreamPosition() + len);
}
}
int width = header.getWidth(imageIndex);
@@ -135,82 +136,17 @@ final class DDSReader {
}
}
private DDSType getType() throws IIOException {
int flags = header.getPixelFormatFlags();
if ((flags & DDS.PIXEL_FORMAT_FLAG_FOURCC) != 0) {
// DXT
int type = header.getFourCC();
return DDSType.valueOf(type);
} else if ((flags & DDS.PIXEL_FORMAT_FLAG_RGB) != 0) {
// RGB
int bitCount = header.getBitCount();
int redMask = header.getRedMask();
int greenMask = header.getGreenMask();
int blueMask = header.getBlueMask();
int alphaMask = ((flags & 0x01) != 0) ? header.getAlphaMask() : 0; // 0x01 alpha
if (bitCount == 16) {
if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
// A1R5G5B5
return DDSType.A1R5G5B5;
} else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
// X1R5G5B5
return DDSType.X1R5G5B5;
} else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
// A4R4G4B4
return DDSType.A4R4G4B4;
} else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
// X4R4G4B4
return DDSType.X4R4G4B4;
} else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
// R5G6B5
return DDSType.R5G6B5;
} else {
throw new IIOException("Unsupported 16bit RGB image.");
}
} else if (bitCount == 24) {
if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
// R8G8B8
return DDSType.R8G8B8;
} else {
throw new IIOException("Unsupported 24bit RGB image.");
}
} else if (bitCount == 32) {
if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
// A8B8G8R8
return DDSType.A8B8G8R8;
} else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
// X8B8G8R8
return DDSType.X8B8G8R8;
} else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
// A8R8G8B8
return DDSType.A8R8G8B8;
} else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
// X8R8G8B8
return DDSType.X8R8G8B8;
} else {
throw new IIOException("Unsupported 32bit RGB image.");
}
} else {
throw new IIOException("Unsupported bit count: " + bitCount);
}
} else {
throw new IIOException("Unsupported YUV or LUMINANCE image.");
}
}
private int getLength(DDSType type, int imageIndex) throws IIOException {
private int getBufferLength(DDSType type, int imageIndex) throws IIOException {
int width = header.getWidth(imageIndex);
int height = header.getHeight(imageIndex);
switch (type) {
case DXT1:
return 8 * ((width + 3) / 4) * ((height + 3) / 4);
case DXT2:
case DXT3:
case DXT4:
case DXT5:
return 16 * ((width + 3) / 4) * ((height + 3) / 4);
return type.blockSize() * ((width + 3) / 4) * ((height + 3) / 4);
case A1R5G5B5:
case X1R5G5B5:
case A4R4G4B4:
@@ -221,9 +157,9 @@ final class DDSReader {
case X8B8G8R8:
case A8R8G8B8:
case X8R8G8B8:
return (type.value() & 0xFF) * width * height;
return type.blockSize() * width * height;
default:
throw new IIOException("Unknown type: " + Integer.toHexString(type.value()));
throw new IIOException("Unknown type: " + type);
}
}
@@ -503,7 +439,7 @@ final class DDSReader {
return pixels;
}
private static int getDXTColor(int c0, int c1, int a, int t) {
static int getDXTColor(int c0, int c1, int a, int t) {
switch (t) {
case 0:
return getDXTColor1(c0, a);
@@ -519,7 +455,7 @@ final class DDSReader {
private static int getDXTColor2_1(int c0, int c1, int a) {
// 2*c0/3 + c1/3
int r = (2 * BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 3;
int r = (2 * BIT5[(c0 & 0xF800) >> 11] + BIT5[(c1 & 0xF800) >> 11]) / 3;
int g = (2 * BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 3;
int b = (2 * BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 3;
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
@@ -527,20 +463,20 @@ final class DDSReader {
private static int getDXTColor1_1(int c0, int c1, int a) {
// (c0+c1) / 2
int r = (BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 2;
int r = (BIT5[(c0 & 0xF800) >> 11] + BIT5[(c1 & 0xF800) >> 11]) / 2;
int g = (BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 2;
int b = (BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 2;
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
private static int getDXTColor1(int c, int a) {
int r = BIT5[(c & 0xFC00) >> 11];
int r = BIT5[(c & 0xF800) >> 11];
int g = BIT6[(c & 0x07E0) >> 5];
int b = BIT5[(c & 0x001F)];
return (a << ARGB_ORDER.alphaShift) | (r << ARGB_ORDER.redShift) | (g << ARGB_ORDER.greenShift) | (b << ARGB_ORDER.blueShift);
}
private static int getDXT5Alpha(int a0, int a1, int t) {
static int getDXT5Alpha(int a0, int a1, int t) {
if (a0 > a1) switch (t) {
case 0:
return a0;
@@ -581,22 +517,22 @@ final class DDSReader {
}
// RGBA Masks
private static final int[] A1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x8000};
private static final int[] X1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x0000};
private static final int[] A4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0xF000};
private static final int[] X4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0x0000};
private static final int[] R5G6B5_MASKS = {0xF800, 0x07E0, 0x001F, 0x0000};
private static final int[] R8G8B8_MASKS = {0xFF0000, 0x00FF00, 0x0000FF, 0x000000};
private static final int[] A8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000};
private static final int[] X8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000};
private static final int[] A8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
private static final int[] X8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000};
static final int[] A1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x8000};
static final int[] X1R5G5B5_MASKS = {0x7C00, 0x03E0, 0x001F, 0x0000};
static final int[] A4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0xF000};
static final int[] X4R4G4B4_MASKS = {0x0F00, 0x00F0, 0x000F, 0x0000};
static final int[] R5G6B5_MASKS = {0xF800, 0x07E0, 0x001F, 0x0000};
static final int[] R8G8B8_MASKS = {0xFF0000, 0x00FF00, 0x0000FF, 0x000000};
static final int[] A8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000};
static final int[] X8B8G8R8_MASKS = {0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000};
static final int[] A8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000};
static final int[] X8R8G8B8_MASKS = {0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000};
// BIT4 = 17 * index;
private static final int[] BIT5 = {0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255};
private static final int[] BIT6 = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255};
static final int[] BIT5 = {0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255};
static final int[] BIT6 = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255};
private static final class Order {
static final class Order {
Order(int redShift, int greenShift, int blueShift, int alphaShift) {
this.redShift = redShift;
this.greenShift = greenShift;
@@ -30,41 +30,156 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static com.twelvemonkeys.imageio.plugins.dds.BlockCompression.*;
import static com.twelvemonkeys.imageio.plugins.dds.DDSReader.*;
/**
* <a href="https://learn.microsoft.com/en-us/windows/win32/direct3d10/d3d10-graphics-programming-guide-resources-block-compression#compression-algorithms">Compression Algorithms</a>
* <a href="https://github.com/microsoft/DirectXTK12/wiki/DDSTextureLoader#remarks">An extended Non-DX10 FourCC list</a>
*/
enum DDSType {
DXT1(0x31545844),
DXT2(0x32545844),
DXT3(0x33545844),
DXT4(0x34545844),
DXT5(0x35545844),
DXT10(0x30315844),
A1R5G5B5((1 << 16) | 2),
X1R5G5B5((2 << 16) | 2),
A4R4G4B4((3 << 16) | 2),
X4R4G4B4((4 << 16) | 2),
R5G6B5((5 << 16) | 2),
R8G8B8((1 << 16) | 3),
A8B8G8R8((1 << 16) | 4),
X8B8G8R8((2 << 16) | 4),
A8R8G8B8((3 << 16) | 4),
X8R8G8B8((4 << 16) | 4);
// Compressed types
DXT1('D' + ('X' << 8) + ('T' << 16) + ('1' << 24), 8, DXGI.DXGI_FORMAT_BC1_UNORM, BC1),
DXT2('D' + ('X' << 8) + ('T' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
DXT3('D' + ('X' << 8) + ('T' << 16) + ('3' << 24), 16, DXGI.DXGI_FORMAT_BC2_UNORM, BC2),
DXT4('D' + ('X' << 8) + ('T' << 16) + ('4' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
DXT5('D' + ('X' << 8) + ('T' << 16) + ('5' << 24), 16, DXGI.DXGI_FORMAT_BC3_UNORM, BC3),
private final int value;
ATI1('A' + ('T' << 8) + ('I' << 16) + ('1' << 24), 8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4), // AKA BC4U
BC4U('B' + ('C' << 8) + ('4' << 16) + ('U' << 24), 8, DXGI.DXGI_FORMAT_BC4_UNORM, BC4),
BC4S('B' + ('C' << 8) + ('4' << 16) + ('S' << 24), 8, DXGI.DXGI_FORMAT_BC4_SNORM, BC4),
ATI2('A' + ('T' << 8) + ('I' << 16) + ('2' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5), // AKA BC5U
BC5U('B' + ('C' << 8) + ('5' << 16) + ('U' << 24), 16, DXGI.DXGI_FORMAT_BC5_UNORM, BC5),
BC5S('B' + ('C' << 8) + ('5' << 16) + ('S' << 24), 16, DXGI.DXGI_FORMAT_BC5_SNORM, BC5),
DDSType(int value) {
this.value = value;
// Special case, see DXT10Header.dxgiFormat for real format
DXT10('D' + ('X' << 8) + ('1' << 16) + ('0' << 24), -1, DXGI.DXGI_FORMAT_UNKNOWN, null),
// Custom uncompressed pixel formats
// TODO: Consider swapping byte order to reflect the DXGI format?
A1R5G5B5(2, DXGI.DXGI_FORMAT_B5G5R5A1_UNORM, A1R5G5B5_MASKS),
X1R5G5B5(2, DXGI.DXGI_FORMAT_UNKNOWN, X1R5G5B5_MASKS),
A4R4G4B4(2, DXGI.DXGI_FORMAT_B4G4R4A4_UNORM, A4R4G4B4_MASKS),
X4R4G4B4(2, DXGI.DXGI_FORMAT_UNKNOWN, X4R4G4B4_MASKS),
R5G6B5( 2, DXGI.DXGI_FORMAT_B5G6R5_UNORM, R5G6B5_MASKS),
R8G8B8( 3, DXGI.DXGI_FORMAT_UNKNOWN, R8G8B8_MASKS),
A8B8G8R8(4, DXGI.DXGI_FORMAT_R8G8B8A8_UNORM, A8B8G8R8_MASKS),
X8B8G8R8(4, DXGI.DXGI_FORMAT_UNKNOWN, X8B8G8R8_MASKS),
A8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8A8_UNORM, A8R8G8B8_MASKS),
X8R8G8B8(4, DXGI.DXGI_FORMAT_B8G8R8X8_UNORM, X8R8G8B8_MASKS);
private final int fourCC;
private final int blockSize;
private final int dxgiFormat;
final BlockCompression compression;
final int[] rgbaMasks;
DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression) {
this(fourCC, blockSize, dxgiFormat, compression, null);
}
public int value() {
return value;
DDSType(int blockSize, int dxgiFormat, int[] rgbaMasks) {
this(0, blockSize, dxgiFormat, null, rgbaMasks);
}
public static DDSType valueOf(int value) {
for (DDSType type : DDSType.values()) {
if (value == type.value()) {
return type;
DDSType(int fourCC, int blockSize, int dxgiFormat, BlockCompression compression, int[] rgbaMasks) {
this.fourCC = fourCC;
this.blockSize = blockSize;
this.dxgiFormat = dxgiFormat;
this.compression = compression;
this.rgbaMasks = rgbaMasks;
}
public int fourCC() {
return fourCC;
}
public int blockSize() {
return blockSize;
}
public boolean isFourCC() {
return fourCC != 0;
}
public boolean isBlockCompression() {
return compression != null;
}
public int dxgiFormat() {
return dxgiFormat;
}
public static DDSType fromFourCC(int fourCC) {
if (fourCC != 0) {
for (DDSType type : values()) {
if (fourCC == type.fourCC()) {
return type;
}
}
}
throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", value));
throw new IllegalArgumentException(String.format("Unknown type: 0x%08x", fourCC));
}
public static DDSType fromDXGIFormat(int dxgiFormat) {
switch (dxgiFormat) {
case DXGI.DXGI_FORMAT_R8G8B8A8_TYPELESS:
case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
case DXGI.DXGI_FORMAT_R8G8B8A8_UINT:
return A8B8G8R8; // ABGR
case DXGI.DXGI_FORMAT_B8G8R8A8_TYPELESS:
case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI.DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
return A8R8G8B8; // ARGB
case DXGI.DXGI_FORMAT_B8G8R8X8_TYPELESS:
case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM:
case DXGI.DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
return X8R8G8B8;
case DXGI.DXGI_FORMAT_B5G5R5A1_UNORM:
return A1R5G5B5;
case DXGI.DXGI_FORMAT_B4G4R4A4_UNORM:
return A4R4G4B4;
case DXGI.DXGI_FORMAT_B5G6R5_UNORM:
return R5G6B5;
case DXGI.DXGI_FORMAT_BC1_TYPELESS:
case DXGI.DXGI_FORMAT_BC1_UNORM:
case DXGI.DXGI_FORMAT_BC1_UNORM_SRGB:
return DXT1;
case DXGI.DXGI_FORMAT_BC2_TYPELESS:
case DXGI.DXGI_FORMAT_BC2_UNORM:
case DXGI.DXGI_FORMAT_BC2_UNORM_SRGB:
return DXT2;
case DXGI.DXGI_FORMAT_BC3_TYPELESS:
case DXGI.DXGI_FORMAT_BC3_UNORM:
case DXGI.DXGI_FORMAT_BC3_UNORM_SRGB:
return DXT4;
case DXGI.DXGI_FORMAT_BC4_TYPELESS:
case DXGI.DXGI_FORMAT_BC4_UNORM:
return BC4U;
case DXGI.DXGI_FORMAT_BC4_SNORM:
return BC4S;
case DXGI.DXGI_FORMAT_BC5_TYPELESS:
case DXGI.DXGI_FORMAT_BC5_UNORM:
return BC5U;
case DXGI.DXGI_FORMAT_BC5_SNORM:
return BC5S;
default:
throw new IllegalArgumentException("Unsupported DXGI_FORMAT: " + dxgiFormat);
}
}
}
@@ -1,50 +0,0 @@
package com.twelvemonkeys.imageio.plugins.dds;
import java.util.Arrays;
import java.util.function.IntPredicate;
/**
* Enum that lists a certain types of DXGI Format this reader supports to read.
*
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format">DXGI Format List</a>
*/
public enum DX10DXGIFormat {
BC1(DDSType.DXT1, rangeInclusive(70, 72)),
BC2(DDSType.DXT2, rangeInclusive(73, 75)),
BC3(DDSType.DXT5, rangeInclusive(76, 78)),
//BC7(99),
B8G8R8A8(DDSType.A8B8G8R8, exactly(87, 90, 91)),
B8G8R8X8(DDSType.X8B8G8R8, exactly(88, 92, 93)),
R8G8B8A8(DDSType.A8R8G8B8, rangeInclusive(27, 32));
private final DDSType ddsType;
private final IntPredicate dxgiFormat;
DX10DXGIFormat(DDSType ddsType, IntPredicate dxgiFormat) {
this.ddsType = ddsType;
this.dxgiFormat = dxgiFormat;
}
DDSType getCorrespondingType() {
return ddsType;
}
static DX10DXGIFormat getFormat(int value) {
for (DX10DXGIFormat format : values()) {
if (format.dxgiFormat.test(value)) return format;
}
throw new IllegalArgumentException("Unsupported DXGI_FORMAT : " + value);
}
/**
* @param acceptedValues values in DXGI Formats List, passed values are expected to be in ascending order
*/
private static IntPredicate exactly(int ... acceptedValues) {
return test -> Arrays.binarySearch(acceptedValues, test) >= 0;
}
private static IntPredicate rangeInclusive(int from, int to) {
return test -> from <= test && test <= to;
}
}
@@ -1,33 +0,0 @@
package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
//https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10
public final class DX10Header {
final DX10DXGIFormat dxgiFormat;
final int resourceDimension, miscFlag, arraySize, miscFlags2;
private DX10Header(int dxgiFormat, int resourceDimension, int miscFlag, int arraySize, int miscFlags2) {
this.dxgiFormat = DX10DXGIFormat.getFormat(dxgiFormat);
this.resourceDimension = resourceDimension;
if (this.resourceDimension != DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D)
throw new IllegalArgumentException("Resource dimension " + resourceDimension + " is not supported, expected 3.");
this.miscFlag = miscFlag;
this.arraySize = arraySize;
this.miscFlags2 = miscFlags2;
}
static DX10Header read(ImageInputStream inputStream) throws IOException {
int dxgiFormat = inputStream.readInt();
int resourceDimension = inputStream.readInt();
int miscFlag = inputStream.readInt();
int arraySize = inputStream.readInt();
int miscFlags2 = inputStream.readInt();
return new DX10Header(dxgiFormat, resourceDimension, miscFlag, arraySize, miscFlags2);
}
DDSType getDDSType() {
return dxgiFormat.getCorrespondingType();
}
}
@@ -0,0 +1,129 @@
package com.twelvemonkeys.imageio.plugins.dds;
/**
* <a href="https://learn.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format">DXGI Format List</a>
*/
interface DXGI {
int DXGI_FORMAT_UNKNOWN = 0;
int DXGI_FORMAT_R32G32B32A32_TYPELESS = 1;
int DXGI_FORMAT_R32G32B32A32_FLOAT = 2;
int DXGI_FORMAT_R32G32B32A32_UINT = 3;
int DXGI_FORMAT_R32G32B32A32_SINT = 4;
int DXGI_FORMAT_R32G32B32_TYPELESS = 5;
int DXGI_FORMAT_R32G32B32_FLOAT = 6;
int DXGI_FORMAT_R32G32B32_UINT = 7;
int DXGI_FORMAT_R32G32B32_SINT = 8;
int DXGI_FORMAT_R16G16B16A16_TYPELESS = 9;
int DXGI_FORMAT_R16G16B16A16_FLOAT = 10;
int DXGI_FORMAT_R16G16B16A16_UNORM = 11;
int DXGI_FORMAT_R16G16B16A16_UINT = 12;
int DXGI_FORMAT_R16G16B16A16_SNORM = 13;
int DXGI_FORMAT_R16G16B16A16_SINT = 14;
int DXGI_FORMAT_R32G32_TYPELESS = 15;
int DXGI_FORMAT_R32G32_FLOAT = 16;
int DXGI_FORMAT_R32G32_UINT = 17;
int DXGI_FORMAT_R32G32_SINT = 18;
int DXGI_FORMAT_R32G8X24_TYPELESS = 19;
int DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 20;
int DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 21;
int DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 22;
int DXGI_FORMAT_R10G10B10A2_TYPELESS = 23;
int DXGI_FORMAT_R10G10B10A2_UNORM = 24;
int DXGI_FORMAT_R10G10B10A2_UINT = 25;
int DXGI_FORMAT_R11G11B10_FLOAT = 26;
int DXGI_FORMAT_R8G8B8A8_TYPELESS = 27;
int DXGI_FORMAT_R8G8B8A8_UNORM = 28;
int DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29;
int DXGI_FORMAT_R8G8B8A8_UINT = 30;
int DXGI_FORMAT_R8G8B8A8_SNORM = 31;
int DXGI_FORMAT_R8G8B8A8_SINT = 32;
int DXGI_FORMAT_R16G16_TYPELESS = 33;
int DXGI_FORMAT_R16G16_FLOAT = 34;
int DXGI_FORMAT_R16G16_UNORM = 35;
int DXGI_FORMAT_R16G16_UINT = 36;
int DXGI_FORMAT_R16G16_SNORM = 37;
int DXGI_FORMAT_R16G16_SINT = 38;
int DXGI_FORMAT_R32_TYPELESS = 39;
int DXGI_FORMAT_D32_FLOAT = 40;
int DXGI_FORMAT_R32_FLOAT = 41;
int DXGI_FORMAT_R32_UINT = 42;
int DXGI_FORMAT_R32_SINT = 43;
int DXGI_FORMAT_R24G8_TYPELESS = 44;
int DXGI_FORMAT_D24_UNORM_S8_UINT = 45;
int DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 46;
int DXGI_FORMAT_X24_TYPELESS_G8_UINT = 47;
int DXGI_FORMAT_R8G8_TYPELESS = 48;
int DXGI_FORMAT_R8G8_UNORM = 49;
int DXGI_FORMAT_R8G8_UINT = 50;
int DXGI_FORMAT_R8G8_SNORM = 51;
int DXGI_FORMAT_R8G8_SINT = 52;
int DXGI_FORMAT_R16_TYPELESS = 53;
int DXGI_FORMAT_R16_FLOAT = 54;
int DXGI_FORMAT_D16_UNORM = 55;
int DXGI_FORMAT_R16_UNORM = 56;
int DXGI_FORMAT_R16_UINT = 57;
int DXGI_FORMAT_R16_SNORM = 58;
int DXGI_FORMAT_R16_SINT = 59;
int DXGI_FORMAT_R8_TYPELESS = 60;
int DXGI_FORMAT_R8_UNORM = 61;
int DXGI_FORMAT_R8_UINT = 62;
int DXGI_FORMAT_R8_SNORM = 63;
int DXGI_FORMAT_R8_SINT = 64;
int DXGI_FORMAT_A8_UNORM = 65;
int DXGI_FORMAT_R1_UNORM = 66;
int DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 67;
int DXGI_FORMAT_R8G8_B8G8_UNORM = 68;
int DXGI_FORMAT_G8R8_G8B8_UNORM = 69;
int DXGI_FORMAT_BC1_TYPELESS = 70;
int DXGI_FORMAT_BC1_UNORM = 71;
int DXGI_FORMAT_BC1_UNORM_SRGB = 72;
int DXGI_FORMAT_BC2_TYPELESS = 73;
int DXGI_FORMAT_BC2_UNORM = 74;
int DXGI_FORMAT_BC2_UNORM_SRGB = 75;
int DXGI_FORMAT_BC3_TYPELESS = 76;
int DXGI_FORMAT_BC3_UNORM = 77;
int DXGI_FORMAT_BC3_UNORM_SRGB = 78;
int DXGI_FORMAT_BC4_TYPELESS = 79;
int DXGI_FORMAT_BC4_UNORM = 80;
int DXGI_FORMAT_BC4_SNORM = 81;
int DXGI_FORMAT_BC5_TYPELESS = 82;
int DXGI_FORMAT_BC5_UNORM = 83;
int DXGI_FORMAT_BC5_SNORM = 84;
int DXGI_FORMAT_B5G6R5_UNORM = 85;
int DXGI_FORMAT_B5G5R5A1_UNORM = 86;
int DXGI_FORMAT_B8G8R8A8_UNORM = 87;
int DXGI_FORMAT_B8G8R8X8_UNORM = 88;
int DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 89;
int DXGI_FORMAT_B8G8R8A8_TYPELESS = 90;
int DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 91;
int DXGI_FORMAT_B8G8R8X8_TYPELESS = 92;
int DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 93;
int DXGI_FORMAT_BC6H_TYPELESS = 94;
int DXGI_FORMAT_BC6H_UF16 = 95;
int DXGI_FORMAT_BC6H_SF16 = 96;
int DXGI_FORMAT_BC7_TYPELESS = 97;
int DXGI_FORMAT_BC7_UNORM = 98;
int DXGI_FORMAT_BC7_UNORM_SRGB = 99;
int DXGI_FORMAT_AYUV = 100;
int DXGI_FORMAT_Y410 = 101;
int DXGI_FORMAT_Y416 = 102;
int DXGI_FORMAT_NV12 = 103;
int DXGI_FORMAT_P010 = 104;
int DXGI_FORMAT_P016 = 105;
int DXGI_FORMAT_420_OPAQUE = 106;
int DXGI_FORMAT_YUY2 = 107;
int DXGI_FORMAT_Y210 = 108;
int DXGI_FORMAT_Y216 = 109;
int DXGI_FORMAT_NV11 = 110;
int DXGI_FORMAT_AI44 = 111;
int DXGI_FORMAT_IA44 = 112;
int DXGI_FORMAT_P8 = 113;
int DXGI_FORMAT_A8P8 = 114;
int DXGI_FORMAT_B4G4R4A4_UNORM = 115;
int DXGI_FORMAT_P208 = 130;
int DXGI_FORMAT_V208 = 131;
int DXGI_FORMAT_V408 = 132;
int DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189;
int DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190;
int DXGI_FORMAT_FORCE_UINT = 0xffffffff;
}
@@ -0,0 +1,45 @@
package com.twelvemonkeys.imageio.plugins.dds;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* @see <a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header-dxt10">DDS_HEADER_DXT10 structure</a>
*/
final class DXT10Header {
final int dxgiFormat;
final int resourceDimension;
final int miscFlag;
final int arraySize;
final int miscFlags2;
private final DDSType type;
private DXT10Header(int dxgiFormat, int resourceDimension, int miscFlag, int arraySize, int miscFlags2) {
type = DDSType.fromDXGIFormat(dxgiFormat); // Validates dxgiFormat
if (resourceDimension != DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D) {
throw new IllegalArgumentException(String.format("Resource dimension %d is not supported, expected: %d",
resourceDimension, DDS.D3D10_RESOURCE_DIMENSION_TEXTURE2D));
}
this.dxgiFormat = dxgiFormat;
this.resourceDimension = resourceDimension;
this.miscFlag = miscFlag;
this.arraySize = arraySize;
this.miscFlags2 = miscFlags2;
}
static DXT10Header read(ImageInputStream inputStream) throws IOException {
int dxgiFormat = inputStream.readInt();
int resourceDimension = inputStream.readInt();
int miscFlag = inputStream.readInt();
int arraySize = inputStream.readInt();
int miscFlags2 = inputStream.readInt();
return new DXT10Header(dxgiFormat, resourceDimension, miscFlag, arraySize, miscFlags2);
}
DDSType getType() {
return type;
}
}
@@ -0,0 +1 @@
com.twelvemonkeys.imageio.plugins.dds.DDSImageWriterSpi
@@ -0,0 +1,101 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.awt.image.BufferedImage;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import org.junit.jupiter.api.Test;
import org.w3c.dom.NodeList;
class DDSImageMetadataTest {
@Test
void standardMetadataDXT1() {
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.DXT1);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
assertEquals(1, compressionTypeNames.getLength());
IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
assertEquals("DXT1", compressionTypeName.getAttribute("value"));
NodeList losslesses = tree.getElementsByTagName("Lossless");
assertEquals(1, losslesses.getLength());
IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
assertEquals("FALSE", lossless.getAttribute("value"));
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
assertEquals(1, bitsPerSamples.getLength());
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
}
@Test
void standardMetadataA8R8G8B8() {
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_ARGB, DDSType.A8R8G8B8);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList compressions = tree.getElementsByTagName("Compression");
assertEquals(0, compressions.getLength());
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
assertEquals(1, bitsPerSamples.getLength());
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
}
@Test
void standardMetadataX8R8G8B8() {
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_RGB, DDSType.X8R8G8B8);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList compressions = tree.getElementsByTagName("Compression");
assertEquals(0, compressions.getLength());
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
assertEquals(1, bitsPerSamples.getLength());
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
assertEquals("8 8 8 0", bitsPerSample.getAttribute("value")); // Or just 8 8 8?
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
assertEquals("none", alpha.getAttribute("value"));
}
@Test
void standardMetadataX1R5G5B5() {
DDSImageMetadata metadata = createDDSImageMetadata(BufferedImage.TYPE_INT_RGB, DDSType.X1R5G5B5);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList compressions = tree.getElementsByTagName("Compression");
assertEquals(0, compressions.getLength());
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
assertEquals(1, bitsPerSamples.getLength());
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
assertEquals("5 5 5 0", bitsPerSample.getAttribute("value")); // Or just 5 5 5?
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
assertEquals("none", alpha.getAttribute("value"));
}
private static DDSImageMetadata createDDSImageMetadata(int bufferedImageType, DDSType ddsType) {
return new DDSImageMetadata(ImageTypeSpecifier.createFromBufferedImageType(bufferedImageType), ddsType);
}
}
@@ -30,14 +30,27 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.Dimension;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.w3c.dom.NodeList;
public class DDSImageReaderTest extends ImageReaderAbstractTest<DDSImageReader> {
@Override
protected ImageReaderSpi createProvider() {
@@ -110,4 +123,67 @@ public class DDSImageReaderTest extends ImageReaderAbstractTest<DDSImageReader>
protected List<String> getMIMETypes() {
return Collections.singletonList("image/vnd-ms.dds");
}
@Test
void metadataDXT5() throws IOException {
ImageReader reader = createReader();
try (ImageInputStream inputStream = ImageIO.createImageInputStream(getClassLoaderResource("/dds/dds_DXT5.dds"))) {
reader.setInput(inputStream);
IIOMetadata metadata = reader.getImageMetadata(0);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList compressionTypeNames = tree.getElementsByTagName("CompressionTypeName");
assertEquals(1, compressionTypeNames.getLength());
IIOMetadataNode compressionTypeName = (IIOMetadataNode) compressionTypeNames.item(0);
assertEquals("DXT5", compressionTypeName.getAttribute("value"));
NodeList losslesses = tree.getElementsByTagName("Lossless");
assertEquals(1, losslesses.getLength());
IIOMetadataNode lossless = (IIOMetadataNode) losslesses.item(0);
assertEquals("FALSE", lossless.getAttribute("value"));
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
assertEquals(1, bitsPerSamples.getLength());
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
assertEquals("8 8 8 8", bitsPerSample.getAttribute("value"));
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
}
finally {
reader.dispose();
}
}
@Test
void metadataRGB565() throws IOException {
ImageReader reader = createReader();
try (ImageInputStream inputStream = ImageIO.createImageInputStream(getClassLoaderResource("/dds/dds_R5G6B5.dds"))) {
reader.setInput(inputStream);
IIOMetadata metadata = reader.getImageMetadata(0);
IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList compressions = tree.getElementsByTagName("Compression");
assertEquals(0, compressions.getLength());
NodeList bitsPerSamples = tree.getElementsByTagName("BitsPerSample");
assertEquals(1, bitsPerSamples.getLength());
IIOMetadataNode bitsPerSample = (IIOMetadataNode) bitsPerSamples.item(0);
assertEquals("5 6 5 0", bitsPerSample.getAttribute("value")); // or "5 6 5"
NodeList alphas = tree.getElementsByTagName("Alpha");
assertEquals(1, alphas.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) alphas.item(0);
assertEquals("none", alpha.getAttribute("value"));
}
finally {
reader.dispose();
}
}
}
@@ -0,0 +1,55 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Arrays;
import javax.imageio.ImageWriteParam;
import org.junit.jupiter.api.Test;
class DDSImageWriteParamTest {
@Test
void defaultParam() {
DDSImageWriteParam param = new DDSImageWriteParam();
assertEquals(DDSImageWriteParam.DEFAULT_TYPE, param.type());
}
@Test
void compressionTypes() {
DDSImageWriteParam param = new DDSImageWriteParam();
String[] compressionTypes = param.getCompressionTypes();
DDSType[] values = Arrays.stream(DDSType.values())
.filter(DDSType::isBlockCompression)
.toArray(DDSType[]::new);
assertEquals(values.length + 1, compressionTypes.length);
for (int i = 0; i < values.length; i++) {
DDSType type = values[i];
assertEquals(type.name(), compressionTypes[i + 1]);
}
assertEquals("None", compressionTypes[0]);
}
@Test
void setCompression() {
DDSImageWriteParam param = new DDSImageWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
String[] compressionTypes = param.getCompressionTypes();
for (String compressionType : compressionTypes) {
param.setCompressionType(compressionType);
assertEquals(compressionType, param.getCompressionType());
if (!"None".equals(compressionType)) {
DDSType type = DDSType.valueOf(compressionType);
assertEquals(type, param.type());
}
}
}
}
@@ -0,0 +1,186 @@
package com.twelvemonkeys.imageio.plugins.dds;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
class DDSImageWriterTest extends ImageWriterAbstractTest<DDSImageWriter> {
@Override
protected ImageWriterSpi createProvider() {
return new DDSImageWriterSpi();
}
@Override
protected List<BufferedImage> getTestData() {
return Arrays.asList(
new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(64, 64, BufferedImage.TYPE_INT_RGB),
new BufferedImage(32, 32, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(16, 16, BufferedImage.TYPE_3BYTE_BGR)
);
}
@Test
void writeRasters() throws IOException {
ImageWriter writer = createWriter();
assertTrue(writer.canWriteRasters());
// Full tests in super class
}
@Test
void writeMipmap() throws IOException {
ImageWriter writer = createWriter();
try {
assertTrue(writer.canWriteSequence());
List<BufferedImage> testData = getTestData();
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int previousSize = 0;
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
writer.prepareWriteSequence(null);
ImageWriteParam param = writer.getDefaultWriteParam();
assertTrue(buffer.size() > previousSize);
previousSize = buffer.size();
for (BufferedImage image : testData) {
writer.writeToSequence(new IIOImage(drawSomething(image), null, null), param);
}
writer.endWriteSequence();
assertTrue(buffer.size() > previousSize, "No image data written");
}
catch (IOException e) {
throw new AssertionError(e.getMessage(), e);
}
// Verify that we can read the file back...
ImageReader reader = ImageIO.getImageReader(writer);
try (ImageInputStream stream = ImageIO.createImageInputStream(new ByteArrayInputStream(buffer.toByteArray()))) {
stream.seek(0);
reader.setInput(stream);
assertEquals(testData.size(), reader.getNumImages(false));
for (int i = 0; i < testData.size(); i++) {
BufferedImage image = reader.read(i, null);
assertNotNull(image);
assertEquals(testData.get(i).getWidth(), image.getWidth());
assertEquals(testData.get(i).getHeight(), image.getHeight());
}
}
finally {
reader.dispose();
}
}
finally {
writer.dispose();
}
}
@Test
void writeMipmapDifferentCompression() throws IOException {
ImageWriter writer = createWriter();
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
IIOWriteWarningListener listener = mock();
writer.addIIOWriteWarningListener(listener);
writer.setOutput(stream);
writer.prepareWriteSequence(null);
ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionType("DXT2");
// Write first with DXT2
List<BufferedImage> testData = getTestData();
writer.writeToSequence(new IIOImage(drawSomething(testData.get(0)), null, null), param);
// Repeat with different type
IIOImage image = new IIOImage(drawSomething(testData.get(1)), null, null);
param.setCompressionType("DXT1");
writer.writeToSequence(image, param);
// Verify warning is issued
verify(listener).warningOccurred(eq(writer), eq(1), anyString());
verifyNoMoreInteractions(listener);
}
catch (IOException e) {
throw new AssertionError(e.getMessage(), e);
}
}
finally {
writer.dispose();
}
}
@Test
void writeMipmapUnexpectedSize() throws IOException {
ImageWriter writer = createWriter();
try {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
writer.setOutput(stream);
writer.prepareWriteSequence(null);
ImageWriteParam param = writer.getDefaultWriteParam();
BufferedImage testData = getTestData().get(0);
IIOImage image = new IIOImage(drawSomething(testData), null, null);
writer.writeToSequence(image, param);
// Repeat with same size... boom.
assertThrows(IIOException.class, () -> writer.writeToSequence(image, param));
}
catch (IOException e) {
throw new AssertionError(e.getMessage(), e);
}
}
finally {
writer.dispose();
}
}
}
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
<name>TwelveMonkeys » ImageIO » HDR plugin</name>
<description>
ImageIO plugin for Radiance RGBE High Dynaimc Range format (HDR).
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
<name>TwelveMonkeys » ImageIO » ICNS plugin</name>
<description>ImageIO plugin for Apple Icon Image (ICNS) format.</description>
<properties>
@@ -33,6 +33,7 @@ package com.twelvemonkeys.imageio.plugins.icns;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
@@ -55,7 +56,7 @@ import java.util.Iterator;
*/
public final class ICNSImageWriter extends ImageWriterBase {
private int sequenceIndex = -1;
private final SequenceSupport sequence = new SequenceSupport();
private ImageWriter pngDelegate;
ICNSImageWriter(ImageWriterSpi provider) {
@@ -64,7 +65,7 @@ public final class ICNSImageWriter extends ImageWriterBase {
@Override
protected void resetMembers() {
sequenceIndex = -1;
sequence.reset();
if (pngDelegate != null) {
pngDelegate.dispose();
@@ -97,41 +98,29 @@ public final class ICNSImageWriter extends ImageWriterBase {
@Override
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
assertOutput();
sequence.start();
// TODO: Allow TOC resource to be passed as stream metadata?
// - We only need number of icons to be written later
// - The contents of the TOC could be updated while adding to the sequence
if (sequenceIndex >= 0) {
throw new IllegalStateException("writeSequence already started");
}
writeICNSHeader();
sequenceIndex = 0;
}
@SuppressWarnings("RedundantThrows")
@Override
public void endWriteSequence() throws IOException {
assertOutput();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
sequence.end();
// TODO: Now that we know the number of icon resources, we could move all data backwards
// and write a TOC... But I don't think the benefit will outweigh the cost.
sequenceIndex = -1;
}
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
assertOutput();
if (sequenceIndex < 0) {
throw new IllegalStateException("prepareWriteSequence not called");
}
int imageIndex = sequence.advance();
if (image.hasRaster()) {
throw new UnsupportedOperationException("image has a Raster");
@@ -148,7 +137,7 @@ public final class ICNSImageWriter extends ImageWriterBase {
imageOutput.writeInt(IconResource.typeFromImage(image.getRenderedImage(), "PNG"));
imageOutput.writeInt(0); // Size, update later
processImageStarted(sequenceIndex);
processImageStarted(imageIndex);
// Write icon in PNG format
ImageWriter writer = getPNGDelegate();
@@ -208,7 +197,7 @@ public final class ICNSImageWriter extends ImageWriterBase {
pngDelegate.addIIOWriteWarningListener(new IIOWriteWarningListener() {
@Override
public void warningOccurred(ImageWriter source, int imageIndex, String warning) {
processWarningOccurred(sequenceIndex, warning);
processWarningOccurred(sequence.current(), warning);
}
});
}
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
<name>TwelveMonkeys » ImageIO » IFF plugin</name>
<description>
ImageIO plugin for Amiga/Electronic Arts Interchange File Format (IFF)
type ILBM and PBM format.
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
<name>TwelveMonkeys » ImageIO » JPEG/JAI TIFF Interop</name>
<description>
Test JPEG plugin and JAI TIFF plugin interoperability
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
<name>TwelveMonkeys » ImageIO » JPEG/JEP-262 Interop</name>
<description>
Test JPEG plugin and JEP-262 (JDK TIFF plugin) interoperability
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
<name>TwelveMonkeys » ImageIO » JPEG plugin</name>
<description>
ImageIO plugin for Joint Photographer Expert Group images (JPEG/JFIF).
</description>
+2 -2
View File
@@ -3,11 +3,11 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
<name>TwelveMonkeys :: ImageIO :: Metadata</name>
<name>TwelveMonkeys » ImageIO » Metadata</name>
<description>
TwelveMonkeys ImageIO metadata support classes.
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
<name>TwelveMonkeys » ImageIO » PCX plugin</name>
<description>
ImageIO plugin for ZSoft Paintbrush Format (PCX)
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
<name>TwelveMonkeys » ImageIO » PDF plugin</name>
<description>
ImageIO plugin for Adobe Portable Document Format (PDF).
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
<name>TwelveMonkeys » ImageIO » PICT plugin</name>
<description>ImageIO plugin for Apple Mac Paint Picture (PICT) format.</description>
<properties>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
<name>TwelveMonkeys » ImageIO » PNM plugin</name>
<description>
ImageIO plugin for NetPBM Portable Any Map (PNM)
</description>
@@ -111,6 +111,7 @@ enum TupleType {
static TupleType forPAM(Raster raster) {
SampleModel sampleModel = raster.getSampleModel();
switch (sampleModel.getTransferType()) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
@@ -145,8 +146,12 @@ enum TupleType {
return TupleType.RGB;
}
else if (bands == 4) {
// Ambiguous, could also be CMYK...
return TupleType.RGB_ALPHA;
}
else if (bands == 5) {
return TupleType.CMYK_ALPHA;
}
// ...else fall through...
}
@@ -154,7 +159,7 @@ enum TupleType {
}
static TupleType forPAM(ImageTypeSpecifier type) {
// Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
// Support only 1 bit b/w, 8-16 bit gray, 8-16 bit/sample RGB and 8-16 bit/sample CMYK
switch (type.getBufferedImageType()) {
// 1 bit b/w or b/w + a
case BufferedImage.TYPE_BYTE_BINARY:
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
<name>TwelveMonkeys » ImageIO » PSD plugin</name>
<description>
ImageIO plugin for Adobe Photoshop Document (PSD).
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
<name>TwelveMonkeys » ImageIO » JDK Reference Tests</name>
<description>
Test cases for the JRE provided ImageReader implementations for reference.
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
<name>TwelveMonkeys » ImageIO » SGI plugin</name>
<description>
ImageIO plugin for Silicon Graphics Image Format (SGI)
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
<name>TwelveMonkeys » ImageIO » TGA plugin</name>
<description>
ImageIO plugin for Truevision TGA Image Format (TGA)
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
<name>TwelveMonkeys » ImageIO » Thumbs.db plugin</name>
<description>
ImageIO plugin for Windows Thumbs DB (Thumbs.db) format.
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
<name>TwelveMonkeys » ImageIO » TIFF/JAI Metadata Interop</name>
<description>
Test TIFF plugin and JAI TIFF plugin Metadata interoperability
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
<name>TwelveMonkeys » ImageIO » TIFF/JDK JPEG Interop</name>
<description>
Test TIFF plugin and JDK JPEG plugin interoperability
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
<name>TwelveMonkeys » ImageIO » TIFF plugin</name>
<description>
ImageIO plugin for Aldus/Adobe Tagged Image File Format (TIFF).
</description>
@@ -42,6 +42,7 @@ import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.imageio.util.SequenceSupport;
import com.twelvemonkeys.io.enc.EncoderStream;
import com.twelvemonkeys.io.enc.PackBitsEncoder;
import com.twelvemonkeys.lang.Validate;
@@ -110,12 +111,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
// Support storing multiple images in one stream (multi-page TIFF)
// Support more of the ImageIO metadata (ie. compression from metadata, etc)
/**
* Flag for active sequence writing
*/
private boolean writingSequence = false;
private int sequenceIndex = 0;
private final SequenceSupport sequence = new SequenceSupport();
/**
* Metadata writer for sequence writing
@@ -751,7 +747,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
ifd = ((TIFFImageMetadata) inData).getIFD();
}
else {
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.<Entry>emptySet());
TIFFImageMetadata outData = new TIFFImageMetadata(Collections.emptySet());
try {
if (Arrays.asList(inData.getMetadataFormatNames()).contains(SUN_NATIVE_IMAGE_METADATA_FORMAT_NAME)) {
@@ -766,7 +762,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
}
}
catch (IIOInvalidTreeException e) {
processWarningOccurred(sequenceIndex, "Could not convert image meta data: " + e.getMessage());
processWarningOccurred(sequence.current(), "Could not convert image meta data: " + e.getMessage());
}
ifd = outData.getIFD();
@@ -966,14 +962,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
@Override
public void prepareWriteSequence(final IIOMetadata streamMetadata) throws IOException {
if (writingSequence) {
throw new IllegalStateException("sequence writing has already been started!");
}
sequence.start();
assertOutput();
configureStreamByteOrder(streamMetadata, imageOutput);
writingSequence = true;
sequenceTIFFWriter = new TIFFWriter(isBigTIFF() ? 8 : 4);
sequenceTIFFWriter.writeTIFFHeader(imageOutput);
sequenceLastIFDPos = imageOutput.getStreamPosition();
@@ -985,26 +978,20 @@ public final class TIFFImageWriter extends ImageWriterBase {
@Override
public void writeToSequence(final IIOImage image, final ImageWriteParam param) throws IOException {
if (!writingSequence) {
throw new IllegalStateException("prepareWriteSequence() must be called before writeToSequence()!");
}
int sequenceIndex = sequence.advance();
if (sequenceIndex > 0) {
imageOutput.flushBefore(sequenceLastIFDPos);
imageOutput.seek(imageOutput.length());
}
sequenceLastIFDPos = writePage(sequenceIndex++, image, param, sequenceTIFFWriter, sequenceLastIFDPos);
sequenceLastIFDPos = writePage(sequenceIndex, image, param, sequenceTIFFWriter, sequenceLastIFDPos);
}
@Override
public void endWriteSequence() throws IOException {
if (!writingSequence) {
throw new IllegalStateException("prepareWriteSequence() must be called before endWriteSequence()!");
}
sequence.end();
writingSequence = false;
sequenceIndex = 0;
sequenceTIFFWriter = null;
sequenceLastIFDPos = -1;
imageOutput.flush();
@@ -1014,8 +1001,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
protected void resetMembers() {
super.resetMembers();
writingSequence = false;
sequenceIndex = 0;
sequence.reset();
sequenceTIFFWriter = null;
sequenceLastIFDPos = -1;
}
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
<name>TwelveMonkeys » ImageIO » WebP plugin</name>
<description>
ImageIO plugin for Google WebP File Format (WebP).
</description>
+2 -2
View File
@@ -4,10 +4,10 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
<name>TwelveMonkeys » ImageIO » XWD plugin</name>
<description>
ImageIO plugin for X11 Window Dump Format (XWD)
</description>
+3 -3
View File
@@ -3,13 +3,13 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<packaging>pom</packaging>
<name>TwelveMonkeys :: ImageIO</name>
<name>TwelveMonkeys » ImageIO</name>
<url>https://github.com/haraldk/TwelveMonkeys/tree/master/imageio</url>
<contributors>
@@ -61,7 +61,7 @@
</modules>
<properties>
<junit.jupiter.version>5.14.2</junit.jupiter.version>
<junit.jupiter.version>5.14.4</junit.jupiter.version>
</properties>
<dependencies>
+5 -5
View File
@@ -4,7 +4,7 @@
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
<packaging>pom</packaging>
<name>TwelveMonkeys</name>
<description>TwelveMonkeys parent POM</description>
@@ -80,7 +80,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.13.1</tag>
<tag>HEAD</tag>
</scm>
<distributionManagement>
@@ -191,7 +191,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.4.0</version>
<version>3.5.0</version>
<configuration>
<encoding>UTF-8</encoding>
</configuration>
@@ -265,7 +265,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.4</version>
<version>3.5.5</version>
<configuration>
<systemProperties>
<property>
@@ -300,7 +300,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>3.5.4</version>
<version>3.5.5</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
+4 -4
View File
@@ -3,19 +3,19 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.13.1</version>
<version>3.13.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.servlet</groupId>
<artifactId>servlet</artifactId>
<name>TwelveMonkeys :: Servlet</name>
<name>TwelveMonkeys » Servlet</name>
<description>
TwelveMonkeys Servlet support classes.
</description>
<properties>
<junit.jupiter.version>5.14.2</junit.jupiter.version>
<junit.jupiter.version>5.14.4</junit.jupiter.version>
</properties>
<dependencies>
@@ -82,7 +82,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.6.1</version>
<version>3.6.2</version>
<executions>
<execution>
<id>jakarta</id>