Compare commits

..

64 Commits

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

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

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

* corrected formatting and extracted string writing to method

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

* add github workflow

* use java 16 for now

* disable fail fast

* add java 15

* use only java 8 and 11 for now

* snapshot deploy

* snapshot deploy

* oracle jdk

* oracle jdk

* oracle jdk

* kcms matrix

* kcms job name

* only deploy for snapshots

* try not operator

* prepare PR

* restore groupId

* Fixed Travis link + bonus project summary updates

* Readme improvements

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

* #629: Fixed build

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

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

* code cleanup from IDE suggestions

* add oracle jdk 16 and 17 to Travis

* test on java 17

* try to fix warning about maven-source-plugin

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

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

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

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

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

(cherry picked from commit 529c59f93f)
2021-12-15 16:09:33 +01:00
Harald Kuhr 08a69886b1 Updated with the latest versions.
(cherry picked from commit 584b1d9b21)
2021-12-15 16:09:33 +01:00
Harald Kuhr ab85ff0ec8 Prepare for next version. 2021-12-15 16:09:06 +01:00
177 changed files with 3485 additions and 9914 deletions
+3 -3
View File
@@ -7,14 +7,14 @@ assignees: ''
---
**Is your feature request related to a use case or a problem you are working on? Please describe.**
A clear and concise description of what the problem or use case is. Understanding the rationale is key, to be able to implemeent the right solution.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem or use case is.
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've already considered, and why they won't work.
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
+9 -9
View File
@@ -9,11 +9,11 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
java: [ 8, 11, 17, 18 ]
java: [ 8, 11, 17 ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
- uses: actions/checkout@v2
- uses: actions/setup-java@v2
with:
distribution: 'temurin'
java-version: ${{ matrix.java }}
@@ -22,7 +22,7 @@ jobs:
- name: Run Tests
run: mvn test
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
uses: mikepenz/action-junit-report@v2
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -35,11 +35,11 @@ jobs:
matrix:
kcms: [ true, false ]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- run: |
download_url="https://javadl.oracle.com/webapps/download/AutoDL?BundleId=245038_d3c52aa6bfa54d3ca74e617f18309292"
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
- uses: actions/setup-java@v3
- uses: actions/setup-java@v2
with:
distribution: 'jdkfile'
jdkFile: ${{ runner.temp }}/java_package.tar.gz
@@ -54,7 +54,7 @@ jobs:
- name: Run Tests
run: mvn test
- name: Publish Test Report
uses: mikepenz/action-junit-report@v3
uses: mikepenz/action-junit-report@v2
if: ${{ !cancelled() }}
with:
report_paths: "**/target/surefire-reports/TEST*.xml"
@@ -66,9 +66,9 @@ jobs:
if: github.ref == 'refs/heads/master' # only perform on latest master
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Set up Maven Central
uses: actions/setup-java@v3
uses: actions/setup-java@v2
with: # running setup-java again overwrites the settings.xml
distribution: 'temurin'
java-version: '8'
+2 -2
View File
@@ -50,7 +50,7 @@ As there is lots of legacy data out there, we see the need for open implementati
**Important note on using Batik:** *Please read [The Apache™ XML Graphics Project - Security](https://xmlgraphics.apache.org/security.html),
and make sure you use an updated and secure version.*
and make sure you use version 1.14 or later.*
Note that GIF, PNG and WBMP formats are already supported through the ImageIO API, using the
[JDK standard plugins](https://docs.oracle.com/en/java/javase/11/docs/api/java.desktop/javax/imageio/package-summary.html).
@@ -523,7 +523,7 @@ q: What about JAI? Several of the formats are already supported by JAI.
a: While JAI (and jai-imageio in particular) have support for some of the same formats, JAI has some major issues.
The most obvious being:
- It's not actively developed. No issue has been fixed for years.
- It's not actively developed. No issues has been fixed for years.
- To get full format support, you need native libs.
Native libs does not exist for several popular platforms/architectures, and further the native libs are not open source.
Some environments may also prevent deployment of native libs, which brings us back to square one.
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
@@ -31,7 +31,7 @@
<dependency>
<groupId>jmagick</groupId>
<artifactId>jmagick</artifactId>
<version>6.6.9</version>
<version>6.2.4</version>
<optional>true</optional>
<scope>provided</scope>
</dependency>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
@@ -34,7 +34,6 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
/**
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
@@ -43,8 +42,11 @@ import java.util.Arrays;
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: FastByteArrayOutputStream.java#2 $
*/
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
/** Max grow size (unless if writing more than this amount of bytes) */
protected int maxGrowSize = 1024 * 1024; // 1 MB
/**
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
* size.
@@ -95,8 +97,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
private void growIfNeeded(int pNewCount) {
if (pNewCount > buf.length) {
int newSize = Math.max(buf.length << 1, pNewCount);
buf = Arrays.copyOf(buf, newSize);
int newSize = Math.max(Math.min(buf.length << 1, buf.length + maxGrowSize), pNewCount);
byte[] newBuf = new byte[newSize];
System.arraycopy(buf, 0, newBuf, 0, count);
buf = newBuf;
}
}
@@ -109,7 +113,10 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
// Non-synchronized version of toByteArray
@Override
public byte[] toByteArray() {
return Arrays.copyOf(buf, count);
byte[] newBuf = new byte[count];
System.arraycopy(buf, 0, newBuf, 0, count);
return newBuf;
}
/**
@@ -45,39 +45,39 @@ import java.nio.ByteBuffer;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
*/
public final class DecoderStream extends FilterInputStream {
private final ByteBuffer buffer;
private final Decoder decoder;
protected final ByteBuffer buffer;
protected final Decoder decoder;
/**
* Creates a new decoder stream and chains it to the
* input stream specified by the {@code stream} argument.
* input stream specified by the {@code pStream} argument.
* The stream will use a default decode buffer size.
*
* @param stream the underlying input stream.
* @param decoder the decoder that will be used to decode the underlying stream
* @param pStream the underlying input stream.
* @param pDecoder the decoder that will be used to decode the underlying stream
*
* @see java.io.FilterInputStream#in
*/
public DecoderStream(final InputStream stream, final Decoder decoder) {
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
// TODO: Let the decoder decide preferred buffer size
this(stream, decoder, 1024);
this(pStream, pDecoder, 1024);
}
/**
* Creates a new decoder stream and chains it to the
* input stream specified by the {@code stream} argument.
* input stream specified by the {@code pStream} argument.
*
* @param stream the underlying input stream.
* @param decoder the decoder that will be used to decode the underlying stream
* @param bufferSize the size of the decode buffer
* @param pStream the underlying input stream.
* @param pDecoder the decoder that will be used to decode the underlying stream
* @param pBufferSize the size of the decode buffer
*
* @see java.io.FilterInputStream#in
*/
public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) {
super(stream);
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
super(pStream);
this.decoder = decoder;
buffer = ByteBuffer.allocate(bufferSize);
decoder = pDecoder;
buffer = ByteBuffer.allocate(pBufferSize);
buffer.flip();
}
@@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream {
return buffer.get() & 0xff;
}
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
if (bytes == null) {
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
if (pBytes == null) {
throw new NullPointerException();
}
else if ((offset < 0) || (offset > bytes.length) || (length < 0) ||
((offset + length) > bytes.length) || ((offset + length) < 0)) {
throw new IndexOutOfBoundsException("bytes.length=" + bytes.length + " offset=" + offset + " length=" + length);
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength);
}
else if (length == 0) {
else if (pLength == 0) {
return 0;
}
@@ -114,11 +114,11 @@ public final class DecoderStream extends FilterInputStream {
}
}
// Read until we have read length bytes, or have reached EOF
// Read until we have read pLength bytes, or have reached EOF
int count = 0;
int off = offset;
int off = pOffset;
while (length > count) {
while (pLength > count) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
break;
@@ -126,8 +126,8 @@ public final class DecoderStream extends FilterInputStream {
}
// Copy as many bytes as possible
int dstLen = Math.min(length - count, buffer.remaining());
buffer.get(bytes, off, dstLen);
int dstLen = Math.min(pLength - count, buffer.remaining());
buffer.get(pBytes, off, dstLen);
// Update offset (rest)
off += dstLen;
@@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream {
return count;
}
public long skip(final long length) throws IOException {
public long skip(final long pLength) throws IOException {
// End of file?
if (!buffer.hasRemaining()) {
if (fill() < 0) {
@@ -147,10 +147,10 @@ public final class DecoderStream extends FilterInputStream {
}
}
// Skip until we have skipped length bytes, or have reached EOF
// Skip until we have skipped pLength bytes, or have reached EOF
long total = 0;
while (total < length) {
while (total < pLength) {
if (!buffer.hasRemaining()) {
if (fill() < 0) {
break;
@@ -158,7 +158,7 @@ public final class DecoderStream extends FilterInputStream {
}
// NOTE: Skipped can never be more than avail, which is an int, so the cast is safe
int skipped = (int) Math.min(length - total, buffer.remaining());
int skipped = (int) Math.min(pLength - total, buffer.remaining());
buffer.position(buffer.position() + skipped);
total += skipped;
}
@@ -174,7 +174,7 @@ public final class DecoderStream extends FilterInputStream {
*
* @throws IOException if an I/O error occurs
*/
private int fill() throws IOException {
protected int fill() throws IOException {
buffer.clear();
int read = decoder.decode(in, buffer);
@@ -45,39 +45,41 @@ import java.nio.ByteBuffer;
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
*/
public final class EncoderStream extends FilterOutputStream {
// TODO: This class need a test case ASAP!!!
private final Encoder encoder;
protected final Encoder encoder;
private final boolean flushOnWrite;
private final ByteBuffer buffer;
protected final ByteBuffer buffer;
/**
* Creates an output stream filter built on top of the specified
* underlying output stream.
*
* @param stream the underlying output stream
* @param encoder the encoder to use
* @param pStream the underlying output stream
* @param pEncoder the encoder to use
*/
public EncoderStream(final OutputStream stream, final Encoder encoder) {
this(stream, encoder, false);
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
this(pStream, pEncoder, false);
}
/**
* Creates an output stream filter built on top of the specified
* underlying output stream.
*
* @param stream the underlying output stream
* @param encoder the encoder to use
* @param flushOnWrite if {@code true}, calls to the byte-array
* @param pStream the underlying output stream
* @param pEncoder the encoder to use
* @param pFlushOnWrite if {@code true}, calls to the byte-array
* {@code write} methods will automatically flush the buffer.
*/
public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) {
super(stream);
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
super(pStream);
this.encoder = encoder;
this.flushOnWrite = flushOnWrite;
encoder = pEncoder;
flushOnWrite = pFlushOnWrite;
buffer = ByteBuffer.allocate(1024);
buffer.flip();
}
public void close() throws IOException {
@@ -102,33 +104,33 @@ public final class EncoderStream extends FilterOutputStream {
}
}
public void write(final byte[] bytes) throws IOException {
write(bytes, 0, bytes.length);
public final void write(final byte[] pBytes) throws IOException {
write(pBytes, 0, pBytes.length);
}
// TODO: Verify that this works for the general case (it probably won't)...
// TODO: We might need a way to explicitly flush the encoder, or specify
// that the encoder can't buffer. In that case, the encoder should probably
// tell the EncoderStream how large buffer it prefers...
public void write(final byte[] values, final int offset, final int length) throws IOException {
if (!flushOnWrite && length < buffer.remaining()) {
// tell the EncoderStream how large buffer it prefers...
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
if (!flushOnWrite && pLength < buffer.remaining()) {
// Buffer data
buffer.put(values, offset, length);
buffer.put(pBytes, pOffset, pLength);
}
else {
// Encode data already in the buffer
encodeBuffer();
// Encode rest without buffering
encoder.encode(out, ByteBuffer.wrap(values, offset, length));
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
}
}
public void write(final int value) throws IOException {
public void write(final int pByte) throws IOException {
if (!buffer.hasRemaining()) {
encodeBuffer(); // Resets bufferPos to 0
}
buffer.put((byte) value);
buffer.put((byte) pByte);
}
}
@@ -1,130 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.enc;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
import static org.junit.Assert.*;
public class DecoderStreamTest {
private final Random rng = new Random(5467809876546L);
private byte[] createData(final int length) {
byte[] data = new byte[length];
rng.nextBytes(data);
return data;
}
@Test
public void testDecodeSingleBytes() throws IOException {
byte[] data = createData(1327);
InputStream source = new ByteArrayInputStream(data);
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
for (byte datum : data) {
int read = stream.read();
assertNotEquals(-1, read);
assertEquals(datum, (byte) read);
}
assertEquals(-1, stream.read());
}
}
@Test
public void testDecodeArray() throws IOException {
int length = 793;
byte[] data = createData(length * 10);
InputStream source = new ByteArrayInputStream(data);
byte[] result = new byte[477];
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
int dataOffset = 0;
while (dataOffset < data.length) {
int count = stream.read(result);
assertFalse(count <= 0);
assertArrayEquals(Arrays.copyOfRange(data, dataOffset, dataOffset + count), Arrays.copyOfRange(result, 0, count));
dataOffset += count;
}
assertEquals(-1, stream.read());
}
}
@Test
public void testDecodeArrayOffset() throws IOException {
int length = 793;
byte[] data = createData(length * 10);
InputStream source = new ByteArrayInputStream(data);
byte[] result = new byte[477];
try (InputStream stream = new DecoderStream(source, new NullDecoder())) {
int dataOffset = 0;
while (dataOffset < data.length) {
int resultOffset = dataOffset % result.length;
int count = stream.read(result, resultOffset, result.length - resultOffset);
assertFalse(count <= 0);
assertArrayEquals(Arrays.copyOfRange(data, dataOffset + resultOffset, dataOffset + count), Arrays.copyOfRange(result, resultOffset, count));
dataOffset += count;
}
assertEquals(-1, stream.read());
}
}
private static final class NullDecoder implements Decoder {
@Override
public int decode(InputStream stream, ByteBuffer buffer) throws IOException {
int read = stream.read(buffer.array(), buffer.arrayOffset(), buffer.remaining());
if (read > 0) {
// Set position, should be equivalent to using buffer.put(stream.read()) until EOF or buffer full
buffer.position(read);
}
return read;
}
}
}
@@ -1,111 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.io.enc;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
import static org.junit.Assert.assertArrayEquals;
public class EncoderStreamTest {
private final Random rng = new Random(5467809876546L);
private byte[] createData(final int length) {
byte[] data = new byte[length];
rng.nextBytes(data);
return data;
}
@Test
public void testEncodeSingleBytes() throws IOException {
byte[] data = createData(1327);
ByteArrayOutputStream result = new ByteArrayOutputStream();
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
for (byte datum : data) {
stream.write(datum);
}
}
assertArrayEquals(data, result.toByteArray());
}
@Test
public void testEncodeArray() throws IOException {
byte[] data = createData(1793);
ByteArrayOutputStream result = new ByteArrayOutputStream();
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
for (int i = 0; i < 10; i++) {
stream.write(data);
}
}
byte[] encoded = result.toByteArray();
for (int i = 0; i < 10; i++) {
assertArrayEquals(data, Arrays.copyOfRange(encoded, i * data.length, (i + 1) * data.length));
}
}
@Test
public void testEncodeArrayOffset() throws IOException {
byte[] data = createData(87);
ByteArrayOutputStream result = new ByteArrayOutputStream();
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
for (int i = 0; i < 10; i++) {
stream.write(data, 13, 59);
}
}
byte[] original = Arrays.copyOfRange(data, 13, 13 + 59);
byte[] encoded = result.toByteArray();
for (int i = 0; i < 10; i++) {
assertArrayEquals(original, Arrays.copyOfRange(encoded, i * original.length, (i + 1) * original.length));
}
}
private static final class NullEncoder implements Encoder {
@Override
public void encode(OutputStream stream, ByteBuffer buffer) throws IOException {
stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
@@ -30,14 +30,9 @@ import static com.twelvemonkeys.contrib.tiff.TIFFUtilities.applyOrientation;
public class EXIFUtilities {
/**
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
* The returned {@code IIOImage} will always contain an image and no raster, and
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
*
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
*
* @param input a {@code URL}
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
* {@code null}.
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
* @throws IOException if an error occurs during reading.
*/
public static IIOImage readWithOrientation(final URL input) throws IOException {
@@ -48,14 +43,9 @@ public class EXIFUtilities {
/**
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
* The returned {@code IIOImage} will always contain an image and no raster, and
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
*
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
*
* @param input an {@code InputStream}
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
* {@code null}.
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
* @throws IOException if an error occurs during reading.
*/
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
@@ -66,14 +56,9 @@ public class EXIFUtilities {
/**
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
* The returned {@code IIOImage} will always contain an image and no raster, and
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
*
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
*
* @param input a {@code File}
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info or
* {@code null}.
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
* @throws IOException if an error occurs during reading.
*/
public static IIOImage readWithOrientation(final File input) throws IOException {
@@ -84,14 +69,9 @@ public class EXIFUtilities {
/**
* Reads image and metadata, applies Exif orientation to image, and returns everything as an {@code IIOImage}.
* The returned {@code IIOImage} will always contain an image and no raster, and
* the {@code RenderedImage} may be safely cast to a {@code BufferedImage}.
*
* If no registered {@code ImageReader} claims to be able to read the input, {@code null} is returned.
*
* @param input an {@code ImageInputStream}
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
* {@code null}.
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info.
* @throws IOException if an error occurs during reading.
*/
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
+4 -4
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -17,7 +17,7 @@
<properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
<batik.version>1.15</batik.version>
<batik.version>1.14</batik.version>
</properties>
<build>
@@ -27,9 +27,9 @@
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
true
</com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
</systemPropertyVariables>
</configuration>
</plugin>
@@ -30,10 +30,21 @@
package com.twelvemonkeys.imageio.plugins.svg;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import org.apache.batik.anim.dom.SVGDOMImplementation;
import org.apache.batik.anim.dom.SVGOMDocument;
@@ -57,19 +68,10 @@ import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.svg.SVGSVGElement;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import com.twelvemonkeys.image.ImageUtil;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.lang.StringUtil;
/**
* Image reader for SVG document fragments.
@@ -77,13 +79,12 @@ import java.util.Map;
* @author Harald Kuhr
* @author Inpspired by code from the Batik Team
* @version $Id: $
* @see <a href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</a>
* @see <A href="http://www.mail-archive.com/batik-dev@xml.apache.org/msg00992.html">batik-dev</A>
*/
public class SVGImageReader extends ImageReaderBase {
final static boolean DEFAULT_ALLOW_EXTERNAL_RESOURCES =
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowExternalResources",
System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources")));
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources"));
private Rasterizer rasterizer;
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
@@ -149,23 +150,29 @@ public class SVGImageReader extends ImageReaderBase {
BufferedImage destination = getDestination(pParam, getImageTypes(pIndex), size.width, size.height);
// Read in the image, using the Batik Transcoder
processImageStarted(pIndex);
BufferedImage image = rasterizer.getImage();
Graphics2D g = destination.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
}
finally {
g.dispose();
}
processImageStarted(pIndex);
processImageComplete();
BufferedImage image = rasterizer.getImage();
return destination;
Graphics2D g = destination.createGraphics();
try {
g.setComposite(AlphaComposite.Src);
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
}
finally {
g.dispose();
}
processImageComplete();
return destination;
}
catch (TranscoderException e) {
Throwable cause = unwrapException(e);
throw new IIOException(cause.getMessage(), cause);
}
}
private static Throwable unwrapException(TranscoderException ex) {
@@ -180,11 +187,11 @@ public class SVGImageReader extends ImageReaderBase {
// Set dimensions
Dimension size = pParam.getSourceRenderSize();
Rectangle viewBox = rasterizer.getViewBox();
Dimension origSize = new Dimension(getWidth(0), getHeight(0));
if (size == null) {
// SVG is not a pixel based format, but we'll scale it, according to
// the subsampling for compatibility
size = getSourceRenderSizeFromSubsamping(pParam, viewBox.getSize());
size = getSourceRenderSizeFromSubsamping(pParam, origSize);
}
if (size != null) {
@@ -204,8 +211,8 @@ public class SVGImageReader extends ImageReaderBase {
}
else {
// Need to resize here...
double xScale = size.getWidth() / viewBox.getWidth();
double yScale = size.getHeight() / viewBox.getHeight();
double xScale = size.getWidth() / origSize.getWidth();
double yScale = size.getHeight() / origSize.getHeight();
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
@@ -213,7 +220,7 @@ public class SVGImageReader extends ImageReaderBase {
}
else if (size != null) {
// Allow non-uniform scaling
hints.put(ImageTranscoder.KEY_AOI, viewBox);
hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize));
}
// Background color
@@ -228,7 +235,7 @@ public class SVGImageReader extends ImageReaderBase {
private Dimension getSourceRenderSizeFromSubsamping(ImageReadParam pParam, Dimension pOrigSize) {
if (pParam.getSourceXSubsampling() > 1 || pParam.getSourceYSubsampling() > 1) {
return new Dimension((int) (pOrigSize.width / (float) pParam.getSourceXSubsampling()),
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
(int) (pOrigSize.height / (float) pParam.getSourceYSubsampling()));
}
return null;
}
@@ -239,13 +246,22 @@ public class SVGImageReader extends ImageReaderBase {
public int getWidth(int pIndex) throws IOException {
checkBounds(pIndex);
return rasterizer.getDefaultWidth();
try {
return rasterizer.getDefaultWidth();
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
}
public int getHeight(int pIndex) throws IOException {
checkBounds(pIndex);
return rasterizer.getDefaultHeight();
try {
return rasterizer.getDefaultHeight();
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
}
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
@@ -259,11 +275,12 @@ public class SVGImageReader extends ImageReaderBase {
* and needs major refactoring!
* </p>
*/
private class Rasterizer extends SVGAbstractTranscoder {
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
private BufferedImage image;
private TranscoderInput transcoderInput;
private final Rectangle2D viewBox = new Rectangle2D.Float();
private final Dimension defaultSize = new Dimension();
private float defaultWidth;
private float defaultHeight;
private boolean initialized = false;
private SVGOMDocument document;
private String uri;
@@ -324,66 +341,54 @@ public class SVGImageReader extends ImageReaderBase {
// ----
SVGSVGElement rootElement = svgDoc.getRootElement();
// Get the viewBox
String viewBoxStr = rootElement.getAttributeNS(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
if (viewBoxStr.length() != 0) {
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
viewBox.setFrame(rect[0], rect[1], rect[2], rect[3]);
}
// Get the 'width' and 'height' attributes of the SVG document
double width = 0;
double height = 0;
UnitProcessor.Context uctx = UnitProcessor.createContext(ctx, rootElement);
// get the 'width' and 'height' attributes of the SVG document
UnitProcessor.Context uctx
= UnitProcessor.createContext(ctx, rootElement);
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
if (!StringUtil.isEmpty(widthStr)) {
width = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
}
if (!StringUtil.isEmpty(heightStr)) {
height = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
if(!StringUtil.isEmpty(heightStr)){
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
}
boolean hasWidth = width > 0.0;
boolean hasHeight = height > 0.0;
boolean hasWidth = defaultWidth > 0.0;
boolean hasHeight = defaultHeight > 0.0;
if (!hasWidth || !hasHeight) {
if (!viewBox.isEmpty()) {
// If one dimension is given, calculate other by aspect ratio in viewBox
String viewBoxStr = rootElement.getAttributeNS
(null, SVGConstants.SVG_VIEW_BOX_ATTRIBUTE);
if (viewBoxStr.length() != 0) {
float[] rect = ViewBox.parseViewBoxAttribute(rootElement, viewBoxStr, null);
// if one dimension is given, calculate other by aspect ratio in viewBox
// or use viewBox if no dimension is given
if (hasWidth) {
height = width * viewBox.getHeight() / viewBox.getWidth();
defaultHeight = defaultWidth * rect[3] / rect[2];
}
else if (hasHeight) {
width = height * viewBox.getWidth() / viewBox.getHeight();
defaultWidth = defaultHeight * rect[2] / rect[3];
}
else {
// ...or use viewBox if no dimension is given
width = viewBox.getWidth();
height = viewBox.getHeight();
defaultWidth = rect[2];
defaultHeight = rect[3];
}
}
else {
// No viewBox, just assume square size
if (hasHeight) {
width = height;
defaultWidth = defaultHeight;
}
else if (hasWidth) {
height = width;
defaultHeight = defaultWidth;
}
else {
// ...or finally fall back to Batik default sizes
width = 400;
height = 400;
// fallback to batik default sizes
defaultWidth = 400;
defaultHeight = 400;
}
}
}
// We now have a size, in the rare case we don't have a viewBox; set it to this size
defaultSize.setSize(width, height);
if (viewBox.isEmpty()) {
viewBox.setRect(0, 0, width, height);
}
// Hack to work around exception above
if (root != null) {
gvtRoot = root;
@@ -396,7 +401,7 @@ public class SVGImageReader extends ImageReaderBase {
ctx = null;
}
private BufferedImage readImage() throws IOException {
private BufferedImage readImage() throws TranscoderException {
init();
if (abortRequested()) {
@@ -421,8 +426,7 @@ public class SVGImageReader extends ImageReaderBase {
}
if (gvtRoot == null) {
Throwable cause = unwrapException(exception);
throw new IIOException(cause.getMessage(), cause);
throw exception;
}
}
ctx = context;
@@ -440,7 +444,7 @@ public class SVGImageReader extends ImageReaderBase {
// ----
setImageSize(defaultSize.width, defaultSize.height);
setImageSize(defaultWidth, defaultHeight);
if (abortRequested()) {
processReadAborted();
@@ -454,17 +458,18 @@ public class SVGImageReader extends ImageReaderBase {
try {
Px = ViewBox.getViewTransform(ref, root, width, height, null);
}
catch (BridgeException ex) {
throw new IIOException(ex.getMessage(), ex);
throw new TranscoderException(ex);
}
if (Px.isIdentity() && (width != defaultSize.width || height != defaultSize.height)) {
if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) {
// The document has no viewBox, we need to resize it by hand.
// we want to keep the document size ratio
float xscale, yscale;
xscale = width / defaultSize.width;
yscale = height / defaultSize.height;
xscale = width / defaultWidth;
yscale = height / defaultHeight;
float scale = Math.min(xscale, yscale);
Px = AffineTransform.getScaleInstance(scale, scale);
}
@@ -514,7 +519,7 @@ public class SVGImageReader extends ImageReaderBase {
}
}
catch (BridgeException ex) {
throw new IIOException(ex.getMessage(), ex);
throw new TranscoderException(ex);
}
this.root = gvtRoot;
@@ -583,7 +588,7 @@ public class SVGImageReader extends ImageReaderBase {
return dest;
}
catch (Exception ex) {
throw new IIOException(ex.getMessage(), ex);
throw new TranscoderException(ex.getMessage(), ex);
}
finally {
if (context != null) {
@@ -592,7 +597,7 @@ public class SVGImageReader extends ImageReaderBase {
}
}
private synchronized void init() throws IIOException {
private synchronized void init() throws TranscoderException {
if (!initialized) {
if (transcoderInput == null) {
throw new IllegalStateException("input == null");
@@ -600,17 +605,11 @@ public class SVGImageReader extends ImageReaderBase {
initialized = true;
try {
super.transcode(transcoderInput, null);
}
catch (TranscoderException e) {
Throwable cause = unwrapException(e);
throw new IIOException(cause.getMessage(), cause);
}
super.transcode(transcoderInput, null);
}
}
private BufferedImage getImage() throws IOException {
private BufferedImage getImage() throws TranscoderException {
if (image == null) {
image = readImage();
}
@@ -618,19 +617,14 @@ public class SVGImageReader extends ImageReaderBase {
return image;
}
int getDefaultWidth() throws IOException {
int getDefaultWidth() throws TranscoderException {
init();
return defaultSize.width;
return (int) Math.ceil(defaultWidth);
}
int getDefaultHeight() throws IOException {
int getDefaultHeight() throws TranscoderException {
init();
return defaultSize.height;
}
Rectangle getViewBox() throws IOException {
init();
return viewBox.getBounds();
return (int) Math.ceil(defaultHeight);
}
void setInput(final TranscoderInput pInput) {
@@ -1,143 +1,141 @@
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.wmf;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
import javax.imageio.IIOException;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.image.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
/**
* WMFImageReader class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
*/
// TODO: Probably possible to do less wrapping/unwrapping of data...
public final class WMFImageReader extends ImageReaderBase {
private SVGImageReader reader = null;
public WMFImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
protected void resetMembers() {
if (reader != null) {
reader.dispose();
}
reader = null;
}
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
init();
processImageStarted(pIndex);
BufferedImage image = reader.read(pIndex, pParam);
if (abortRequested()) {
processReadAborted();
return image;
}
processImageProgress(100f);
processImageComplete();
return image;
}
private void init() throws IOException {
// Need the extra test, to avoid throwing an IOException from the Transcoder
if (imageInput == null) {
throw new IllegalStateException("input == null");
}
if (reader == null) {
WMFTranscoder transcoder = new WMFTranscoder();
ByteArrayOutputStream output = new ByteArrayOutputStream(8192);
try (Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
TranscoderOutput out = new TranscoderOutput(writer);
// TODO: Transcodinghints?
transcoder.transcode(in, out);
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
reader = new SVGImageReader(getOriginatingProvider());
reader.setInput(new ByteArrayImageInputStream(output.toByteArray()));
}
}
@Override
public ImageReadParam getDefaultReadParam() {
return new SVGReadParam();
}
public int getWidth(int pIndex) throws IOException {
init();
return reader.getWidth(pIndex);
}
public int getHeight(int pIndex) throws IOException {
init();
return reader.getHeight(pIndex);
}
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
init();
return reader.getImageTypes(pImageIndex);
}
}
/*
* Copyright (c) 2008, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.wmf;
import com.twelvemonkeys.imageio.ImageReaderBase;
import com.twelvemonkeys.imageio.plugins.svg.SVGImageReader;
import com.twelvemonkeys.imageio.plugins.svg.SVGReadParam;
import com.twelvemonkeys.imageio.util.IIOUtil;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.wmf.tosvg.WMFTranscoder;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
/**
* WMFImageReader class description.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haku $
* @version $Id: WMFImageReader.java,v 1.0 29.jul.2004 13:00:59 haku Exp $
*/
// TODO: Probably possible to do less wrapping/unwrapping of data...
// TODO: Consider using temp file instead of in-memory stream
public final class WMFImageReader extends ImageReaderBase {
private SVGImageReader reader = null;
public WMFImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
protected void resetMembers() {
if (reader != null) {
reader.dispose();
}
reader = null;
}
public BufferedImage read(int pIndex, ImageReadParam pParam) throws IOException {
init();
processImageStarted(pIndex);
BufferedImage image = reader.read(pIndex, pParam);
if (abortRequested()) {
processReadAborted();
return image;
}
processImageProgress(100f);
processImageComplete();
return image;
}
private synchronized void init() throws IOException {
// Need the extra test, to avoid throwing an IOException from the Transcoder
if (imageInput == null) {
throw new IllegalStateException("input == null");
}
if (reader == null) {
WMFTranscoder transcoder = new WMFTranscoder();
ByteArrayOutputStream output = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(output, "UTF8");
try {
TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput));
TranscoderOutput out = new TranscoderOutput(writer);
// TODO: Transcodinghints?
transcoder.transcode(in, out);
}
catch (TranscoderException e) {
throw new IIOException(e.getMessage(), e);
}
reader = new SVGImageReader(getOriginatingProvider());
reader.setInput(ImageIO.createImageInputStream(new ByteArrayInputStream(output.toByteArray())));
}
}
@Override
public ImageReadParam getDefaultReadParam() {
return new SVGReadParam();
}
public int getWidth(int pIndex) throws IOException {
init();
return reader.getWidth(pIndex);
}
public int getHeight(int pIndex) throws IOException {
init();
return reader.getHeight(pIndex);
}
public Iterator<ImageTypeSpecifier> getImageTypes(final int pImageIndex) throws IOException {
init();
return reader.getImageTypes(pImageIndex);
}
}
@@ -43,7 +43,8 @@ import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import java.awt.image.ImagingOpException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
@@ -52,10 +53,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
@@ -194,12 +192,12 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
reader.setInput(redSquare.getInputStream());
BufferedImage imageRed = reader.read(0, param);
assertRGBEquals("Expected all red", 0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF, 0);
assertEquals(0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF);
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
reader.setInput(blueSquare.getInputStream());
BufferedImage imageBlue = reader.read(0, param);
assertRGBEquals("Expected all blue", 0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF, 0);
assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF);
}
@Test
@@ -339,69 +337,4 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
reader.dispose();
}
}
@Test
public void testReadWitSourceRenderSize() throws IOException {
URL resource = getClassLoaderResource("/svg/circle.svg");
SVGImageReader reader = createReader();
TestData data = new TestData(resource, (Dimension) null);
try (ImageInputStream stream = data.getInputStream()) {
reader.setInput(stream);
SVGReadParam param = reader.getDefaultReadParam();
param.setSourceRenderSize(new Dimension(100, 100));
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(100, image.getWidth());
assertEquals(100, image.getHeight());
// Some quick samples
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 0), 0);
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 99), 0);
assertRGBEquals("Expected transparent corner", 0, image.getRGB(99, 99), 0);
assertRGBEquals("Expected red center", 0xffff0000, image.getRGB(50, 50), 0);
}
finally {
reader.dispose();
}
}
@Test
public void testReadWitSourceRenderSizeViewBoxNegativeXY() throws IOException {
URL resource = getClassLoaderResource("/svg/Android_robot.svg");
SVGImageReader reader = createReader();
TestData data = new TestData(resource, (Dimension) null);
try (ImageInputStream stream = data.getInputStream()) {
reader.setInput(stream);
SVGReadParam param = reader.getDefaultReadParam();
param.setSourceRenderSize(new Dimension(219, 256)); // Aspect scaled to 256 boxed
BufferedImage image = reader.read(0, param);
assertNotNull(image);
assertEquals(219, image.getWidth());
assertEquals(256, image.getHeight());
// Some quick samples
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 0), 0);
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 0), 0);
assertRGBEquals("Expected transparent corner", 0, image.getRGB(0, 255), 0);
assertRGBEquals("Expected transparent corner", 0, image.getRGB(218, 255), 0);
assertRGBEquals("Expected green head", 0xffa4c639, image.getRGB(109, 20), 25);
assertRGBEquals("Expected green center", 0xffa4c639, image.getRGB(109, 128), 25);
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(80, 246), 25);
assertRGBEquals("Expected green feet", 0xffa4c639, image.getRGB(130, 246), 25);
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(0, 128), 0);
assertRGBEquals("Expected white edge", 0xffffffff, image.getRGB(218, 128), 0);
}
finally {
reader.dispose();
}
}
}
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
<circle cx="25" cy="25" r="25" fill="red"/></svg>

Before

Width:  |  Height:  |  Size: 436 B

+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
@@ -39,11 +39,7 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.xml.XMLSerializer;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.*;
import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata;
@@ -51,7 +47,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInput;
import java.io.File;
@@ -81,7 +77,7 @@ public final class BMPImageReader extends ImageReaderBase {
super(new BMPImageReaderSpi());
}
BMPImageReader(final ImageReaderSpi pProvider) {
protected BMPImageReader(final ImageReaderSpi pProvider) {
super(pProvider);
}
@@ -362,18 +358,14 @@ public final class BMPImageReader extends ImageReaderBase {
processImageStarted(imageIndex);
for (int y = 0; y < height; y++) {
int bitCount = header.getBitCount();
switch (bitCount) {
switch (header.getBitCount()) {
case 1:
case 2:
case 4:
case 8:
case 24:
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
int bitsPerSample = bitCount == 24 ? 8 : bitCount;
int samplesPerPixel = bitCount == 24 ? 3 : 1;
readRowByte(input, height, srcRegion, xSub, ySub, bitsPerSample, samplesPerPixel, rowDataByte, destRaster, clippedRow, y);
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
break;
case 16:
@@ -387,7 +379,7 @@ public final class BMPImageReader extends ImageReaderBase {
break;
default:
throw new AssertionError("Unsupported pixel depth: " + bitCount);
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
}
processImageProgress(100f * y / height);
@@ -484,7 +476,6 @@ public final class BMPImageReader extends ImageReaderBase {
}
private void readRowByte(final DataInput input, final int height, final Rectangle srcRegion, final int xSub, final int ySub,
int bitsPerSample, int samplesPerPixel,
final byte[] rowDataByte, final WritableRaster destChannel, final Raster srcChannel, final int y) throws IOException {
// Flip into position?
int srcY = !header.topDown ? height - 1 - y : y;
@@ -501,7 +492,9 @@ public final class BMPImageReader extends ImageReaderBase {
// Subsample horizontal
if (xSub != 1) {
IIOUtil.subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
for (int x = 0; x < srcRegion.width / xSub; x++) {
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
}
}
destChannel.setDataElements(0, dstY, srcChannel);
@@ -116,7 +116,7 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
}
}
public ImageReader createReaderInstance(final Object pExtension) {
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
return new BMPImageReader(this);
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -1,627 +0,0 @@
package com.twelvemonkeys.imageio;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType.*;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* Base class for easy read-only implementation of the standard image metadata format.
* Chroma, Data and Transparency nodes values are based on the required
* {@link ImageTypeSpecifier}.
* Other values or overrides may be specified using the builder.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
public class StandardImageMetadataSupport extends AbstractMetadata {
// The only required field, most standard metadata can be extracted from the type
private final ImageTypeSpecifier type;
protected final ColorSpaceType colorSpaceType;
protected final boolean blackIsZero;
private final IndexColorModel palette;
protected final String compressionName;
protected final boolean compressionLossless;
protected final PlanarConfiguration planarConfiguration;
private final int[] bitsPerSample;
private final int[] significantBits;
private final int[] sampleMSB;
protected final Double pixelAspectRatio;
protected final ImageOrientation orientation;
protected final String formatVersion;
protected final SubimageInterpretation subimageInterpretation;
private final Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type, Consider a long timestamp + TimeZone to avoid messing up the API...
private final Collection<TextEntry> textEntries;
protected StandardImageMetadataSupport(Builder builder) {
notNull(builder, "builder");
// Baseline
type = builder.type;
// Chroma
colorSpaceType = builder.colorSpaceType;
blackIsZero = builder.blackIsZero;
palette = builder.palette;
// Compression
compressionName = builder.compressionName;
compressionLossless = builder.compressionLossless;
// Data
planarConfiguration = builder.planarConfiguration;
bitsPerSample = builder.bitsPerSample;
significantBits = builder.significantBits;
sampleMSB = builder.sampleMSB;
// Dimension
orientation = builder.orientation;
pixelAspectRatio = builder.pixelAspectRatio;
// Document
formatVersion = builder.formatVersion;
documentCreationTime = builder.documentCreationTime;
subimageInterpretation = builder.subimageInterpretation;
// Text
textEntries = builder.textEntries;
}
public static Builder builder(ImageTypeSpecifier type) {
return new Builder(type);
}
public static class Builder {
private final ImageTypeSpecifier type;
private ColorSpaceType colorSpaceType;
private boolean blackIsZero = true;
private IndexColorModel palette;
private String compressionName;
private boolean compressionLossless = true;
private PlanarConfiguration planarConfiguration;
public int[] bitsPerSample;
private int[] significantBits;
private int[] sampleMSB;
private Double pixelAspectRatio;
private ImageOrientation orientation = ImageOrientation.Normal;
private String formatVersion;
private SubimageInterpretation subimageInterpretation;
private Calendar documentCreationTime; // TODO: This field should be a LocalDateTime or other java.time type
private final Collection<TextEntry> textEntries = new ArrayList<>();
protected Builder(ImageTypeSpecifier type) {
this.type = notNull(type, "type");
}
public Builder withColorSpaceType(ColorSpaceType colorSpaceType) {
this.colorSpaceType = colorSpaceType;
return this;
}
public Builder withBlackIsZero(boolean blackIsZero) {
this.blackIsZero = blackIsZero;
return this;
}
public Builder withPalette(IndexColorModel palette) {
this.palette = palette;
return this;
}
public Builder withCompressionTypeName(String compressionName) {
this.compressionName = notNull(compressionName, "compressionName").equalsIgnoreCase("none") ? null : compressionName;
return this;
}
public Builder withCompressionLossless(boolean lossless) {
this.compressionLossless = isTrue(lossless || compressionName != null, lossless, "Lossy compression requires compression name");
return this;
}
public Builder withPlanarConfiguration(PlanarConfiguration planarConfiguration) {
this.planarConfiguration = planarConfiguration;
return this;
}
public Builder withBitsPerSample(int... bitsPerSample) {
this.bitsPerSample = bitsPerSample;
return this;
}
public Builder withSignificantBitsPerSample(int... significantBits) {
this.significantBits = isTrue(significantBits.length == 1 || significantBits.length == type.getNumBands(),
significantBits,
String.format("single value or %d values expected", type.getNumBands()));
return this;
}
public Builder withSampleMSB(int... sampleMSB) {
this.sampleMSB = isTrue(sampleMSB.length == 1 || sampleMSB.length == type.getNumBands(),
sampleMSB,
String.format("single value or %d values expected", type.getNumBands()));
return this;
}
public Builder withPixelAspectRatio(Double pixelAspectRatio) {
this.pixelAspectRatio = pixelAspectRatio;
return this;
}
public Builder withOrientation(ImageOrientation orientation) {
this.orientation = notNull(orientation, "orientation");
return this;
}
public Builder withFormatVersion(String formatVersion) {
this.formatVersion = notNull(formatVersion, "formatVersion");
return this;
}
public Builder withSubimageInterpretation(SubimageInterpretation interpretation) {
this.subimageInterpretation = interpretation;
return this;
}
public Builder withDocumentCreationTime(Calendar creationTime) {
this.documentCreationTime = creationTime;
return this;
}
public Builder withTextEntries(Map<String, String> entries) {
return withTextEntries(toTextEntries(notNull(entries, "entries").entrySet()));
}
private Collection<TextEntry> toTextEntries(Collection<Map.Entry<String, String>> entries) {
TextEntry[] result = new TextEntry[entries.size()];
int i = 0;
for (Map.Entry<String, String> entry : entries) {
result[i++] = new TextEntry(entry.getKey(), entry.getValue());
}
return Arrays.asList(result);
}
public Builder withTextEntries(Collection<TextEntry> entries) {
this.textEntries.addAll(notNull(entries, "entries"));
return this;
}
public Builder withTextEntry(String keyword, String value) {
if (value != null && !value.isEmpty()) {
this.textEntries.add(new TextEntry(notNull(keyword, "keyword"), value));
}
return this;
}
public IIOMetadata build() {
return new StandardImageMetadataSupport(this);
}
}
protected enum ColorSpaceType {
XYZ(3),
Lab(3),
Luv(3),
YCbCr(3),
Yxy(3),
YCCK(4),
PhotoYCC(3),
RGB(3),
GRAY(1),
HSV(3),
HLS(3),
CMYK(3),
CMY(3),
// Generic types (so much extra work, because Java names can't start with a number, phew...)
GENERIC_2CLR(2, "2CLR"),
GENERIC_3CLR(3, "3CLR"),
GENERIC_4CLR(4, "4CLR"),
GENERIC_5CLR(5, "5CLR"),
GENERIC_6CLR(6, "6CLR"),
GENERIC_7CLR(7, "7CLR"),
GENERIC_8CLR(8, "8CLR"),
GENERIC_9CLR(9, "9CLR"),
GENERIC_ACLR(0xA, "ACLR"),
GENERIC_BCLR(0xB, "BCLR"),
GENERIC_CCLR(0xC, "CCLR"),
GENERIC_DCLR(0xD, "DCLR"),
GENERIC_ECLR(0xE, "ECLR"),
GENERIC_FCLR(0xF, "FCLR");
final int numChannels;
private final String nameOverride;
ColorSpaceType(int numChannels) {
this(numChannels, null);
}
ColorSpaceType(int numChannels, String nameOverride) {
this.numChannels = numChannels;
this.nameOverride = nameOverride;
}
@Override
public String toString() {
return nameOverride != null ? nameOverride : super.toString();
}
}
protected enum PlanarConfiguration {
PixelInterleaved,
PlaneInterleaved,
LineInterleaved,
TileInterleaved
}
protected enum ImageOrientation {
Normal,
Rotate90,
Rotate180,
Rotate270,
FlipH,
FlipV,
FlipHRotate90,
FlipVRotate90
}
protected enum SubimageInterpretation {
Standalone,
SinglePage,
FullResolution,
ReducedResolution,
PyramidLayer,
Preview,
VolumeSlice,
ObjectView,
Panorama,
AnimationFrame,
TransparencyMask,
CompositingLayer,
SpectralSlice,
Unknown
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chromaNode = new IIOMetadataNode("Chroma");
ColorModel colorModel = colorSpaceType != null ? null : type.getColorModel();
ColorSpaceType csType = colorSpaceType != null ? colorSpaceType : colorSpaceType(colorModel.getColorSpace());
int numComponents = colorSpaceType != null ? colorSpaceType.numChannels : colorModel.getNumComponents();
IIOMetadataNode colorSpaceTypeNode = new IIOMetadataNode("ColorSpaceType");
chromaNode.appendChild(colorSpaceTypeNode);
colorSpaceTypeNode.setAttribute("name", csType.toString());
IIOMetadataNode numChannelsNode = new IIOMetadataNode("NumChannels");
numChannelsNode.setAttribute("value", String.valueOf(numComponents));
chromaNode.appendChild(numChannelsNode);
IIOMetadataNode blackIsZeroNode = new IIOMetadataNode("BlackIsZero");
blackIsZeroNode.setAttribute("value", booleanString(blackIsZero));
chromaNode.appendChild(blackIsZeroNode);
if (colorModel instanceof IndexColorModel || palette != null) {
IndexColorModel colorMap = palette != null ? palette : (IndexColorModel) colorModel;
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
chromaNode.appendChild(paletteNode);
for (int i = 0; i < colorMap.getMapSize(); i++) {
IIOMetadataNode paletteEntryNode = new IIOMetadataNode("PaletteEntry");
paletteNode.appendChild(paletteEntryNode);
paletteEntryNode.setAttribute("index", Integer.toString(i));
paletteEntryNode.setAttribute("red", Integer.toString(colorMap.getRed(i)));
paletteEntryNode.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
paletteEntryNode.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
// Assumption: BITMASK transparency will use single transparent pixel
if (colorMap.getTransparency() == Transparency.TRANSLUCENT) {
paletteEntryNode.setAttribute("alpha", Integer.toString(colorMap.getAlpha(i)));
}
}
if (colorMap.getTransparentPixel() != -1) {
IIOMetadataNode backgroundIndexNode = new IIOMetadataNode("BackgroundIndex");
chromaNode.appendChild(backgroundIndexNode);
backgroundIndexNode.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
}
}
// TODO: BackgroundColor?
return chromaNode;
}
private static ColorSpaceType colorSpaceType(ColorSpace colorSpace) {
switch (colorSpace.getType()) {
case ColorSpace.TYPE_XYZ:
return XYZ;
case ColorSpace.TYPE_Lab:
return Lab;
case ColorSpace.TYPE_Luv:
return Luv;
case ColorSpace.TYPE_YCbCr:
return YCbCr;
case ColorSpace.TYPE_Yxy:
return Yxy;
// Note: Can't map to YCCK or PhotoYCC, as there's no corresponding constant in java.awt.ColorSpace
case ColorSpace.TYPE_RGB:
return RGB;
case ColorSpace.TYPE_GRAY:
return GRAY;
case ColorSpace.TYPE_HSV:
return HSV;
case ColorSpace.TYPE_HLS:
return HLS;
case ColorSpace.TYPE_CMYK:
return CMYK;
case ColorSpace.TYPE_CMY:
return CMY;
default:
int numComponents = colorSpace.getNumComponents();
if (numComponents == 1) {
return GRAY;
}
else if (numComponents < 16) {
return ColorSpaceType.valueOf("GENERIC_" + Integer.toHexString(numComponents) + "CLR");
}
}
throw new IllegalArgumentException("Unknown ColorSpace type: " + colorSpace);
}
protected static final class TextEntry {
static final List<String> COMPRESSIONS = Arrays.asList("none", "lzw", "zip", "bzip", "other");
final String keyword;
final String value;
final String language;
final String encoding;
final String compression;
public TextEntry(final String keyword, final String value) {
this(keyword, value, null, null, null);
}
public TextEntry(final String keyword, final String value, final String language, final String encoding, final String compression) {
this.keyword = keyword;
this.value = notNull(value, "value");
this.language = language;
this.encoding = encoding;
this.compression = isTrue(compression == null || COMPRESSIONS.contains(compression), compression, String.format("Unknown compression: %s (expected: %s)", compression, COMPRESSIONS));
}
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (compressionName == null) {
return null;
}
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", compressionName);
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", booleanString(compressionLossless));
node.appendChild(lossless);
return node;
}
protected static String booleanString(boolean booleanValue) {
return booleanValue ? "TRUE" : "FALSE";
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
IIOMetadataNode planarConfigurationNode = new IIOMetadataNode("PlanarConfiguration");
dataNode.appendChild(planarConfigurationNode);
planarConfigurationNode.setAttribute("value", planarConfiguration != null ? planarConfiguration.toString() :
(type.getSampleModel() instanceof BandedSampleModel ? "PlaneInterleaved" : "PixelInterleaved"));
String sampleFormatValue = colorSpaceType == null && type.getColorModel() instanceof IndexColorModel
? "Index"
: sampleFormat(type.getSampleModel());
if (sampleFormatValue != null) {
IIOMetadataNode sampleFormatNode = new IIOMetadataNode("SampleFormat");
sampleFormatNode.setAttribute("value", sampleFormatValue);
dataNode.appendChild(sampleFormatNode);
}
int[] bitsPerSample = this.bitsPerSample != null ? this.bitsPerSample : type.getSampleModel().getSampleSize();
IIOMetadataNode bitsPerSampleNode = new IIOMetadataNode("BitsPerSample");
bitsPerSampleNode.setAttribute("value", createListValue(bitsPerSample.length, bitsPerSample));
dataNode.appendChild(bitsPerSampleNode);
if (significantBits != null) {
String significantBitsValue = createListValue(type.getNumBands(), significantBits);
if (!significantBitsValue.equals(bitsPerSampleNode.getAttribute("value"))) {
IIOMetadataNode significantBitsPerSampleNode = new IIOMetadataNode("SignificantBitsPerSample");
significantBitsPerSampleNode.setAttribute("value", significantBitsValue);
dataNode.appendChild(significantBitsPerSampleNode);
}
}
if (sampleMSB != null) {
// TODO: Only if different from default!
IIOMetadataNode sampleMSBNode = new IIOMetadataNode("SampleMSB");
sampleMSBNode.setAttribute("value", createListValue(type.getNumBands(), sampleMSB));
dataNode.appendChild(sampleMSBNode);
}
return dataNode;
}
private static String createListValue(final int itemCount, final int... values) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < itemCount; i++) {
if (buffer.length() > 0) {
buffer.append(' ');
}
buffer.append(values[i % values.length]);
}
return buffer.toString();
}
private static String sampleFormat(SampleModel sampleModel) {
switch (sampleModel.getDataType()) {
case DataBuffer.TYPE_SHORT:
case DataBuffer.TYPE_INT:
if (sampleModel instanceof ComponentSampleModel) {
return "SignedIntegral";
}
// Otherwise fall-through, most likely a *PixelPackedSampleModel
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
return "UnsignedIntegral";
case DataBuffer.TYPE_FLOAT:
case DataBuffer.TYPE_DOUBLE:
return "Real";
default:
return null;
}
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
if (pixelAspectRatio != null) {
IIOMetadataNode pixelAspectRatioNode = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatioNode.setAttribute("value", String.valueOf(pixelAspectRatio));
dimensionNode.appendChild(pixelAspectRatioNode);
}
IIOMetadataNode imageOrientationNode = new IIOMetadataNode("ImageOrientation");
imageOrientationNode.setAttribute("value", orientation.toString());
dimensionNode.appendChild(imageOrientationNode);
return dimensionNode.hasChildNodes() ? dimensionNode : null;
}
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode documentNode = new IIOMetadataNode("Document");
if (formatVersion != null) {
IIOMetadataNode formatVersionNode = new IIOMetadataNode("FormatVersion");
documentNode.appendChild(formatVersionNode);
formatVersionNode.setAttribute("value", formatVersion);
}
if (subimageInterpretation != null) {
IIOMetadataNode subImageInterpretationNode = new IIOMetadataNode("SubimageInterpretation");
documentNode.appendChild(subImageInterpretationNode);
subImageInterpretationNode.setAttribute("value", subimageInterpretation.toString());
}
if (documentCreationTime != null) {
IIOMetadataNode imageCreationTimeNode = new IIOMetadataNode("ImageCreationTime");
documentNode.appendChild(imageCreationTimeNode);
imageCreationTimeNode.setAttribute("year", String.valueOf(documentCreationTime.get(Calendar.YEAR)));
imageCreationTimeNode.setAttribute("month", String.valueOf(documentCreationTime.get(Calendar.MONTH) + 1));
imageCreationTimeNode.setAttribute("day", String.valueOf(documentCreationTime.get(Calendar.DAY_OF_MONTH)));
imageCreationTimeNode.setAttribute("hour", String.valueOf(documentCreationTime.get(Calendar.HOUR_OF_DAY)));
imageCreationTimeNode.setAttribute("minute", String.valueOf(documentCreationTime.get(Calendar.MINUTE)));
imageCreationTimeNode.setAttribute("second", String.valueOf(documentCreationTime.get(Calendar.SECOND)));
}
return documentNode.hasChildNodes() ? documentNode : null;
}
@Override
protected IIOMetadataNode getStandardTextNode() {
if (textEntries.isEmpty()) {
return null;
}
IIOMetadataNode textNode = new IIOMetadataNode("Text");
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
for (TextEntry entry : textEntries) {
IIOMetadataNode textEntryNode = new IIOMetadataNode("TextEntry");
textNode.appendChild(textEntryNode);
if (entry.keyword != null) {
textEntryNode.setAttribute("keyword", entry.keyword);
}
textEntryNode.setAttribute("value", entry.value);
if (entry.language != null) {
textEntryNode.setAttribute("language", entry.language);
}
if (entry.encoding != null) {
textEntryNode.setAttribute("encoding", entry.encoding);
}
if (entry.compression != null) {
textEntryNode.setAttribute("compression", entry.compression);
}
}
return textNode;
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparencyNode = new IIOMetadataNode("Transparency");
ColorModel colorModel = type.getColorModel();
IIOMetadataNode alphaNode = new IIOMetadataNode("Alpha");
transparencyNode.appendChild(alphaNode);
alphaNode.setAttribute("value", colorModel.hasAlpha() ? (colorModel.isAlphaPremultiplied() ? "premultiplied" : "nonpremultiplied") : "none");
if (colorModel instanceof IndexColorModel) {
IndexColorModel icm = (IndexColorModel) colorModel;
if (icm.getTransparentPixel() != -1) {
IIOMetadataNode transparentIndexNode = new IIOMetadataNode("TransparentIndex");
transparencyNode.appendChild(transparentIndexNode);
transparentIndexNode.setAttribute("value", Integer.toString(icm.getTransparentPixel()));
}
}
return transparencyNode;
}
}
@@ -54,8 +54,8 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
// Our IndexColorModel delegate
private final IndexColorModel icm;
private final int extraSamples;
private final int samples;
private final boolean hasAlpha;
/**
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
@@ -86,33 +86,33 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
);
this.icm = icm;
this.extraSamples = extraSamples;
this.samples = 1 + extraSamples;
this.hasAlpha = hasAlpha;
}
@Override
public int getNumComponents() {
return getNumColorComponents() + extraSamples;
return samples;
}
@Override
public int getRed(final int pixel) {
public final int getRed(final int pixel) {
return icm.getRed(pixel);
}
@Override
public int getGreen(final int pixel) {
public final int getGreen(final int pixel) {
return icm.getGreen(pixel);
}
@Override
public int getBlue(final int pixel) {
public final int getBlue(final int pixel) {
return icm.getBlue(pixel);
}
@Override
public int getAlpha(final int pixel) {
return hasAlpha() ? (int) ((((float) pixel) / ((1 << getComponentSize(3)) - 1)) * 255.0f + 0.5f) : 0xff;
public final int getAlpha(final int pixel) {
return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
}
private int getSample(final Object inData, final int index) {
@@ -120,15 +120,15 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
switch (transferType) {
case DataBuffer.TYPE_BYTE:
byte[] bdata = (byte[]) inData;
byte bdata[] = (byte[]) inData;
pixel = bdata[index] & 0xff;
break;
case DataBuffer.TYPE_USHORT:
short[] sdata = (short[]) inData;
short sdata[] = (short[]) inData;
pixel = sdata[index] & 0xffff;
break;
case DataBuffer.TYPE_INT:
int[] idata = (int[]) inData;
int idata[] = (int[]) inData;
pixel = idata[index];
break;
default:
@@ -139,27 +139,27 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
}
@Override
public int getRed(final Object inData) {
public final int getRed(final Object inData) {
return getRed(getSample(inData, 0));
}
@Override
public int getGreen(final Object inData) {
public final int getGreen(final Object inData) {
return getGreen(getSample(inData, 0));
}
@Override
public int getBlue(final Object inData) {
public final int getBlue(final Object inData) {
return getBlue(getSample(inData, 0));
}
@Override
public int getAlpha(final Object inData) {
return hasAlpha() ? getAlpha(getSample(inData, 1)) : 0xff;
public final int getAlpha(final Object inData) {
return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
}
@Override
public SampleModel createCompatibleSampleModel(final int w, final int h) {
public final SampleModel createCompatibleSampleModel(final int w, final int h) {
return new PixelInterleavedSampleModel(transferType, w, h, samples, w * samples, createOffsets(samples));
}
@@ -174,17 +174,17 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
}
@Override
public boolean isCompatibleSampleModel(final SampleModel sm) {
public final boolean isCompatibleSampleModel(final SampleModel sm) {
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
}
@Override
public WritableRaster createCompatibleWritableRaster(final int w, final int h) {
public final WritableRaster createCompatibleWritableRaster(final int w, final int h) {
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
}
@Override
public boolean isCompatibleRaster(final Raster raster) {
public final boolean isCompatibleRaster(final Raster raster) {
int size = raster.getSampleModel().getSampleSize(0);
return ((raster.getTransferType() == transferType) &&
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
@@ -1,348 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import javax.imageio.stream.ImageInputStreamImpl;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.max;
/**
* A buffered {@link javax.imageio.stream.ImageInputStream} that is backed by a {@link java.nio.channels.SeekableByteChannel}
* and provides greatly improved performance
* compared to {@link javax.imageio.stream.FileCacheImageInputStream} or {@link javax.imageio.stream.MemoryCacheImageInputStream}
* for shorter reads, like single byte or bit reads.
*/
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private static final Closeable CLOSEABLE_STUB = new Closeable() {
@Override public void close() {}
};
static final int DEFAULT_BUFFER_SIZE = 8192;
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
private byte[] buffer = byteBuffer.array();
private int bufferPos;
private int bufferLimit;
private final ByteBuffer integralCache = ByteBuffer.allocate(8);
private final byte[] integralCacheArray = integralCache.array();
private SeekableByteChannel channel;
private Closeable closeable;
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
*
* @param file a {@code File} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
* @throws IOException if an I/O error occurs while opening the file.
*/
public BufferedChannelImageInputStream(final File file) throws IOException {
this(notNull(file, "file").toPath());
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Path}.
*
* @param file a {@code Path} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
* @throws UnsupportedOperationException if the {@code file} is associated with a provider that does not support creating file channels.
* @throws IOException if an I/O error occurs while opening the file.
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
*/
public BufferedChannelImageInputStream(final Path file) throws IOException {
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
*
* @param file a {@code RandomAccessFile} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
*/
public BufferedChannelImageInputStream(final RandomAccessFile file) {
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
this(notNull(file, "file").getChannel(), true);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
* <p>
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
* </p>
*
* @param inputStream a {@code FileInputStream} to read from.
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
*/
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
this(notNull(inputStream, "inputStream").getChannel(), false);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
* <p>
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
* </p>
*
* @param channel a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}.
*/
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
this(notNull(channel, "channel"), false);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
* <p>
* Closing this stream will close the {@code Cache}.
* </p>
*
* @param cache a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}.
*/
BufferedChannelImageInputStream(final Cache cache) {
this(notNull(cache, "cache"), true);
}
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
this.channel = notNull(channel, "channel");
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
private boolean fillBuffer() throws IOException {
byteBuffer.rewind();
int length = channel.read(byteBuffer);
bufferPos = 0;
bufferLimit = max(length, 0);
return bufferLimit > 0;
}
private boolean bufferEmpty() {
return bufferPos >= bufferLimit;
}
@Override
public void setByteOrder(ByteOrder byteOrder) {
super.setByteOrder(byteOrder);
integralCache.order(byteOrder);
}
@Override
public int read() throws IOException {
checkClosed();
if (bufferEmpty() && !fillBuffer()) {
return -1;
}
bitOffset = 0;
streamPos++;
return buffer[bufferPos++] & 0xff;
}
@Override
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
checkClosed();
bitOffset = 0;
if (bufferEmpty()) {
// Bypass buffer if buffer is empty for reads longer than buffer
if (length >= buffer.length) {
return readDirect(bytes, offset, length);
}
else if (!fillBuffer()) {
return -1;
}
}
int fromBuffer = readBuffered(bytes, offset, length);
if (length > fromBuffer) {
// Due to known bugs in certain JDK-bundled ImageIO plugins expecting read to behave as readFully,
// we'll read as much as possible from the buffer, and the rest directly after
return fromBuffer + max(0, readDirect(bytes, offset + fromBuffer, length - fromBuffer));
}
return fromBuffer;
}
private int readDirect(final byte[] bytes, final int offset, final int length) throws IOException {
// Invalidate the buffer, as its contents is no longer in sync with the stream's position.
bufferLimit = 0;
ByteBuffer wrapped = ByteBuffer.wrap(bytes, offset, length);
int read = 0;
while (wrapped.hasRemaining()) {
int count = channel.read(wrapped);
if (count == -1) {
if (read == 0) {
return -1;
}
break;
}
read += count;
}
streamPos += read;
return read;
}
private int readBuffered(final byte[] bytes, final int offset, final int length) {
// Read as much as possible from buffer
int available = Math.min(bufferLimit - bufferPos, length);
if (available > 0) {
System.arraycopy(buffer, bufferPos, bytes, offset, available);
bufferPos += available;
streamPos += available;
}
return available;
}
public long length() {
// WTF?! This method is allowed to throw IOException in the interface...
try {
checkClosed();
return channel.size();
}
catch (IOException ignore) {
}
return -1;
}
public void close() throws IOException {
super.close();
buffer = null;
byteBuffer = null;
channel = null;
try {
closeable.close();
}
finally {
closeable = null;
}
}
// Need to override the readShort(), readInt() and readLong() methods,
// because the implementations in ImageInputStreamImpl expects the
// read(byte[], int, int) to always read the expected number of bytes,
// causing uninitialized values, alignment issues and EOFExceptions at
// random places...
// Notes:
// * readUnsignedXx() is covered by their signed counterparts
// * readChar() is covered by readShort()
// * readFloat() and readDouble() is covered by readInt() and readLong()
// respectively.
// * readLong() may be covered by two readInt()s, we'll override to be safe
@Override
public short readShort() throws IOException {
readFully(integralCacheArray, 0, 2);
return integralCache.getShort(0);
}
@Override
public int readInt() throws IOException {
readFully(integralCacheArray, 0, 4);
return integralCache.getInt(0);
}
@Override
public long readLong() throws IOException {
readFully(integralCacheArray, 0, 8);
return integralCache.getLong(0);
}
@Override
public void seek(long position) throws IOException {
checkClosed();
if (position < flushedPos) {
throw new IndexOutOfBoundsException("position < flushedPos!");
}
bitOffset = 0;
if (streamPos == position) {
return;
}
// Optimized to not invalidate buffer if new position is within current buffer
long newBufferPos = bufferPos + position - streamPos;
if (newBufferPos >= 0 && newBufferPos < bufferLimit) {
bufferPos = (int) newBufferPos;
}
else {
// Will invalidate buffer
bufferLimit = 0;
channel.position(position);
}
streamPos = position;
}
@Override
public void flushBefore(final long pos) throws IOException {
super.flushBefore(pos);
if (channel instanceof Cache) {
// In case of memory cache, free up memory
((Cache) channel).flushBefore(pos);
}
}
}
@@ -49,7 +49,6 @@ import static java.lang.Math.max;
* {@link File} or {@link RandomAccessFile} can be used as input.
*
* @see javax.imageio.stream.FileImageInputStream
* @deprecated Use {@link BufferedChannelImageInputStream} instead.
*/
// TODO: Create a memory-mapped version?
// Or not... From java.nio.channels.FileChannel.map:
@@ -58,7 +57,6 @@ import static java.lang.Math.max;
// the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively
// large files into memory.
@Deprecated
public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
static final int DEFAULT_BUFFER_SIZE = 8192;
@@ -192,10 +190,10 @@ public final class BufferedFileImageInputStream extends ImageInputStreamImpl {
public void close() throws IOException {
super.close();
buffer = null;
raf.close();
raf = null;
buffer = null;
}
// Need to override the readShort(), readInt() and readLong() methods,
@@ -37,19 +37,18 @@ import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.NoSuchFileException;
import java.util.Iterator;
import java.util.Locale;
/**
* BufferedFileImageInputStreamSpi
* Experimental
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/
public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedFileImageInputStreamSpi() {
this(new StreamProviderInfo());
}
@@ -70,13 +69,12 @@ public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
if (input instanceof File) {
try {
return new BufferedChannelImageInputStream((File) input);
return new BufferedFileImageInputStream((File) input);
}
catch (FileNotFoundException | NoSuchFileException e) {
catch (FileNotFoundException e) {
// For consistency with the JRE bundled SPIs, we'll return null here,
// even though the spec does not say that's allowed.
// The problem is that the SPIs can only declare that they support an input type like a File,
@@ -93,8 +91,7 @@ public final class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi {
return false;
}
@Override
public String getDescription(final Locale locale) {
public String getDescription(final Locale pLocale) {
return "Service provider that instantiates an ImageInputStream from a File";
}
@@ -1,78 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ServiceRegistry;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.Iterator;
import java.util.Locale;
/**
* BufferedInputStreamImageInputStreamSpi.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpi.java,v 1.0 08/09/2022 haraldk Exp$
*/
public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedInputStreamImageInputStreamSpi() {
this(new StreamProviderInfo());
}
private BufferedInputStreamImageInputStreamSpi(ProviderInfo providerInfo) {
super(providerInfo.getVendorName(), providerInfo.getVersion(), InputStream.class);
}
@Override
public void onRegistration(final ServiceRegistry registry, final Class<?> category) {
Iterator<ImageInputStreamSpi> providers = registry.getServiceProviders(ImageInputStreamSpi.class, new InputStreamFilter(), true);
while (providers.hasNext()) {
ImageInputStreamSpi provider = providers.next();
if (provider != this) {
registry.setOrdering(ImageInputStreamSpi.class, this, provider);
}
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof InputStream) {
ReadableByteChannel channel = Channels.newChannel((InputStream) input);
if (channel instanceof SeekableByteChannel) {
// Special case for FileInputStream/FileChannel, we can get a seekable channel directly
return new BufferedChannelImageInputStream((SeekableByteChannel) channel);
}
// Otherwise, create a cache for backwards seeking
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
}
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
}
@Override
public boolean canUseCacheFile() {
return true;
}
@Override
public String getDescription(final Locale locale) {
return "Service provider that instantiates an ImageInputStream from an InputStream";
}
private static class InputStreamFilter implements ServiceRegistry.Filter {
@Override
public boolean filter(final Object provider) {
return ((ImageInputStreamSpi) provider).getInputClass() == InputStream.class;
}
}
}
@@ -48,7 +48,7 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$
* @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/
public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
public BufferedRAFImageInputStreamSpi() {
this(new StreamProviderInfo());
}
@@ -69,10 +69,9 @@ public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
}
}
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) {
public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) {
if (input instanceof RandomAccessFile) {
return new BufferedChannelImageInputStream((RandomAccessFile) input);
return new BufferedFileImageInputStream((RandomAccessFile) input);
}
throw new IllegalArgumentException("Expected input of type RandomAccessFile: " + input);
@@ -83,8 +82,7 @@ public final class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi {
return false;
}
@Override
public String getDescription(final Locale locale) {
public String getDescription(final Locale pLocale) {
return "Service provider that instantiates an ImageInputStream from a RandomAccessFile";
}
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
private final int dataOffset;
private final int dataLength;
public ByteArrayImageInputStream(final byte[] data) {
this(data, 0, data != null ? data.length : -1);
public ByteArrayImageInputStream(final byte[] pData) {
this(pData, 0, pData != null ? pData.length : -1);
}
public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
this.data = notNull(data, "data");
dataOffset = isMax(data.length, offset, "offset");
dataLength = isMax(data.length - offset, length, "length");
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
data = notNull(pData, "data");
dataOffset = isBetween(0, pData.length, offset, "offset");
dataLength = isBetween(0, pData.length - offset, length, "length");
}
private static int isMax(final int high, final int value, final String name) {
return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
private static int isBetween(final int low, final int high, final int value, final String name) {
return isTrue(value >= low && value <= high, value, String.format("%s out of range [%d, %d]: %d", name, low, high, value));
}
public int read() throws IOException {
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
return data[((int) streamPos++) + dataOffset] & 0xff;
}
public int read(byte[] buffer, int offset, int len) throws IOException {
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
if (streamPos >= dataLength) {
return -1;
}
int length = (int) Math.min(dataLength - streamPos, len);
int length = (int) Math.min(this.dataLength - streamPos, pLength);
bitOffset = 0;
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
streamPos += length;
return length;
@@ -45,7 +45,7 @@ import java.util.Locale;
* @author last modified by $Author: haraldk$
* @version $Id: ByteArrayImageInputStreamSpi.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$
*/
public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
public class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
public ByteArrayImageInputStreamSpi() {
this(new StreamProviderInfo());
@@ -55,17 +55,16 @@ public final class ByteArrayImageInputStreamSpi extends ImageInputStreamSpi {
super(providerInfo.getVendorName(), providerInfo.getVersion(), byte[].class);
}
@Override
public ImageInputStream createInputStreamInstance(Object input, boolean useCacheFile, File cacheDir) {
if (input instanceof byte[]) {
return new ByteArrayImageInputStream((byte[]) input);
public ImageInputStream createInputStreamInstance(Object pInput, boolean pUseCache, File pCacheDir) {
if (pInput instanceof byte[]) {
return new ByteArrayImageInputStream((byte[]) pInput);
}
else {
throw new IllegalArgumentException("Expected input of type byte[]: " + pInput);
}
throw new IllegalArgumentException("Expected input of type byte[]: " + input);
}
@Override
public String getDescription(Locale locale) {
public String getDescription(Locale pLocale) {
return "Service provider that instantiates an ImageInputStream from a byte array";
}
@@ -1,7 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import java.nio.channels.SeekableByteChannel;
interface Cache extends SeekableByteChannel {
void flushBefore(long pos);
}
@@ -1,133 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import javax.imageio.stream.ImageInputStreamImpl;
import java.io.IOException;
import java.io.InputStream;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* An {@code ImageInputStream} that adapts an {@code InputSteam},
* by reading directly from the stream without and form of caching or buffering.
* <p>
* Note: This is <em>not</em> a general-purpose {@code ImageInputStream}, and is designed for reading large chunks,
* typically of pixel data, from an {@code InputStream}.
* It does <em>not</em> support backwards seeking, or reading bits.
* </p>
*/
public final class DirectImageInputStream extends ImageInputStreamImpl {
private final InputStream stream;
private final long length;
public DirectImageInputStream(final InputStream stream) {
this(stream, -1L);
}
public DirectImageInputStream(final InputStream stream, long length) {
this.stream = notNull(stream, "stream");
this.length = isTrue(length >= 0L || length == -1L, length, "negative length: %d");
}
@Override
public int read() throws IOException {
bitOffset = 0;
streamPos++;
return stream.read();
}
@Override
public int read(final byte[] bytes, int off, int len) throws IOException {
bitOffset = 0;
int read = stream.read(bytes, off, len);
if (read > 0) {
streamPos += read;
}
return read;
}
@Override
public void seek(long pos) throws IOException {
checkClosed();
if (pos < streamPos) {
// Handle as if flushedPos == streamPos at any time
throw new IndexOutOfBoundsException("pos < flushedPos");
}
bitOffset = 0;
while (streamPos < pos) {
long skipped = stream.skip(pos - streamPos);
if (skipped <= 0) {
break;
}
streamPos += skipped;
}
}
@Override
public long getFlushedPosition() {
// Handle as if flushedPos == streamPos at any time
return streamPos;
}
@Override
public long length() {
return length;
}
@SuppressWarnings("RedundantThrows")
@Override
public int readBit() throws IOException {
throw new UnsupportedOperationException("Bit reading not supported");
}
@SuppressWarnings("RedundantThrows")
@Override
public long readBits(int numBits) throws IOException {
throw new UnsupportedOperationException("Bit reading not supported");
}
@Override
public void close() throws IOException {
// We could seek to EOF here, but the usual case is we know where the next chunk of data is
stream.close();
super.close();
}
}
@@ -1,112 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.max;
import static java.nio.file.StandardOpenOption.DELETE_ON_CLOSE;
import static java.nio.file.StandardOpenOption.READ;
import static java.nio.file.StandardOpenOption.WRITE;
// Note: We could consider creating a memory-mapped version...
// But, from java.nio.channels.FileChannel.map:
// For most operating systems, mapping a file into memory is more
// expensive than reading or writing a few tens of kilobytes of data via
// the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively
// large files into memory.
final class FileCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private final FileChannel cache;
private final ReadableByteChannel channel;
// TODO: Perhaps skip this constructor?
FileCache(InputStream stream, File cacheDir) throws IOException {
// Stream will be closed with channel, documented behavior
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
}
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
this.channel = notNull(channel, "channel");
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
// Create a temp file to hold our cache,
// will be deleted when this channel is closed, as we close the cache
Path cacheFile = cacheDir == null
? Files.createTempFile("imageio", ".tmp")
: Files.createTempFile(cacheDir.toPath(), "imageio", ".tmp");
cache = FileChannel.open(cacheFile, DELETE_ON_CLOSE, READ, WRITE);
}
@SuppressWarnings("StatementWithEmptyBody")
void fetch() throws IOException {
while (cache.position() >= cache.size() && cache.transferFrom(channel, cache.size(), max(cache.position() - cache.size(), BLOCK_SIZE)) > 0) {
// Continue transfer...
}
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
cache.close();
}
@Override
public int read(ByteBuffer dest) throws IOException {
fetch();
if (cache.position() >= cache.size()) {
return -1;
}
return cache.read(dest);
}
@Override
public long position() throws IOException {
return cache.position();
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
cache.position(newPosition);
return this;
}
@Override
public long size() {
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
return -1;
}
@Override
public int write(ByteBuffer src) {
throw new NonWritableChannelException();
}
@Override
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override public void flushBefore(long pos) {
}
}
@@ -1,152 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SeekableByteChannel;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.min;
final class MemoryCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private final List<byte[]> cache = new ArrayList<>();
private final ReadableByteChannel channel;
private long length;
private long position;
private long start;
// TODO: Maybe get rid of this constructor, as we don't want to do this if we have a FileInputStream/FileChannel...
MemoryCache(InputStream stream) {
this(Channels.newChannel(notNull(stream, "stream")));
}
public MemoryCache(ReadableByteChannel channel) {
this.channel = notNull(channel, "channel");
}
byte[] fetchBlock() throws IOException {
long currPos = position;
long index = currPos / BLOCK_SIZE;
if (index >= Integer.MAX_VALUE) {
throw new IOException("Memory cache max size exceeded");
}
while (index >= cache.size()) {
byte[] block;
try {
block = new byte[BLOCK_SIZE];
}
catch (OutOfMemoryError e) {
throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
}
cache.add(block);
length += readBlock(block);
}
return cache.get((int) index);
}
private int readBlock(final byte[] block) throws IOException {
ByteBuffer wrapped = ByteBuffer.wrap(block);
while (wrapped.hasRemaining()) {
int count = channel.read(wrapped);
if (count == -1) {
// Last block
break;
}
}
return wrapped.position();
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
cache.clear();
}
@Override
public int read(ByteBuffer dest) throws IOException {
byte[] buffer = fetchBlock();
int bufferPos = (int) (position % BLOCK_SIZE);
if (position >= length) {
return -1;
}
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
dest.put(buffer, bufferPos, len);
position += len;
return len;
}
@Override
public long position() throws IOException {
return position;
}
@Override
public SeekableByteChannel position(long newPosition) throws IOException {
if (newPosition < start) {
throw new IOException("Seek before flush position");
}
this.position = newPosition;
return this;
}
@Override
public long size() throws IOException {
// We could allow the size to grow, but that means the stream cannot rely on this size, so we'll just pretend we don't know...
return -1;
}
@Override
public int write(ByteBuffer src) {
throw new NonWritableChannelException();
}
@Override
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override
public void flushBefore(long pos) {
if (pos < start) {
throw new IndexOutOfBoundsException("pos < flushed position");
}
if (pos > position) {
throw new IndexOutOfBoundsException("pos > current position");
}
int blocks = (int) (pos / BLOCK_SIZE); // Overflow guarded for in fetchBlock
// Clear blocks no longer needed
for (int i = 0; i < blocks; i++) {
cache.set(i, null);
}
start = pos;
}
}
@@ -33,7 +33,9 @@ package com.twelvemonkeys.imageio.stream;
import com.twelvemonkeys.imageio.spi.ProviderInfo;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.FileCacheImageInputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -50,7 +52,7 @@ import java.util.Locale;
* @version $Id: URLImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$
*/
// TODO: URI instead of URL?
public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
public class URLImageInputStreamSpi extends ImageInputStreamSpi {
public URLImageInputStreamSpi() {
this(new StreamProviderInfo());
}
@@ -62,28 +64,53 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
// TODO: Create a URI or URLImageInputStream class, with a getUR[I|L] method, to allow for multiple file formats
// The good thing with that is that it does not clash with the built-in Sun-stuff or other people's hacks
// The bad thing is that most people don't expect there to be an UR[I|L]ImageInputStreamSpi..
@Override
public ImageInputStream createInputStreamInstance(final Object input, final boolean useCacheFile, final File cacheDir) throws IOException {
if (input instanceof URL) {
URL url = (URL) input;
public ImageInputStream createInputStreamInstance(final Object pInput, final boolean pUseCache, final File pCacheDir) throws IOException {
if (pInput instanceof URL) {
URL url = (URL) pInput;
// Special case for file protocol, a lot faster than FileCacheImageInputStream
if ("file".equals(url.getProtocol())) {
try {
return new BufferedChannelImageInputStream(new File(url.toURI()));
return new BufferedFileImageInputStream(new File(url.toURI()));
}
catch (URISyntaxException shouldNeverHappen) {
// This should never happen, but if it does, we'll fall back to using the stream
shouldNeverHappen.printStackTrace();
catch (URISyntaxException ignore) {
// This should never happen, but if it does, we'll fall back to using the stream
ignore.printStackTrace();
}
}
// Otherwise revert to cached
InputStream urlStream = url.openStream();
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
final InputStream urlStream = url.openStream();
if (pUseCache) {
return new FileCacheImageInputStream(urlStream, pCacheDir) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
}
}
};
}
else {
return new MemoryCacheImageInputStream(urlStream) {
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original..
}
}
};
}
}
else {
throw new IllegalArgumentException("Expected input of type URL: " + pInput);
}
throw new IllegalArgumentException("Expected input of type URL: " + input);
}
@Override
@@ -91,7 +118,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
return true;
}
public String getDescription(final Locale locale) {
public String getDescription(final Locale pLocale) {
return "Service provider that instantiates an ImageInputStream from a URL";
}
}
@@ -81,7 +81,7 @@ class IIOInputStreamAdapter extends InputStream {
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
Validate.notNull(pInput, "stream");
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %d");
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %f");
input = pInput;
left = pLength;
@@ -30,15 +30,22 @@
package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.SampleModel;
import javax.imageio.ImageTypeSpecifier;
import com.twelvemonkeys.imageio.color.DiscreteAlphaIndexColorModel;
/**
* Factory class for creating {@code ImageTypeSpecifier}s.
* Fixes some subtle bugs in {@code ImageTypeSpecifier}'s factory methods, but
@@ -51,52 +58,28 @@ import static com.twelvemonkeys.lang.Validate.notNull;
*/
public final class ImageTypeSpecifiers {
private static final ImageTypeSpecifier TYPE_INT_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
0xFF0000,
0x00FF00,
0x0000FF,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_INT_BGR = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 24,
0x0000FF,
0x00FF00,
0xFF0000,
0x0,
DataBuffer.TYPE_INT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_565_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 16,
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
private static final ImageTypeSpecifier TYPE_USHORT_555_RGB = createPackedOddBits(ColorSpace.getInstance(ColorSpace.CS_sRGB), 15,
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
private ImageTypeSpecifiers() {}
public static ImageTypeSpecifier createFromBufferedImageType(final int bufferedImageType) {
switch (bufferedImageType) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the INT_RGB and USHORT types
case BufferedImage.TYPE_INT_RGB:
return TYPE_INT_RGB;
case BufferedImage.TYPE_INT_BGR:
return TYPE_INT_BGR;
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for the USHORT types
case BufferedImage.TYPE_USHORT_565_RGB:
return TYPE_USHORT_565_RGB;
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0xF800,
0x07E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
case BufferedImage.TYPE_USHORT_555_RGB:
return TYPE_USHORT_555_RGB;
return createPacked(ColorSpace.getInstance(ColorSpace.CS_sRGB),
0x7C00,
0x03E0,
0x001F,
0x0,
DataBuffer.TYPE_USHORT,
false);
default:
}
@@ -107,41 +90,23 @@ public final class ImageTypeSpecifiers {
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
int bits = calculateRequiredBits(redMask | greenMask | blueMask | alphaMask);
if (bits != 32) {
if (transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround for BYTE/USHORT types
return createPackedOddBits(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
return ImageTypeSpecifier.createPacked(colorSpace, redMask, greenMask, blueMask, alphaMask, transferType, isAlphaPremultiplied);
}
private static int calculateRequiredBits(int mask) {
// See https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
int r = 1;
while ((mask >>>= 1) != 0) {
r++;
}
return r;
}
static ImageTypeSpecifier createPackedOddBits(final ColorSpace colorSpace, int bits,
final int redMask, final int greenMask,
final int blueMask, final int alphaMask,
final int transferType, boolean isAlphaPremultiplied) {
// ImageTypeSpecifier unconditionally uses bits == 32, we'll use a workaround
notNull(colorSpace, "colorSpace");
isTrue(colorSpace.getType() == ColorSpace.TYPE_RGB, colorSpace, "ColorSpace must be TYPE_RGB");
isTrue(redMask != 0 || greenMask != 0 || blueMask != 0 || alphaMask != 0, "No mask has at least 1 bit set");
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask,
isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
public static ImageTypeSpecifier createInterleaved(final ColorSpace colorSpace,
final int[] bandOffsets,
final int dataType,
@@ -270,20 +235,4 @@ public final class ImageTypeSpecifiers {
ColorModel colorModel = new DiscreteAlphaIndexColorModel(pColorModel, extraSamples, hasAlpha);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
public static ImageTypeSpecifier createFromRenderedImage(RenderedImage image) {
if (image == null) {
throw new IllegalArgumentException("image == null!");
}
if (image instanceof BufferedImage) {
int bufferedImageType = ((BufferedImage) image).getType();
if (bufferedImageType != BufferedImage.TYPE_CUSTOM) {
return createFromBufferedImageType(bufferedImageType);
}
}
return new ImageTypeSpecifier(image);
}
}
@@ -1,5 +1,4 @@
com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi
com.twelvemonkeys.imageio.stream.BufferedInputStreamImageInputStreamSpi
# Use SPI loading as a hook for early profile activation
com.twelvemonkeys.imageio.color.ProfileDeferralActivator$Spi
@@ -1,332 +0,0 @@
package com.twelvemonkeys.imageio;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ColorSpaceType;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.ImageOrientation;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.PlanarConfiguration;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.SubimageInterpretation;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport.TextEntry;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import org.junit.Test;
import org.w3c.dom.NodeList;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import static com.twelvemonkeys.imageio.StandardImageMetadataSupport.builder;
import static org.junit.Assert.*;
public class StandardImageMetadataSupportTest {
@Test(expected = IllegalArgumentException.class)
public void createNullBuilder() {
new StandardImageMetadataSupport(null);
}
@Test(expected = IllegalArgumentException.class)
public void createNullType() {
new StandardImageMetadataSupport(builder(null));
}
@Test(expected = IllegalArgumentException.class)
public void builderNullType() {
builder(null).build();
}
@Test
public void createValid() {
IIOMetadata metadata = new StandardImageMetadataSupport(builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
assertNotNull(metadata);
}
@Test
public void builderValid() {
IIOMetadata metadata = builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB))
.build();
assertNotNull(metadata);
}
@Test
public void compressionValuesUnspecified() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.build();
assertNull(metadata.getStandardCompressionNode());
}
@Test
public void compressionValuesNone() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionTypeName("nOnE") // Case-insensitive
.build();
assertNull(metadata.getStandardCompressionNode());
}
@Test
public void compressionValuesName() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionTypeName("foo")
.build();
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
assertNotNull(compressionNode);
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
assertEquals("foo", compressionName.getAttribute("value"));
// Defaults to lossless true
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
assertEquals("TRUE", compressionLossless.getAttribute("value"));
}
@Test(expected = IllegalArgumentException.class)
public void withCompressionLossyIllegal() {
builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionLossless(false);
}
@Test
public void compressionValuesLossy() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withCompressionTypeName("bar")
.withCompressionLossless(false)
.build();
IIOMetadataNode compressionNode = metadata.getStandardCompressionNode();
assertNotNull(compressionNode);
IIOMetadataNode compressionName = (IIOMetadataNode) compressionNode.getElementsByTagName("CompressionTypeName").item(0);
assertEquals("bar", compressionName.getAttribute("value"));
IIOMetadataNode compressionLossless = (IIOMetadataNode) compressionNode.getElementsByTagName("Lossless").item(0);
assertEquals("FALSE", compressionLossless.getAttribute("value"));
}
@Test
public void withDocumentValuesDefault() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.build();
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
assertNull(documentNode);
}
@Test
public void withDocumentValues() {
Calendar creationTime = Calendar.getInstance();
creationTime.set(2022, Calendar.SEPTEMBER, 8, 14, 5, 0);
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withFormatVersion("42")
.withDocumentCreationTime(creationTime)
.build();
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
assertNotNull(documentNode);
IIOMetadataNode formatVersion = (IIOMetadataNode) documentNode.getElementsByTagName("FormatVersion").item(0);
assertEquals("42", formatVersion.getAttribute("value"));
IIOMetadataNode imageCreationTime = (IIOMetadataNode) documentNode.getElementsByTagName("ImageCreationTime").item(0);
assertEquals("2022", imageCreationTime.getAttribute("year"));
assertEquals("9", imageCreationTime.getAttribute("month"));
assertEquals("8", imageCreationTime.getAttribute("day"));
assertEquals("14", imageCreationTime.getAttribute("hour"));
assertEquals("5", imageCreationTime.getAttribute("minute"));
assertEquals("0", imageCreationTime.getAttribute("second"));
}
@Test
public void withTextValuesDefault() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNull(textNode);
}
@Test
public void withTextValuesSingle() {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withTextEntry("foo", "bar")
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNotNull(textNode);
IIOMetadataNode textEntry = (IIOMetadataNode) textNode.getElementsByTagName("TextEntry").item(0);
assertEquals("foo", textEntry.getAttribute("keyword"));
assertEquals("bar", textEntry.getAttribute("value"));
}
@Test
public void withTextValuesMap() {
Map<String, String> entries = new HashMap<>();
entries.put("foo", "bar");
entries.put("bar", "xyzzy");
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withTextEntries(entries)
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNotNull(textNode);
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
assertEquals(entries.size(), textEntries.getLength());
int i = 0;
for (Entry<String, String> entry : entries.entrySet()) {
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
assertEquals(entry.getKey(), textEntry.getAttribute("keyword"));
assertEquals(entry.getValue(), textEntry.getAttribute("value"));
i++;
}
}
@Test
public void withTextValuesList() {
List<TextEntry> entries = Arrays.asList(
new TextEntry(null, "foo"), // No key allowed
new TextEntry("foo", "bar"),
new TextEntry("bar", "xyzzy"),
new TextEntry("bar", "nothing happens..."), // Duplicates allowed
new TextEntry("everything", "válüè", "unknown", "UTF-8", "zip")
);
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withTextEntries(entries)
.build();
IIOMetadataNode textNode = metadata.getStandardTextNode();
assertNotNull(textNode);
NodeList textEntries = textNode.getElementsByTagName("TextEntry");
assertEquals(entries.size(), textEntries.getLength());
for (int i = 0; i < entries.size(); i++) {
TextEntry entry = entries.get(i);
IIOMetadataNode textEntry = (IIOMetadataNode) textEntries.item(i);
assertAttributeEqualOrAbsent(entry.keyword, textEntry, "keyword");
assertEquals(entry.value, textEntry.getAttribute("value"));
assertAttributeEqualOrAbsent(entry.language, textEntry, "language");
assertAttributeEqualOrAbsent(entry.encoding, textEntry, "encoding");
assertAttributeEqualOrAbsent(entry.compression, textEntry, "compression");
}
}
private static void assertAttributeEqualOrAbsent(final String expectedValue, IIOMetadataNode node, final String attribute) {
if (expectedValue != null) {
assertEquals(expectedValue, node.getAttribute(attribute));
}
else {
assertFalse(node.hasAttribute(attribute));
}
}
@Test
public void withPlanarColorspaceType() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList(
"XYZ", "Lab", "Luv", "YCbCr", "Yxy", "YCCK", "PhotoYCC",
"RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY",
"2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR",
"9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR"
);
for (ColorSpaceType value : ColorSpaceType.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withColorSpaceType(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardChromaNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ColorSpaceType").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("name")); // Format oddity: Why is this not "value"?
assertTrue(allowedValues.contains(value.toString()));
}
}
@Test
public void withPlanarConfiguration() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList("PixelInterleaved", "PlaneInterleaved", "LineInterleaved", "TileInterleaved");
for (PlanarConfiguration value : PlanarConfiguration.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR))
.withPlanarConfiguration(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardDataNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("PlanarConfiguration").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
assertTrue(allowedValues.contains(value.toString()));
}
}
@Test
public void withImageOrientation() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList("Normal", "Rotate90", "Rotate180", "Rotate270", "FlipH", "FlipV", "FlipHRotate90", "FlipVRotate90");
for (ImageOrientation value : ImageOrientation.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY))
.withOrientation(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardDimensionNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("ImageOrientation").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
assertTrue(allowedValues.contains(value.toString()));
}
}
@Test
public void withSubimageInterpretation() {
// See: https://docs.oracle.com/javase/7/docs/api/javax/imageio/metadata/doc-files/standard_metadata.html
Collection<String> allowedValues = Arrays.asList(
"Standalone", "SinglePage", "FullResolution", "ReducedResolution", "PyramidLayer",
"Preview", "VolumeSlice", "ObjectView", "Panorama", "AnimationFrame",
"TransparencyMask", "CompositingLayer", "SpectralSlice", "Unknown"
);
for (SubimageInterpretation value : SubimageInterpretation.values()) {
StandardImageMetadataSupport metadata = (StandardImageMetadataSupport) builder(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB))
.withSubimageInterpretation(value)
.build();
assertNotNull(metadata);
IIOMetadataNode documentNode = metadata.getStandardDocumentNode();
assertNotNull(documentNode);
IIOMetadataNode subImageInterpretation = (IIOMetadataNode) documentNode.getElementsByTagName("SubimageInterpretation").item(0);
assertEquals(value.toString(), subImageInterpretation.getAttribute("value"));
assertTrue(allowedValues.contains(value.toString()));
}
}
}
@@ -37,9 +37,7 @@ import java.awt.image.*;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
public class DiscreteAlphaIndexColorModelTest {
@@ -206,25 +204,6 @@ public class DiscreteAlphaIndexColorModelTest {
assertThat(raster.getTransferType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE));
}
@Test
public void testNumComponents() {
int[] colors = createIntLut(1 << 8);
IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
ColorModel colorModelDiscreteAlpha = new DiscreteAlphaIndexColorModel(icm, 1, true);
ColorModel colorModelDiscreteAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 2, true);
ColorModel colorModelNoAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 42, false);
assertEquals(3, colorModelDiscreteAlpha.getNumColorComponents());
assertEquals(4, colorModelDiscreteAlpha.getNumComponents());
assertEquals(3, colorModelDiscreteAlphaExtra.getNumColorComponents());
assertEquals(5, colorModelDiscreteAlphaExtra.getNumComponents()); // Questionable
assertEquals(3, colorModelNoAlphaExtra.getNumColorComponents());
assertEquals(45, colorModelNoAlphaExtra.getNumComponents()); // Questionable
}
private static int[] createIntLut(final int count) {
int[] lut = new int[count];
@@ -1,434 +0,0 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
/**
* BufferedFileImageInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
// TODO: Remove this test, and instead test the disk cache directly!
public class BufferedChannelImageInputStreamFileCacheTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) {
random.nextBytes(data);
return new ByteArrayInputStream(data);
}
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(new ByteArrayInputStream(new byte[0]), null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
}
}
@Test
public void testCreateNullStream() throws IOException {
try {
new FileCache((InputStream) null, null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testCreateNullChannel() throws IOException {
try {
new FileCache((ReadableByteChannel) null, null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9];
for (int i = 0; i < data.length / result.length; i++) {
// Read backwards
long newPos = data.length - result.length - i * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@Test
public void testReadOutsideDataSeek0Read() throws IOException {
byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2];
stream.read(buffer);
stream.seek(0);
assertNotEquals(-1, stream.read());
assertNotEquals(-1, stream.read(buffer));
}
}
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2 % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testClose() throws IOException {
// Create wrapper stream
Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close();
verify(cache, only()).close();
}
@Test
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
// See #606 for details.
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
// Ie: Relies on read to return all bytes at once, without blocking
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
assertEquals(size, len + head);
assertArrayEquals(bytes, result);
}
}
}
@@ -1,434 +0,0 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.ReadableByteChannel;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
/**
* BufferedFileImageInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
// TODO: Remove this test, and instead test the memory cache directly!
public class BufferedChannelImageInputStreamMemoryCacheTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) {
random.nextBytes(data);
return new ByteArrayInputStream(data);
}
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(new ByteArrayInputStream(new byte[0])))) {
assertEquals("Stream length should be unknown", -1, stream.length());
}
}
@Test
public void testCreateNullStream() {
try {
new MemoryCache((InputStream) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testCreateNullChannel() {
try {
new MemoryCache((ReadableByteChannel) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
assertEquals("Wrong data read", -1, stream.read());
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9];
for (int i = 0; i < data.length / result.length; i++) {
// Read backwards
long newPos = data.length - result.length - i * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@Test
public void testReadOutsideDataSeek0Read() throws IOException {
byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2];
stream.read(buffer);
stream.seek(0);
assertNotEquals(-1, stream.read());
assertNotEquals(-1, stream.read(buffer));
}
}
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomDataToInputStream(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2 % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testClose() throws IOException {
// Create wrapper stream
Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close();
verify(cache, only()).close();
}
@Test
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
// See #606 for details.
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
// Ie: Relies on read to return all bytes at once, without blocking
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
assertEquals(size, len + head);
assertArrayEquals(bytes, result);
}
}
}
@@ -1,431 +0,0 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
* BufferedFileImageInputStreamTestCase
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
public class BufferedChannelImageInputStreamTest {
private final Random random = new Random(170984354357234566L);
private File randomDataToFile(byte[] data) throws IOException {
random.nextBytes(data);
File file = File.createTempFile("read", ".tmp");
Files.write(file.toPath(), data);
return file;
}
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(File.createTempFile("empty", ".tmp")))) {
assertEquals("Data length should be same as stream length", 0, stream.length());
}
}
@Test
public void testCreateNullFileInputStream() {
try {
new BufferedChannelImageInputStream((FileInputStream) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("inputstream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testCreateNullByteChannel() {
try {
new BufferedChannelImageInputStream((SeekableByteChannel) null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("channel"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 1024];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[1024 * 18];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] result = new byte[9];
for (int i = 0; i < data.length / result.length; i++) {
// Read backwards
long newPos = stream.length() - result.length - i * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@Test
public void testReadOutsideDataSeek0Read() throws IOException {
byte[] data = new byte[256];
File file = randomDataToFile(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
assertEquals("File length should be same as stream length", file.length(), stream.length());
byte[] buffer = new byte[data.length * 2];
stream.read(buffer);
stream.seek(0);
assertNotEquals(-1, stream.read());
assertNotEquals(-1, stream.read(buffer));
}
}
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
File file = randomDataToFile(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
File file = randomDataToFile(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
File file = randomDataToFile(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2 % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
File file = randomDataToFile(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
File file = randomDataToFile(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
File file = randomDataToFile(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
File file = randomDataToFile(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testCloseChannel() throws IOException {
SeekableByteChannel channel = mock(SeekableByteChannel.class);
ImageInputStream stream = new BufferedChannelImageInputStream(channel);
stream.close();
verify(channel, never()).close();
}
@Test
public void testWorkaroundForWBMPImageReaderExpectsReadToBehaveAsReadFully() throws IOException {
// See #606 for details.
// Bug in JDK WBMPImageReader, uses read(byte[], int, int) instead of readFully(byte[], int, int).
// Ie: Relies on read to return all bytes at once, without blocking
int size = BufferedChannelImageInputStream.DEFAULT_BUFFER_SIZE * 7;
byte[] bytes = new byte[size];
File file = randomDataToFile(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileInputStream(file))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
assertEquals(size, len + head);
assertArrayEquals(bytes, result);
}
}
}
@@ -45,9 +45,7 @@ import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.*;
/**
* BufferedFileImageInputStreamTestCase
@@ -56,7 +54,6 @@ import static org.mockito.Mockito.verify;
* @author last modified by $Author: haraldk$
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
@Deprecated
public class BufferedFileImageInputStreamTest {
private final Random random = new Random(170984354357234566L);
@@ -75,7 +72,6 @@ public class BufferedFileImageInputStreamTest {
}
}
@SuppressWarnings("resource")
@Test
public void testCreateNullFile() throws IOException {
try {
@@ -1,26 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.spi.ImageInputStreamSpi;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
/**
* BufferedInputStreamImageInputStreamSpiTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
*/
public class BufferedFileInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
@Override
protected ImageInputStreamSpi createProvider() {
return new BufferedInputStreamImageInputStreamSpi();
}
@Override
protected InputStream createInput() throws IOException {
return Files.newInputStream(File.createTempFile("test-", ".tst").toPath());
}
}
@@ -53,7 +53,6 @@ import static org.mockito.Mockito.*;
* @author last modified by $Author: haraldk$
* @version $Id: BufferedImageInputStreamTest.java,v 1.0 Jun 30, 2008 3:07:42 PM haraldk Exp$
*/
@SuppressWarnings("deprecation")
public class BufferedImageInputStreamTest {
private final Random random = new Random(3450972865211L);
@@ -434,7 +433,7 @@ public class BufferedImageInputStreamTest {
* and {@code pFirstOffset == pSecondOffset}.
* Otherwise {@code false}.
*/
public static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
if (pFirst == pSecond && pFirstOffset == pSecondOffset) {
return true;
}
@@ -1,24 +0,0 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.spi.ImageInputStreamSpi;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
/**
* BufferedInputStreamImageInputStreamSpiTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: BufferedInputStreamImageInputStreamSpiTest.java,v 1.0 08/09/2022 haraldk Exp$
*/
public class BufferedInputStreamImageInputStreamSpiTest extends ImageInputStreamSpiTest<InputStream> {
@Override
protected ImageInputStreamSpi createProvider() {
return new BufferedInputStreamImageInputStreamSpi();
}
@Override
protected InputStream createInput() {
return new ByteArrayInputStream(new byte[0]);
}
}
@@ -1,380 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.stream;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* NonSeekableImageInputStreamTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: NonSeekableImageInputStreamTest.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
public class DirectImageInputStreamTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomData(byte[] data) {
random.nextBytes(data);
return new ByteArrayInputStream(data);
}
@Test
public void testCreate() throws IOException {
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(new byte[0]), 0)) {
assertEquals("Data length should be same as stream length", 0, stream.length());
}
}
@Test
public void testCreateNullFile() throws IOException {
try (@SuppressWarnings("unused") DirectImageInputStream stream = new DirectImageInputStream(null)) {
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
assertNotNull("Null exception message", expected.getMessage());
String message = expected.getMessage().toLowerCase();
assertTrue("Exception message does not contain parameter name", message.contains("stream"));
assertTrue("Exception message does not contain null", message.contains("null"));
}
}
@Test
public void testRead() throws IOException {
byte[] data = new byte[1024 * 1024];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (byte value : data) {
assertEquals("Wrong data read", value & 0xff, stream.read());
}
}
}
@Test
public void testReadArray() throws IOException {
byte[] data = new byte[1024 * 10];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
byte[] result = new byte[1024];
for (int i = 0; i < data.length / result.length; i++) {
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSkip() throws IOException {
byte[] data = new byte[1024 * 14];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
byte[] result = new byte[7];
for (int i = 0; i < data.length / result.length; i += 2) {
stream.readFully(result);
stream.skipBytes(result.length);
assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length));
}
}
}
@Test
public void testReadSeek() throws IOException {
byte[] data = new byte[24 * 18];
InputStream input = randomData(data);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
byte[] result = new byte[9];
for (int i = 0; i < data.length / (2 * result.length); i++) {
long newPos = i * 2 * result.length;
stream.seek(newPos);
assertEquals("Wrong stream position", newPos, stream.getStreamPosition());
stream.readFully(result);
assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length));
}
}
}
@SuppressWarnings("ConstantConditions")
@Ignore("Bit reading requires backwards seek or buffer...")
@Test
public void testReadBitRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomData(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
}
}
@SuppressWarnings("ConstantConditions")
@Ignore("Bit reading requires backwards seek or buffer...")
@Test
public void testReadBitsRandom() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomData(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
assertEquals(i % 8, stream.getBitOffset());
}
}
}
@SuppressWarnings("ConstantConditions")
@Ignore("Bit reading requires backwards seek or buffer...")
@Test
public void testReadBitsRandomOffset() throws IOException {
byte[] bytes = new byte[8];
InputStream input = randomData(bytes);
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i));
assertEquals(i * 2L % 8, stream.getBitOffset());
}
}
}
@Test
public void testReadShort() throws IOException {
byte[] bytes = new byte[31];
InputStream input = randomData(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
assertEquals(buffer.getShort(), stream.readShort());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
}
}
@Test
public void testReadInt() throws IOException {
byte[] bytes = new byte[31];
InputStream input = randomData(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
// Create stream
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
assertEquals(buffer.getInt(), stream.readInt());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
}
}
@Test
public void testReadLong() throws IOException {
byte[] bytes = new byte[8743]; // Slightly more than one buffer size
InputStream input = randomData(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
buffer.position(0);
buffer.order(ByteOrder.LITTLE_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
assertEquals(buffer.getLong(), stream.readLong());
}
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
}
@Test
public void testSeekPastEOF() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomData(bytes);
try (DirectImageInputStream stream = new DirectImageInputStream(input)) {
stream.seek(1000);
assertEquals(-1, stream.read());
assertEquals(-1, stream.read(new byte[1], 0, 1));
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readFully(new byte[1]);
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readByte();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readShort();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readInt();
}
});
assertThrows(EOFException.class, new ThrowingRunnable() {
@Override
public void run() throws Throwable {
stream.readLong();
}
});
}
try (DirectImageInputStream stream = new DirectImageInputStream(new ByteArrayInputStream(bytes))) {
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testClose() throws IOException {
// Create wrapper stream
InputStream input = mock(InputStream.class);
ImageInputStream stream = new DirectImageInputStream(input);
stream.close();
verify(input, only()).close();
}
}
@@ -30,15 +30,20 @@
package com.twelvemonkeys.imageio.util;
import com.twelvemonkeys.lang.Validate;
import static org.junit.Assert.assertEquals;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DirectColorModel;
import java.awt.image.IndexColorModel;
import javax.imageio.ImageTypeSpecifier;
import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import java.awt.color.*;
import java.awt.image.*;
import static org.junit.Assert.assertEquals;
import com.twelvemonkeys.lang.Validate;
public class ImageTypeSpecifiersTest {
@@ -65,19 +70,12 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier expected;
switch (type) {
// Special handling for INT_RGB and BGR, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits)
case BufferedImage.TYPE_INT_RGB:
expected = createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false);
break;
case BufferedImage.TYPE_INT_BGR:
expected = createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false);
break;
// Special handling for USHORT_565 and 555, due to bug in ImageTypeSpecifier for these types (DirectColorModel is 32 bits)
case BufferedImage.TYPE_USHORT_565_RGB:
expected = createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
expected = createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
break;
case BufferedImage.TYPE_USHORT_555_RGB:
expected = createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
expected = createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false);
break;
default:
expected = ImageTypeSpecifier.createFromBufferedImageType(type);
@@ -88,24 +86,12 @@ public class ImageTypeSpecifiersTest {
}
@Test
public void testCreatePacked24() {
public void testCreatePacked32() {
// TYPE_INT_RGB
assertEquals(
createPacked(sRGB, 24, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false)
);
// TYPE_INT_BGR
assertEquals(
createPacked(sRGB, 24, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
);
// Extra: Make sure color models bits is actually 24 (ImageTypeSpecifier equivalent returns 32)
assertEquals(24, ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, 0, DataBuffer.TYPE_INT, false).getColorModel().getPixelSize());
}
@Test
public void testCreatePacked32() {
// TYPE_INT_ARGB
assertEquals(
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, false),
@@ -116,36 +102,35 @@ public class ImageTypeSpecifiersTest {
ImageTypeSpecifier.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true),
ImageTypeSpecifiers.createPacked(sRGB, DCM_RED_MASK, DCM_GREEN_MASK, DCM_BLUE_MASK, DCM_ALPHA_MASK, DataBuffer.TYPE_INT, true)
);
}
@Test
public void testCreatePacked15() {
// TYPE_USHORT_555_RGB
// TYPE_INT_BGR
assertEquals(
createPacked(sRGB, 15, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
ImageTypeSpecifier.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_BGR_RED_MASK, DCM_BGR_GRN_MASK, DCM_BGR_BLU_MASK, 0, DataBuffer.TYPE_INT, false)
);
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
// Extra: Make sure color models bits is actually 15 (ImageTypeSpecifier equivalent returns 32)
assertEquals(15, ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false).getColorModel().getPixelSize());
}
@Test
public void testCreatePacked16() {
// TYPE_USHORT_555_RGB
assertEquals(
createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_555_RED_MASK, DCM_555_GRN_MASK, DCM_555_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
// "SHORT 555 RGB" (impossible, only BYTE, USHORT, INT supported)
// TYPE_USHORT_565_RGB
assertEquals(
createPacked(sRGB, 16, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, DCM_565_RED_MASK, DCM_565_GRN_MASK, DCM_565_BLU_MASK, 0, DataBuffer.TYPE_USHORT, false)
);
// "USHORT 4444 ARGB"
assertEquals(
createPacked(sRGB, 16,0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, false)
);
// "USHORT 4444 ARGB PRE"
assertEquals(
createPacked(sRGB, 16, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xf00, 0xf0, 0xf, 0xf000, DataBuffer.TYPE_USHORT, true)
);
@@ -157,17 +142,17 @@ public class ImageTypeSpecifiersTest {
public void testCreatePacked8() {
// "BYTE 332 RGB"
assertEquals(
createPacked(sRGB, 8, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
createPacked(sRGB, 0xe0, 0x1c, 0x03, 0x0, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xe0, 0x1c, 0x3, 0x0, DataBuffer.TYPE_BYTE, false)
);
// "BYTE 2222 ARGB"
assertEquals(
createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false),
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false)
);
// "BYTE 2222 ARGB PRE"
assertEquals(
createPacked(sRGB, 8, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true),
ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, true)
);
@@ -175,12 +160,15 @@ public class ImageTypeSpecifiersTest {
assertEquals(8, ImageTypeSpecifiers.createPacked(sRGB, 0xc0, 0x30, 0x0c, 0x03, DataBuffer.TYPE_BYTE, false).getColorModel().getPixelSize());
}
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace, final int bits,
private ImageTypeSpecifier createPacked(final ColorSpace colorSpace,
final int redMask, final int greenMask, final int blueMask, final int alphaMask,
final int transferType, final boolean isAlphaPremultiplied) {
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT || transferType == DataBuffer.TYPE_INT, transferType, "transferType: %s");
Validate.isTrue(transferType == DataBuffer.TYPE_BYTE || transferType == DataBuffer.TYPE_USHORT, transferType, "transferType: %s");
ColorModel colorModel = new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
int bits = transferType == DataBuffer.TYPE_BYTE ? 8 : 16;
ColorModel colorModel =
new DirectColorModel(colorSpace, bits, redMask, greenMask, blueMask, alphaMask, isAlphaPremultiplied, transferType);
return new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(1, 1));
}
@@ -728,28 +716,6 @@ public class ImageTypeSpecifiersTest {
);
}
@Test
public void testCreateFromBufferedImageTypeShouldEqualConstructor() {
for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) {
BufferedImage image = new BufferedImage(1, 1, type);
ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image);
ImageTypeSpecifier fromType = ImageTypeSpecifiers.createFromBufferedImageType(type);
assertEquals(fromConstructor.getColorModel(), fromType.getColorModel());
}
}
@Test
public void testCreateFromRenderedImageShouldEqualConstructor() {
for (int type = BufferedImage.TYPE_INT_RGB; type < BufferedImage.TYPE_BYTE_INDEXED; type++) {
BufferedImage image = new BufferedImage(1, 1, type);
ImageTypeSpecifier fromConstructor = new ImageTypeSpecifier(image);
ImageTypeSpecifier fromImage = ImageTypeSpecifiers.createFromRenderedImage(image);
assertEquals(fromConstructor.getColorModel(), fromImage.getColorModel());
}
}
private static byte[] createByteLut(final int count) {
byte[] lut = new byte[count];
for (int i = 0; i < count; i++) {
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
@@ -40,8 +40,11 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
@@ -241,7 +244,10 @@ public final class HDRImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return new HDRMetadata(getRawImageType(imageIndex), header);
checkBounds(imageIndex);
readHeader();
return new HDRMetadata(header);
}
public static void main(final String[] args) throws IOException {
@@ -1,19 +1,83 @@
/*
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.hdr;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataNode;
public class HDRMetadata extends StandardImageMetadataSupport {
public HDRMetadata(ImageTypeSpecifier type, HDRHeader header) {
super(builder(type)
.withCompressionTypeName("RLE")
.withTextEntry("Software", header.getSoftware()));
final class HDRMetadata extends AbstractMetadata {
private final HDRHeader header;
HDRMetadata(final HDRHeader header) {
this.header = header;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
csType.setAttribute("name", "RGB");
// TODO: Support XYZ
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", "3");
chroma.appendChild(numChannels);
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
blackIsZero.setAttribute("value", "TRUE");
chroma.appendChild(blackIsZero);
return chroma;
}
// No compression
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "RLE");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
return node;
}
// For HDR, the stored sample data is UnsignedIntegral and data is 4 channels (RGB+Exp),
// but decoded to Real (float) 3 chanel RGB
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
@@ -28,4 +92,38 @@ public class HDRMetadata extends StandardImageMetadataSupport {
return node;
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
// TODO: Support other orientations
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "Normal");
dimension.appendChild(imageOrientation);
return dimension;
}
// No document node
@Override
protected IIOMetadataNode getStandardTextNode() {
if (header.getSoftware() != null) {
IIOMetadataNode text = new IIOMetadataNode("Text");
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
textEntry.setAttribute("keyword", "Software");
textEntry.setAttribute("value", header.getSoftware());
text.appendChild(textEntry);
return text;
}
return null;
}
// No tiling
// No transparency
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
@@ -1,41 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.icns;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import javax.imageio.ImageTypeSpecifier;
final class ICNSImageMetadata extends StandardImageMetadataSupport {
ICNSImageMetadata(ImageTypeSpecifier type, String compressionName) {
super(builder(type).withCompressionTypeName(compressionName));
}
}
@@ -35,16 +35,11 @@ import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.*;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInputStream;
import java.io.File;
@@ -66,9 +61,10 @@ import java.util.List;
* @see <a href="http://en.wikipedia.org/wiki/Apple_Icon_Image_format">Apple Icon Image format (Wikipedia)</a>
*/
public final class ICNSImageReader extends ImageReaderBase {
// TODO: Support ToC resource for faster parsing/faster determine number of icons?
// TODO: Subsampled reading for completeness, even if never used?
private final List<IconResource> icons = new ArrayList<>();
private final List<IconResource> masks = new ArrayList<>();
private List<IconResource> icons = new ArrayList<IconResource>();
private List<IconResource> masks = new ArrayList<IconResource>();
private IconResource lastResourceRead;
private int length;
@@ -140,7 +136,7 @@ public final class ICNSImageReader extends ImageReaderBase {
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
IconResource resource = readIconResource(imageIndex);
List<ImageTypeSpecifier> specifiers = new ArrayList<>();
List<ImageTypeSpecifier> specifiers = new ArrayList<ImageTypeSpecifier>();
switch (resource.depth()) {
case 1:
@@ -234,9 +230,14 @@ public final class ICNSImageReader extends ImageReaderBase {
packedSize -= 4;
}
try (InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize)) {
InputStream input = IIOUtil.createStreamAdapter(imageInput, packedSize);
try {
ICNSUtil.decompress(new DataInputStream(input), data, 0, (data.length * 24) / 32); // 24 bit data
}
finally {
input.close();
}
}
else {
data = new byte[resource.length - ICNS.RESOURCE_HEADER_SIZE];
@@ -490,7 +491,7 @@ public final class ICNSImageReader extends ImageReaderBase {
String format;
if (Arrays.equals(ICNS.PNG_MAGIC, Arrays.copyOfRange(magic, 0, ICNS.PNG_MAGIC.length))) {
if (Arrays.equals(ICNS.PNG_MAGIC, magic)) {
format = "PNG";
}
else if (Arrays.equals(ICNS.JPEG_2000_MAGIC, magic)) {
@@ -526,6 +527,7 @@ public final class ICNSImageReader extends ImageReaderBase {
IconResource resource = IconResource.read(imageInput);
if (resource.isTOC()) {
// TODO: IconResource.readTOC()?
int resourceCount = (resource.length - ICNS.RESOURCE_HEADER_SIZE) / ICNS.RESOURCE_HEADER_SIZE;
long pos = resource.start + resource.length;
@@ -568,23 +570,6 @@ public final class ICNSImageReader extends ImageReaderBase {
}
}
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
IconResource resource = readIconResource(imageIndex);
String compressionName;
if (resource.isForeignFormat()) {
// Special handling of PNG/JPEG 2000 icons
imageInput.seek(resource.start + ICNS.RESOURCE_HEADER_SIZE);
compressionName = getForeignFormat(imageInput);
}
else {
compressionName = resource.isCompressed() ? "RLE" : "None";
}
return new ICNSImageMetadata(getRawImageType(imageIndex), compressionName);
}
private static final class ICNSBitMaskColorModel extends IndexColorModel {
static final IndexColorModel INSTANCE = new ICNSBitMaskColorModel();
@@ -593,6 +578,7 @@ public final class ICNSImageReader extends ImageReaderBase {
}
}
@SuppressWarnings({"UnusedAssignment"})
public static void main(String[] args) throws IOException {
int argIndex = 0;
@@ -34,13 +34,7 @@ import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.stream.SubImageOutputStream;
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import javax.imageio.IIOException;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.*;
import javax.imageio.event.IIOWriteWarningListener;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
@@ -110,7 +104,6 @@ public final class ICNSImageWriter extends ImageWriterBase {
sequenceIndex = 0;
}
@SuppressWarnings("RedundantThrows")
@Override
public void endWriteSequence() throws IOException {
assertOutput();
@@ -38,13 +38,8 @@ import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.awt.image.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Iterator;
/**
@@ -145,12 +140,17 @@ final class SipsJP2Reader {
}
private static String checkErrorMessage(final Process process) throws IOException {
try (InputStream stream = process.getErrorStream()) {
InputStream stream = process.getErrorStream();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String message = reader.readLine();
return message != null && message.startsWith("Error: ") ? message.substring(7) : null;
}
finally {
stream.close();
}
}
private static String[] buildCommand(final File sipsCommand, final File tempFile) {
@@ -159,13 +159,19 @@ final class SipsJP2Reader {
};
}
private static File dumpToFile(final ImageInputStream stream) throws IOException {
File tempFile = File.createTempFile("imageio-icns-", ".png");
tempFile.deleteOnExit();
try (FileOutputStream out = new FileOutputStream(tempFile)) {
FileOutputStream out = new FileOutputStream(tempFile);
try {
FileUtil.copy(IIOUtil.createStreamAdapter(stream), out);
}
finally {
out.close();
}
return tempFile;
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
@@ -1,13 +1,13 @@
package com.twelvemonkeys.imageio.plugins.iff;
import javax.imageio.IIOException;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.imageio.plugins.iff.IFF.*;
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
import static com.twelvemonkeys.lang.Validate.isTrue;
/**
* Form.
@@ -27,7 +27,7 @@ abstract class Form {
abstract int width();
abstract int height();
abstract double aspect();
abstract float aspect();
abstract int bitplanes();
abstract int compressionType();
@@ -118,7 +118,7 @@ abstract class Form {
}
private ILBMForm(final int formType, final BMHDChunk bitmapHeader, final CAMGChunk viewMode, final CMAPChunk colorMap, final AbstractMultiPaletteChunk multiPalette, final XS24Chunk thumbnail, final BODYChunk body) {
super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s"));
super(formType);
this.bitmapHeader = bitmapHeader;
this.viewMode = viewMode;
this.colorMap = colorMap;
@@ -127,19 +127,6 @@ abstract class Form {
this.body = body;
}
private static boolean validFormType(int formType) {
switch (formType) {
case TYPE_ACBM:
case TYPE_ILBM:
case TYPE_PBM:
case TYPE_RGB8:
case TYPE_RGBN:
return true;
default:
return false;
}
}
@Override
int width() {
return bitmapHeader.width;
@@ -161,8 +148,8 @@ abstract class Form {
}
@Override
double aspect() {
return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (double) bitmapHeader.yAspect);
float aspect() {
return bitmapHeader.yAspect == 0 ? 0 : (bitmapHeader.xAspect / (float) bitmapHeader.yAspect);
}
@Override
@@ -310,7 +297,7 @@ abstract class Form {
}
private DEEPForm(final int formType, final DGBLChunk deepGlobal, final DLOCChunk deepLocation, final DPELChunk deepPixel, final XS24Chunk thumbnail, final BODYChunk body) {
super(isTrue(validFormType(formType), formType, "Unknown IFF Form type: %s"));
super(formType);
this.deepGlobal = deepGlobal;
this.deepLocation = deepLocation;
this.deepPixel = deepPixel;
@@ -318,15 +305,6 @@ abstract class Form {
this.body = body;
}
private static boolean validFormType(int formType) {
switch (formType) {
case TYPE_DEEP:
case TYPE_TVPP:
return true;
default:
return false;
}
}
@Override
int width() {
@@ -359,8 +337,8 @@ abstract class Form {
}
@Override
double aspect() {
return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (double) deepGlobal.yAspect;
float aspect() {
return deepGlobal.yAspect == 0 ? 0 : deepGlobal.xAspect / (float) deepGlobal.yAspect;
}
@Override
@@ -1,83 +1,188 @@
/*
* Copyright (c) 2020, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import java.awt.image.*;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.*;
import java.awt.image.IndexColorModel;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import static com.twelvemonkeys.imageio.plugins.iff.IFF.*;
import static com.twelvemonkeys.imageio.plugins.iff.IFFUtil.toChunkStr;
import static com.twelvemonkeys.lang.Validate.isTrue;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.util.Collections.emptyList;
final class IFFImageMetadata extends StandardImageMetadataSupport {
IFFImageMetadata(ImageTypeSpecifier type, Form header, IndexColorModel palette) {
this(builder(type), notNull(header, "header"), palette);
final class IFFImageMetadata extends AbstractMetadata {
private final Form header;
private final IndexColorModel colorMap;
private final List<GenericChunk> meta;
IFFImageMetadata(Form header, IndexColorModel colorMap) {
this.header = notNull(header, "header");
isTrue(validFormType(header.formType), header.formType, "Unknown IFF Form type: %s");
this.colorMap = colorMap;
this.meta = header.meta;
}
private IFFImageMetadata(Builder builder, Form header, IndexColorModel palette) {
super(builder.withPalette(palette)
.withCompressionTypeName(compressionName(header))
.withBitsPerSample(bitsPerSample(header))
.withPlanarConfiguration(planarConfiguration(header))
.withPixelAspectRatio(header.aspect() != 0 ? header.aspect() : null)
.withFormatVersion("1.0")
.withTextEntries(textEntries(header)));
}
private static String compressionName(Form header) {
switch (header.compressionType()) {
case BMHDChunk.COMPRESSION_NONE:
return "None";
case BMHDChunk.COMPRESSION_BYTE_RUN:
return "RLE";
case 4:
// Compression type 4 means different things for different FORM types, we support
// Impulse RGB8 RLE compression: 24 bit RGB + 1 bit mask + 7 bit run count
if (header.formType == TYPE_RGB8) {
return "RGB8";
}
private boolean validFormType(int formType) {
switch (formType) {
default:
return "Unknown";
return false;
case TYPE_ACBM:
case TYPE_DEEP:
case TYPE_ILBM:
case TYPE_PBM:
case TYPE_RGB8:
case TYPE_RGBN:
case TYPE_TVPP:
return true;
}
}
private static int[] bitsPerSample(Form header) {
int bitplanes = header.bitplanes();
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
switch (header.bitplanes()) {
case 8:
if (colorMap == null) {
csType.setAttribute("name", "GRAY");
break;
}
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 24:
case 25:
case 32:
csType.setAttribute("name", "RGB");
break;
default:
csType.setAttribute("name", "Unknown");
}
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
chroma.appendChild(numChannels);
if (colorMap == null && header.bitplanes() == 8) {
numChannels.setAttribute("value", Integer.toString(1));
}
else if (header.bitplanes() == 25 || header.bitplanes() == 32) {
numChannels.setAttribute("value", Integer.toString(4));
}
else {
numChannels.setAttribute("value", Integer.toString(3));
}
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
chroma.appendChild(blackIsZero);
blackIsZero.setAttribute("value", "TRUE");
// NOTE: TGA files may contain a color map, even if true color...
// Not sure if this is a good idea to expose to the meta data,
// as it might be unexpected... Then again...
if (colorMap != null) {
IIOMetadataNode palette = new IIOMetadataNode("Palette");
chroma.appendChild(palette);
for (int i = 0; i < colorMap.getMapSize(); i++) {
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
palette.appendChild(paletteEntry);
paletteEntry.setAttribute("index", Integer.toString(i));
paletteEntry.setAttribute("red", Integer.toString(colorMap.getRed(i)));
paletteEntry.setAttribute("green", Integer.toString(colorMap.getGreen(i)));
paletteEntry.setAttribute("blue", Integer.toString(colorMap.getBlue(i)));
}
if (colorMap.getTransparentPixel() != -1) {
IIOMetadataNode backgroundIndex = new IIOMetadataNode("BackgroundIndex");
chroma.appendChild(backgroundIndex);
backgroundIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
}
}
// TODO: TVPP TVPaint Project files have a MIXR chunk with a background color
// and also a BGP1 (background pen 1?) and BGP2 chunks
// if (extensions != null && extensions.getBackgroundColor() != 0) {
// Color background = new Color(extensions.getBackgroundColor(), true);
//
// IIOMetadataNode backgroundColor = new IIOMetadataNode("BackgroundColor");
// chroma.appendChild(backgroundColor);
//
// backgroundColor.setAttribute("red", Integer.toString(background.getRed()));
// backgroundColor.setAttribute("green", Integer.toString(background.getGreen()));
// backgroundColor.setAttribute("blue", Integer.toString(background.getBlue()));
// }
return chroma;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.compressionType() == BMHDChunk.COMPRESSION_NONE) {
return null; // All defaults
}
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "RLE");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
return node;
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode data = new IIOMetadataNode("Data");
// PlanarConfiguration
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
switch (header.formType) {
case TYPE_DEEP:
case TYPE_TVPP:
case TYPE_RGB8:
case TYPE_PBM:
planarConfiguration.setAttribute("value", "PixelInterleaved");
break;
case TYPE_ILBM:
planarConfiguration.setAttribute("value", "PlaneInterleaved");
break;
default:
planarConfiguration.setAttribute("value", "Unknown " + IFFUtil.toChunkStr(header.formType));
break;
}
data.appendChild(planarConfiguration);
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
sampleFormat.setAttribute("value", colorMap != null ? "Index" : "UnsignedIntegral");
data.appendChild(sampleFormat);
// BitsPerSample
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
String value = bitsPerSampleValue(header.bitplanes());
bitsPerSample.setAttribute("value", value);
data.appendChild(bitsPerSample);
// SignificantBitsPerSample not in format
// SampleMSB not in format
return data;
}
private String bitsPerSampleValue(int bitplanes) {
switch (bitplanes) {
case 1:
case 2:
@@ -87,47 +192,91 @@ final class IFFImageMetadata extends StandardImageMetadataSupport {
case 6:
case 7:
case 8:
return new int[] {bitplanes};
return Integer.toString(bitplanes);
case 24:
return new int[] {8, 8, 8};
return "8 8 8";
case 25:
if (header.formType != TYPE_RGB8) {
throw new IllegalArgumentException(String.format("25 bit depth only supported for FORM type RGB8: %s", IFFUtil.toChunkStr(header.formType)));
}
return new int[] {8, 8, 8, 1};
return "8 8 8 1";
case 32:
return new int[] {8, 8, 8, 8};
return "8 8 8 8";
default:
throw new IllegalArgumentException("Unknown bit count: " + bitplanes);
}
}
private static PlanarConfiguration planarConfiguration(Form header) {
switch (header.formType) {
case TYPE_DEEP:
case TYPE_TVPP:
case TYPE_RGB8:
case TYPE_PBM:
return PlanarConfiguration.PixelInterleaved;
case TYPE_ILBM:
return PlanarConfiguration.PlaneInterleaved;
default:
return null;
@Override
protected IIOMetadataNode getStandardDimensionNode() {
if (header.aspect() == 0) {
return null;
}
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
// PixelAspectRatio
IIOMetadataNode pixelAspectRatio = new IIOMetadataNode("PixelAspectRatio");
pixelAspectRatio.setAttribute("value", String.valueOf(header.aspect()));
dimension.appendChild(pixelAspectRatio);
// TODO: HorizontalScreenSize?
// TODO: VerticalScreenSize?
return dimension;
}
private static List<TextEntry> textEntries(Form header) {
if (header.meta.isEmpty()) {
return emptyList();
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode document = new IIOMetadataNode("Document");
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
document.appendChild(formatVersion);
formatVersion.setAttribute("value", "1.0");
return document;
}
@Override
protected IIOMetadataNode getStandardTextNode() {
if (meta.isEmpty()) {
return null;
}
List<TextEntry> text = new ArrayList<>();
for (GenericChunk chunk : header.meta) {
text.add(new TextEntry(toChunkStr(chunk.chunkId),
new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8:StandardCharsets.US_ASCII)));
IIOMetadataNode text = new IIOMetadataNode("Text");
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
for (GenericChunk chunk : meta) {
IIOMetadataNode node = new IIOMetadataNode("TextEntry");
node.setAttribute("keyword", IFFUtil.toChunkStr(chunk.chunkId));
node.setAttribute("value", new String(chunk.data, chunk.chunkId == IFF.CHUNK_UTF8 ? StandardCharsets.UTF_8 : StandardCharsets.US_ASCII));
text.appendChild(node);
}
return text;
}
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
if ((colorMap == null || !colorMap.hasAlpha()) && header.bitplanes() != 32 && header.bitplanes() != 25) {
return null;
}
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
if (header.bitplanes() == 25 || header.bitplanes() == 32) {
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", header.premultiplied() ? "premultiplied" : "nonpremultiplied");
transparency.appendChild(alpha);
}
if (colorMap != null && colorMap.getTransparency() == Transparency.BITMASK) {
IIOMetadataNode transparentIndex = new IIOMetadataNode("TransparentIndex");
transparentIndex.setAttribute("value", Integer.toString(colorMap.getTransparentPixel()));
transparency.appendChild(transparentIndex);
}
return transparency;
}
}
@@ -38,19 +38,14 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -355,7 +350,9 @@ public final class IFFImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
return new IFFImageMetadata(getRawImageType(imageIndex), header, header.colorMap());
init(imageIndex);
return new IFFImageMetadata(header, header.colorMap());
}
@Override
@@ -1,37 +1,24 @@
package com.twelvemonkeys.imageio.plugins.iff;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import javax.imageio.IIOException;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*;
import java.awt.image.IndexColorModel;
import java.nio.charset.StandardCharsets;
import static java.awt.image.BufferedImage.*;
import static org.junit.Assert.*;
public class IFFImageMetadataTest {
private static final ImageTypeSpecifier TYPE_8_BIT_GRAY = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_GRAY);
private static final ImageTypeSpecifier TYPE_8_BIT_PALETTE = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_BYTE_INDEXED);
private static final ImageTypeSpecifier TYPE_24_BIT_RGB = ImageTypeSpecifiers.createFromBufferedImageType(TYPE_3BYTE_BGR);
private static final ImageTypeSpecifier TYPE_32_BIT_ARGB = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
private static final ImageTypeSpecifier TYPE_32_BIT_ARGB_DEEP = ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE);
@Test
public void testStandardFeatures() throws IIOException {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
final IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
final IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Standard metadata format
assertTrue(metadata.isStandardMetadataFormatSupported());
@@ -64,9 +51,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength());
@@ -91,9 +78,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength());
@@ -119,10 +106,9 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
byte[] bw = {0, (byte) 0xff};
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(5, chroma.getLength());
@@ -133,7 +119,7 @@ public class IFFImageMetadataTest {
IIOMetadataNode numChannels = (IIOMetadataNode) colorSpaceType.getNextSibling();
assertEquals("NumChannels", numChannels.getNodeName());
assertEquals("4", numChannels.getAttribute("value"));
assertEquals("3", numChannels.getAttribute("value"));
IIOMetadataNode blackIsZero = (IIOMetadataNode) numChannels.getNextSibling();
assertEquals("BlackIsZero", blackIsZero.getNodeName());
@@ -167,9 +153,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode compression = getStandardNode(metadata, "Compression");
IIOMetadataNode compression = metadata.getStandardCompressionNode();
assertNotNull(compression);
assertEquals("Compression", compression.getNodeName());
assertEquals(2, compression.getLength());
@@ -190,9 +176,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_NONE, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
assertNull(getStandardNode(metadata, "Compression")); // No compression, all default...
assertNull(metadata.getStandardCompressionNode()); // No compression, all default...
}
@Test
@@ -200,9 +186,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -227,9 +213,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -254,9 +240,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -283,10 +269,9 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, i, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
byte[] rgb = new byte[2 << i]; // Colors doesn't really matter here
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), rgb.length, rgb, rgb, rgb, 0));
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -312,9 +297,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_PBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_GRAY, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -339,9 +324,9 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_PBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -371,20 +356,10 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(bitmapHeader);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
if (dimension != null) {
assertEquals("Dimension", dimension.getNodeName());
assertEquals(1, dimension.getLength());
IIOMetadataNode imageOrientation = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
}
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNull(dimension);
}
@Test
@@ -393,24 +368,20 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(new CAMGChunk(4));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
// No Dimension node is okay, or one with an aspect ratio of 1.0
if (dimension != null) {
assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength());
assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
}
@@ -427,22 +398,18 @@ public class IFFImageMetadataTest {
.with(bitmapHeader)
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength());
assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("2.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
@@ -458,22 +425,18 @@ public class IFFImageMetadataTest {
.with(bitmapHeader)
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength());
assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("0.5", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
@@ -484,22 +447,18 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(viewPort);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode dimension = getStandardNode(metadata, "Dimension");
IIOMetadataNode dimension = metadata.getStandardDimensionNode();
assertNotNull(dimension);
assertEquals("Dimension", dimension.getNodeName());
assertEquals(2, dimension.getLength());
assertEquals(1, dimension.getLength());
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) dimension.getFirstChild();
assertEquals("PixelAspectRatio", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode imageOrientation = (IIOMetadataNode) pixelAspectRatio.getNextSibling();
assertEquals("ImageOrientation", imageOrientation.getNodeName());
assertEquals("Normal", imageOrientation.getAttribute("value"));
assertNull(imageOrientation.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
@@ -507,33 +466,32 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode document = getStandardNode(metadata, "Document");
IIOMetadataNode document = metadata.getStandardDocumentNode();
assertNotNull(document);
assertEquals("Document", document.getNodeName());
assertEquals(1, document.getLength());
IIOMetadataNode formatVersion = (IIOMetadataNode) document.getFirstChild();
assertEquals("FormatVersion", formatVersion.getNodeName());
assertEquals("1.0", formatVersion.getAttribute("value"));
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) document.getFirstChild();
assertEquals("FormatVersion", pixelAspectRatio.getNodeName());
assertEquals("1.0", pixelAspectRatio.getAttribute("value"));
assertNull(formatVersion.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
public void testStandardText() throws IIOException {
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
String[] texts = {"annotation", "dupe", "äñnótâtïøñ"};
int[] chunks = {IFF.CHUNK_ANNO, IFF.CHUNK_UTF8};
String[] texts = {"annotation", "äñnótâtïøñ"};
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 8, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0))
.with(new GenericChunk(chunks[0], texts[0].getBytes(StandardCharsets.US_ASCII)))
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.US_ASCII)))
.with(new GenericChunk(chunks[2], texts[2].getBytes(StandardCharsets.UTF_8)));
.with(new GenericChunk(chunks[1], texts[1].getBytes(StandardCharsets.UTF_8)));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_8_BIT_PALETTE, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode text = getStandardNode(metadata, "Text");
IIOMetadataNode text = metadata.getStandardTextNode();
assertNotNull(text);
assertEquals("Text", text.getNodeName());
assertEquals(texts.length, text.getLength());
@@ -551,21 +509,10 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 24, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_24_BIT_RGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
if (transparency != null) {
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName());
assertEquals("none", alpha.getAttribute("value"));
assertNull(alpha.getNextSibling()); // No more children
}
// Otherwise, no transparency, just defaults
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNull(transparency); // No transparency, just defaults
}
@Test
@@ -573,18 +520,18 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_ILBM)
.with(new BMHDChunk(300, 200, 32, BMHDChunk.MASK_HAS_MASK, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName());
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", pixelAspectRatio.getNodeName());
assertEquals("nonpremultiplied", pixelAspectRatio.getAttribute("value"));
assertNull(alpha.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
@@ -593,33 +540,28 @@ public class IFFImageMetadataTest {
.with(new BMHDChunk(300, 200, 1, BMHDChunk.MASK_TRANSPARENT_COLOR, BMHDChunk.COMPRESSION_BYTE_RUN, 1));
byte[] bw = {0, (byte) 0xff};
ImageTypeSpecifier fromIndexColorModel = ImageTypeSpecifiers.createFromIndexColorModel(new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IFFImageMetadata metadata = new IFFImageMetadata(fromIndexColorModel, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, new IndexColorModel(header.bitplanes(), bw.length, bw, bw, bw, header.transparentIndex()));
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName());
assertEquals(2, transparency.getLength());
assertEquals(1, transparency.getLength());
IIOMetadataNode alpha = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("Alpha", alpha.getNodeName());
assertEquals("nonpremultiplied", alpha.getAttribute("value"));
IIOMetadataNode pixelAspectRatio = (IIOMetadataNode) transparency.getFirstChild();
assertEquals("TransparentIndex", pixelAspectRatio.getNodeName());
assertEquals("1", pixelAspectRatio.getAttribute("value"));
IIOMetadataNode transparentIndex = (IIOMetadataNode) alpha.getNextSibling();
assertEquals("TransparentIndex", transparentIndex.getNodeName());
assertEquals("1", transparentIndex.getAttribute("value"));
assertNull(transparentIndex.getNextSibling()); // No more children
assertNull(pixelAspectRatio.getNextSibling()); // No more children
}
@Test
public void testStandardRGB8() throws IIOException {
Form header = Form.ofType(IFF.TYPE_RGB8)
.with(new BMHDChunk(300, 200, 25, BMHDChunk.MASK_NONE, BMHDChunk.COMPRESSION_BYTE_RUN, 0));
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Chroma
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength());
@@ -639,7 +581,7 @@ public class IFFImageMetadataTest {
assertNull(blackIsZero.getNextSibling()); // No more children
// Data
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -659,7 +601,7 @@ public class IFFImageMetadataTest {
assertNull(bitsPerSample.getNextSibling()); // No more children
// Transparency
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
@@ -682,10 +624,10 @@ public class IFFImageMetadataTest {
Form header = Form.ofType(IFF.TYPE_DEEP)
.with(new DGBLChunk(8))
.with(dpel);
IFFImageMetadata metadata = new IFFImageMetadata(TYPE_32_BIT_ARGB_DEEP, header, header.colorMap());
IFFImageMetadata metadata = new IFFImageMetadata(header, null);
// Chroma
IIOMetadataNode chroma = getStandardNode(metadata, "Chroma");
IIOMetadataNode chroma = metadata.getStandardChromaNode();
assertNotNull(chroma);
assertEquals("Chroma", chroma.getNodeName());
assertEquals(3, chroma.getLength());
@@ -707,7 +649,7 @@ public class IFFImageMetadataTest {
assertNull(blackIsZero.getNextSibling()); // No more children
// Data
IIOMetadataNode data = getStandardNode(metadata, "Data");
IIOMetadataNode data = metadata.getStandardDataNode();
assertNotNull(data);
assertEquals("Data", data.getNodeName());
assertEquals(3, data.getLength());
@@ -727,7 +669,7 @@ public class IFFImageMetadataTest {
assertNull(bitsPerSample.getNextSibling()); // No more children
// Transparency
IIOMetadataNode transparency = getStandardNode(metadata, "Transparency");
IIOMetadataNode transparency = metadata.getStandardTransparencyNode();
assertNotNull(transparency);
assertEquals("Transparency", transparency.getNodeName());
assertEquals(1, transparency.getLength());
@@ -738,13 +680,4 @@ public class IFFImageMetadataTest {
assertNull(alpha.getNextSibling()); // No more children
}
// TODO: Test RGB8 + ColorMap
private IIOMetadataNode getStandardNode(IIOMetadata metadata, String nodeName) {
IIOMetadataNode asTree = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
NodeList nodes = asTree.getElementsByTagName(nodeName);
return nodes.getLength() > 0 ? (IIOMetadataNode) nodes.item(0) : null;
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
@@ -173,14 +173,10 @@ public final class TIFFEntry extends AbstractEntry {
return "TileByteCounts";
case TIFF.TAG_COPYRIGHT:
return "Copyright";
case TIFF.TAG_YCBCR_COEFFICIENTS:
return "YCbCrCoefficients";
case TIFF.TAG_YCBCR_SUB_SAMPLING:
return "YCbCrSubSampling";
case TIFF.TAG_YCBCR_POSITIONING:
return "YCbCrPositioning";
case TIFF.TAG_REFERENCE_BLACK_WHITE:
return "ReferenceBlackWhite";
case TIFF.TAG_COLOR_MAP:
return "ColorMap";
case TIFF.TAG_INK_SET:
@@ -1,16 +1,16 @@
package com.twelvemonkeys.imageio.metadata.tiff;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Random;
import org.junit.Test;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
/**
* HalfTest.
@@ -97,7 +97,7 @@ public class HalfTest {
}
@Test(expected = NullPointerException.class)
public void testParseHalfNull() {
public void testParseHAlfNull() {
Half.parseHalf(null);
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
@@ -45,7 +45,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInput;
import java.io.DataInputStream;
@@ -213,12 +213,14 @@ public final class PCXImageReader extends ImageReaderBase {
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
for (int y = 0; y < height; y++) {
if (header.getBitsPerPixel() != 1) {
throw new AssertionError();
switch (header.getBitsPerPixel()) {
case 1:
readRowByte(input, srcRegion, xSub, ySub, planeData, 0, planeWidth * header.getChannels(), destRaster, clippedRow, y);
break;
default:
throw new AssertionError();
}
readRowByte(input, srcRegion, xSub, ySub, planeData, 0, planeWidth * header.getChannels(), destRaster, clippedRow, y);
int pixelPos = 0;
for (int planePos = 0; planePos < planeWidth; planePos++) {
BitRotator.bitRotateCW(planeData, planePos, planeWidth, rowDataByte, pixelPos, 1);
@@ -377,12 +379,10 @@ public final class PCXImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
// checkBounds(imageIndex);
// readHeader();
//
// return new PCXMetadata(header, getVGAPalette());
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
return new PCXMetadata(rawType, header);
checkBounds(imageIndex);
readHeader();
return new PCXMetadata(header, getVGAPalette());
}
private IndexColorModel getVGAPalette() throws IOException {
@@ -1,29 +1,236 @@
/*
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.pcx;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.IndexColorModel;
final class PCXMetadata extends StandardImageMetadataSupport {
public PCXMetadata(ImageTypeSpecifier type, PCXHeader header) {
super(builder(type)
.withPlanarConfiguration(planarConfiguration(header))
.withCompressionTypeName(compressionName(header))
.withFormatVersion(String.valueOf(header.getVersion())));
final class PCXMetadata extends AbstractMetadata {
private final PCXHeader header;
private final IndexColorModel vgaPalette;
PCXMetadata(final PCXHeader header, final IndexColorModel vgaPalette) {
this.header = header;
this.vgaPalette = vgaPalette;
}
private static PlanarConfiguration planarConfiguration(PCXHeader header) {
return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
private static String compressionName(PCXHeader header) {
switch (header.getCompression()) {
case PCX.COMPRESSION_NONE:
return "None";
case PCX.COMPRESSION_RLE:
return "RLE";
IndexColorModel palette = null;
boolean gray = false;
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
switch (header.getBitsPerPixel()) {
case 1:
case 2:
case 4:
palette = header.getEGAPalette();
csType.setAttribute("name", "RGB");
break;
case 8:
// We may have IndexColorModel here for 1 channel images
if (header.getChannels() == 1 && vgaPalette != null) {
palette = vgaPalette;
csType.setAttribute("name", "RGB");
break;
}
if (header.getChannels() == 1) {
csType.setAttribute("name", "GRAY");
gray = true;
break;
}
csType.setAttribute("name", "RGB");
break;
case 24:
// Some sources says this is possible... Untested.
csType.setAttribute("name", "RGB");
break;
default:
csType.setAttribute("name", "Unknown");
}
return "Unknown";
chroma.appendChild(csType);
// NOTE: Channels in chroma node reflects channels in color model, not data! (see data node)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", gray ? "1" : "3");
chroma.appendChild(numChannels);
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
blackIsZero.setAttribute("value", "TRUE");
chroma.appendChild(blackIsZero);
if (palette != null) {
IIOMetadataNode paletteNode = new IIOMetadataNode("Palette");
chroma.appendChild(paletteNode);
for (int i = 0; i < palette.getMapSize(); i++) {
IIOMetadataNode paletteEntry = new IIOMetadataNode("PaletteEntry");
paletteEntry.setAttribute("index", Integer.toString(i));
paletteEntry.setAttribute("red", Integer.toString(palette.getRed(i)));
paletteEntry.setAttribute("green", Integer.toString(palette.getGreen(i)));
paletteEntry.setAttribute("blue", Integer.toString(palette.getBlue(i)));
paletteNode.appendChild(paletteEntry);
}
}
return chroma;
}
// No compression
@Override
protected IIOMetadataNode getStandardCompressionNode() {
if (header.getCompression() != PCX.COMPRESSION_NONE) {
IIOMetadataNode node = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", header.getCompression() == PCX.COMPRESSION_RLE ? "RLE" : "Uknown");
node.appendChild(compressionTypeName);
IIOMetadataNode lossless = new IIOMetadataNode("Lossless");
lossless.setAttribute("value", "TRUE");
node.appendChild(lossless);
return node;
}
return null;
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
// Planar configuration only makes sense for multi-channel images
if (header.getChannels() > 1) {
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
planarConfiguration.setAttribute("value", "LineInterleaved");
node.appendChild(planarConfiguration);
}
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
switch (header.getBitsPerPixel()) {
case 1:
case 2:
case 4:
sampleFormat.setAttribute("value", "Index");
break;
case 8:
if (header.getChannels() == 1 && vgaPalette != null) {
sampleFormat.setAttribute("value", "Index");
break;
}
// Else fall through for GRAY
default:
sampleFormat.setAttribute("value", "UnsignedIntegral");
break;
}
node.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
bitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel())));
node.appendChild(bitsPerSample);
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
significantBitsPerSample.setAttribute("value", createListValue(header.getChannels(), Integer.toString(header.getBitsPerPixel())));
node.appendChild(significantBitsPerSample);
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
sampleMSB.setAttribute("value", createListValue(header.getChannels(), "0"));
return node;
}
private String createListValue(final int itemCount, final String... values) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < itemCount; i++) {
if (buffer.length() > 0) {
buffer.append(' ');
}
buffer.append(values[i % values.length]);
}
return buffer.toString();
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value", "Normal");
dimension.appendChild(imageOrientation);
return dimension;
}
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Document");
IIOMetadataNode imageOrientation = new IIOMetadataNode("FormatVersion");
imageOrientation.setAttribute("value", String.valueOf(header.getVersion()));
dimension.appendChild(imageOrientation);
return dimension;
}
// No text node
// No tiling
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
// NOTE: There doesn't seem to be any god way to determine transparency, other than by convention
// 1 channel: Gray, 2 channel: Gray + Alpha, 3 channel: RGB, 4 channel: RGBA (hopefully never CMYK...)
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", header.getChannels() == 1 || header.getChannels() == 3 ? "none" : "nonpremultiplied");
transparency.appendChild(alpha);
return transparency;
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
@@ -67,28 +67,18 @@ import com.twelvemonkeys.io.enc.Decoder;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.*;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.geom.*;
import java.awt.color.ColorSpace;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.image.*;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.io.*;
import java.util.List;
import java.util.*;
/**
* Reader for Apple Mac Paint Picture (PICT) format.
@@ -2621,9 +2611,7 @@ public final class PICTImageReader extends ImageReaderBase {
return getYPtCoord(getPICTFrame().height);
}
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException {
checkBounds(imageIndex);
public Iterator<ImageTypeSpecifier> getImageTypes(int pIndex) {
// TODO: The images look slightly different in Preview.. Could indicate the color space is wrong...
return Collections.singletonList(
ImageTypeSpecifiers.createPacked(
@@ -2635,10 +2623,10 @@ public final class PICTImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
ImageTypeSpecifier rawType = getRawImageType(imageIndex);
checkBounds(imageIndex);
getPICTFrame(); // TODO: Would probably be better to use readPictHeader here, but it isn't cached
return new PICTMetadata(rawType, version, screenImageXRatio, screenImageYRatio);
return new PICTMetadata(version, screenImageXRatio, screenImageYRatio);
}
protected static void showIt(final BufferedImage pImage, final String pTitle) {
@@ -30,20 +30,19 @@
package com.twelvemonkeys.imageio.plugins.pict;
import java.io.EOFException;
import java.io.IOException;
import java.util.Locale;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
import java.io.EOFException;
import java.io.IOException;
import java.util.Locale;
/**
* PICTImageReaderSpi
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @version $Id: PICTImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haraldk Exp$
* @version $Id: PICTImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haku Exp$
*/
public final class PICTImageReaderSpi extends ImageReaderSpiBase {
@@ -62,7 +61,7 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase {
ImageInputStream stream = (ImageInputStream) pSource;
// PICT format doesn't have good magic and our method often gives false positives,
// PICT format don't have good magic and our method often gives false positives,
// We'll check for other known formats (BMP, GIF, JPEG, PNG, PSD, TIFF) first
if (isOtherFormat(stream)) {
return false;
@@ -77,7 +76,7 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase {
}
else {
// We need to reset AND set mark again, to make sure the reset call in
// the finally-block will not consume existing marks
// the finally block will not consume existing marks
stream.reset();
stream.mark();
@@ -150,8 +149,8 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase {
}
static void skipNullHeader(final ImageInputStream pStream) throws IOException {
// NOTE: Only skip if FILE FORMAT, not needed for macOS DnD
// Spec says "platform dependent", may not be all nulls...
// NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD
// Spec says "platform dependent", may not be all nulls..
pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE);
}
@@ -1,38 +1,8 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.pict;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataNode;
/**
* PICTMetadata.
@@ -41,13 +11,82 @@ import javax.imageio.ImageTypeSpecifier;
* @author last modified by $Author: haraldk$
* @version $Id: PICTMetadata.java,v 1.0 23/03/2021 haraldk Exp$
*/
final class PICTMetadata extends StandardImageMetadataSupport {
PICTMetadata(final ImageTypeSpecifier type, final int version, final double screenImageXRatio, final double screenImageYRatio) {
super(builder(type)
.withPixelAspectRatio(screenImageXRatio > 0.0d && screenImageYRatio > 0.0d ? screenImageXRatio / screenImageYRatio : 1)
.withFormatVersion(Integer.toString(version))
);
// As this is a vector-ish format, some of the data makes no sense... :-P
// It is, however, consistent with the getRawImageTyp/getImageTypes
public class PICTMetadata extends AbstractMetadata {
private final int version;
private final double screenImageXRatio;
private final double screenImageYRatio;
PICTMetadata(final int version, final double screenImageXRatio, final double screenImageYRatio) {
this.version = version;
this.screenImageXRatio = screenImageXRatio;
this.screenImageYRatio = screenImageYRatio;
}
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
csType.setAttribute("name", "RGB");
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
chroma.appendChild(numChannels);
numChannels.setAttribute("value", "3");
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
chroma.appendChild(blackIsZero);
blackIsZero.setAttribute("value", "TRUE");
return chroma;
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
if (screenImageXRatio > 0.0d && screenImageYRatio > 0.0d) {
IIOMetadataNode node = new IIOMetadataNode("Dimension");
double ratio = screenImageXRatio / screenImageYRatio;
IIOMetadataNode subNode = new IIOMetadataNode("PixelAspectRatio");
subNode.setAttribute("value", "" + ratio);
node.appendChild(subNode);
return node;
}
return null;
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode data = new IIOMetadataNode("Data");
// As this is a vector-ish format, with possibly multiple regions of pixel data, this makes no sense... :-P
// This is, however, consistent with the getRawImageTyp/getImageTypes
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
planarConfiguration.setAttribute("value", "PixelInterleaved");
data.appendChild(planarConfiguration);
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
sampleFormat.setAttribute("value", "UnsignedIntegral");
data.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
bitsPerSample.setAttribute("value", "32");
data.appendChild(bitsPerSample);
return data;
}
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode document = new IIOMetadataNode("Document");
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
document.appendChild(formatVersion);
formatVersion.setAttribute("value", Integer.toString(version));
return document;
}
}
@@ -1,33 +1,3 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.pntg;
import com.twelvemonkeys.imageio.ImageReaderBase;
@@ -64,7 +34,7 @@ public final class PNTGImageReader extends ImageReaderBase {
private static final Set<ImageTypeSpecifier> IMAGE_TYPES =
Collections.singleton(ImageTypeSpecifiers.createIndexed(new int[] {-1, 0}, false, -1, 1, DataBuffer.TYPE_BYTE));
PNTGImageReader(final ImageReaderSpi provider) {
protected PNTGImageReader(final ImageReaderSpi provider) {
super(provider);
}
@@ -153,7 +123,9 @@ public final class PNTGImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
return new PNTGMetadata(getRawImageType(imageIndex));
checkBounds(imageIndex);
return new PNTGMetadata();
}
private void readHeader() throws IOException {
@@ -1,33 +1,3 @@
/*
* Copyright (c) 2021, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.pntg;
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
@@ -1,38 +1,8 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.pntg;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataNode;
/**
* PNTGMetadata.
@@ -41,11 +11,76 @@ import javax.imageio.ImageTypeSpecifier;
* @author last modified by $Author: haraldk$
* @version $Id: PNTGMetadata.java,v 1.0 23/03/2021 haraldk Exp$
*/
final class PNTGMetadata extends StandardImageMetadataSupport {
public PNTGMetadata(ImageTypeSpecifier type) {
super(builder(type)
.withBlackIsZero(false)
.withCompressionTypeName("PackBits")
.withFormatVersion("1.0"));
public class PNTGMetadata extends AbstractMetadata {
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
chroma.appendChild(csType);
csType.setAttribute("name", "GRAY");
// NOTE: Channels in chroma node reflects channels in color model (see data node, for channels in data)
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
chroma.appendChild(numChannels);
numChannels.setAttribute("value", "1");
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
chroma.appendChild(blackIsZero);
blackIsZero.setAttribute("value", "FALSE");
return chroma;
}
@Override
protected IIOMetadataNode getStandardCompressionNode() {
IIOMetadataNode compressionNode = new IIOMetadataNode("Compression");
IIOMetadataNode compressionTypeName = new IIOMetadataNode("CompressionTypeName");
compressionTypeName.setAttribute("value", "PackBits"); // RLE?
compressionNode.appendChild(compressionTypeName);
compressionNode.appendChild(new IIOMetadataNode("Lossless"));
// "value" defaults to TRUE
return compressionNode;
}
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode data = new IIOMetadataNode("Data");
// PlanarConfiguration
IIOMetadataNode planarConfiguration = new IIOMetadataNode("PlanarConfiguration");
planarConfiguration.setAttribute("value", "PixelInterleaved");
data.appendChild(planarConfiguration);
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
sampleFormat.setAttribute("value", "UnsignedIntegral");
data.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
bitsPerSample.setAttribute("value", "1");
data.appendChild(bitsPerSample);
return data;
}
@Override
protected IIOMetadataNode getStandardDocumentNode() {
IIOMetadataNode document = new IIOMetadataNode("Document");
IIOMetadataNode formatVersion = new IIOMetadataNode("FormatVersion");
document.appendChild(formatVersion);
formatVersion.setAttribute("value", "1.0");
// TODO: We could get the file creation time from MacBinary header here...
return document;
}
@Override
protected IIOMetadataNode getStandardTextNode() {
// TODO: We could get the file name from MacBinary header here...
return super.getStandardTextNode();
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, Harald Kuhr
* Copyright (c) 2015, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -1,11 +1,7 @@
package com.twelvemonkeys.imageio.plugins.pntg;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import org.junit.Test;
import java.awt.image.*;
/**
* PNTGMetadataTest.
*
@@ -16,6 +12,6 @@ import java.awt.image.*;
public class PNTGMetadataTest {
@Test
public void testCreate() {
new PNTGMetadata(ImageTypeSpecifiers.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY));
new PNTGMetadata();
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
@@ -43,7 +43,7 @@ import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.DataInput;
import java.io.DataInputStream;
@@ -468,7 +468,10 @@ public final class PNMImageReader extends ImageReaderBase {
@Override
public IIOMetadata getImageMetadata(final int imageIndex) throws IOException {
return new PNMMetadata(getRawImageType(imageIndex), header);
checkBounds(imageIndex);
readHeader();
return new PNMMetadata(header);
}
public static void main(String[] args) throws IOException {
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022, Harald Kuhr
* Copyright (c) 2014, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -30,35 +30,92 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.StandardImageMetadataSupport;
import com.twelvemonkeys.imageio.AbstractMetadata;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*;
import java.awt.*;
import java.awt.image.DataBuffer;
import java.nio.ByteOrder;
/**
* PNMMetadata.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
final class PNMMetadata extends StandardImageMetadataSupport {
final class PNMMetadata extends AbstractMetadata {
private final PNMHeader header;
PNMMetadata(ImageTypeSpecifier type, PNMHeader header) {
super(builder(type)
.withColorSpaceType(colorSpace(header))
// TODO: Might make sense to set gamma?
.withBlackIsZero(header.getTupleType() != TupleType.BLACKANDWHITE_WHITE_IS_ZERO)
.withSignificantBitsPerSample(significantBits(header))
.withSampleMSB(header.getByteOrder() == ByteOrder.BIG_ENDIAN ? 0 : header.getBitsPerSample() - 1)
.withOrientation(orientation(header))
);
PNMMetadata(final PNMHeader header) {
this.header = header;
}
private static int significantBits(PNMHeader header) {
@Override
protected IIOMetadataNode getStandardChromaNode() {
IIOMetadataNode chroma = new IIOMetadataNode("Chroma");
IIOMetadataNode csType = new IIOMetadataNode("ColorSpaceType");
switch (header.getTupleType()) {
case BLACKANDWHITE:
case BLACKANDWHITE_ALPHA:
case BLACKANDWHITE_WHITE_IS_ZERO:
case GRAYSCALE:
case GRAYSCALE_ALPHA:
csType.setAttribute("name", "GRAY");
break;
case RGB:
case RGB_ALPHA:
csType.setAttribute("name", "RGB");
break;
case CMYK:
case CMYK_ALPHA:
csType.setAttribute("name", "CMYK");
break;
}
if (csType.getAttribute("name") != null) {
chroma.appendChild(csType);
}
IIOMetadataNode numChannels = new IIOMetadataNode("NumChannels");
numChannels.setAttribute("value", Integer.toString(header.getSamplesPerPixel()));
chroma.appendChild(numChannels);
// TODO: Might make sense to set gamma?
IIOMetadataNode blackIsZero = new IIOMetadataNode("BlackIsZero");
blackIsZero.setAttribute("value", header.getTupleType() == TupleType.BLACKANDWHITE_WHITE_IS_ZERO
? "FALSE"
: "TRUE");
chroma.appendChild(blackIsZero);
return chroma;
}
// No compression
@Override
protected IIOMetadataNode getStandardDataNode() {
IIOMetadataNode node = new IIOMetadataNode("Data");
IIOMetadataNode sampleFormat = new IIOMetadataNode("SampleFormat");
sampleFormat.setAttribute("value", header.getTransferType() == DataBuffer.TYPE_FLOAT
? "Real"
: "UnsignedIntegral");
node.appendChild(sampleFormat);
IIOMetadataNode bitsPerSample = new IIOMetadataNode("BitsPerSample");
bitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(header.getBitsPerSample())));
node.appendChild(bitsPerSample);
IIOMetadataNode significantBitsPerSample = new IIOMetadataNode("SignificantBitsPerSample");
significantBitsPerSample.setAttribute("value", createListValue(header.getSamplesPerPixel(), Integer.toString(computeSignificantBits())));
node.appendChild(significantBitsPerSample);
String msb = header.getByteOrder() == ByteOrder.BIG_ENDIAN
? "0"
: Integer.toString(header.getBitsPerSample() - 1);
IIOMetadataNode sampleMSB = new IIOMetadataNode("SampleMSB");
sampleMSB.setAttribute("value", createListValue(header.getSamplesPerPixel(), msb));
return node;
}
private int computeSignificantBits() {
if (header.getTransferType() == DataBuffer.TYPE_FLOAT) {
return header.getBitsPerSample();
}
@@ -75,30 +132,38 @@ final class PNMMetadata extends StandardImageMetadataSupport {
return significantBits;
}
private static ColorSpaceType colorSpace(PNMHeader header) {
switch (header.getTupleType()) {
case BLACKANDWHITE:
case BLACKANDWHITE_ALPHA:
case BLACKANDWHITE_WHITE_IS_ZERO:
case GRAYSCALE:
case GRAYSCALE_ALPHA:
return ColorSpaceType.GRAY;
default:
return null; // Fall back to color model's type
}
}
private String createListValue(final int itemCount, final String... values) {
StringBuilder buffer = new StringBuilder();
private static ImageOrientation orientation(PNMHeader header) {
// For some reason, the float values are stored bottom-up
return header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB
? ImageOrientation.FlipH
: ImageOrientation.Normal;
for (int i = 0; i < itemCount; i++) {
if (buffer.length() > 0) {
buffer.append(' ');
}
buffer.append(values[i % values.length]);
}
return buffer.toString();
}
@Override
protected IIOMetadataNode getStandardDimensionNode() {
IIOMetadataNode dimension = new IIOMetadataNode("Dimension");
IIOMetadataNode imageOrientation = new IIOMetadataNode("ImageOrientation");
imageOrientation.setAttribute("value",
header.getFileType() == PNM.PFM_GRAY || header.getFileType() == PNM.PFM_RGB
? "FlipH"
: "Normal");
dimension.appendChild(imageOrientation);
return dimension;
}
// No document node
@Override
protected IIOMetadataNode getStandardTextNode() {
// TODO: Could avoid this override, by changing the StandardImageMetadataSupport to
// use List<Entry<String, String>> instead of Map<String, String> (we use duplicate "comment"s).
if (!header.getComments().isEmpty()) {
IIOMetadataNode text = new IIOMetadataNode("Text");
@@ -114,4 +179,17 @@ final class PNMMetadata extends StandardImageMetadataSupport {
return null;
}
// No tiling
@Override
protected IIOMetadataNode getStandardTransparencyNode() {
IIOMetadataNode transparency = new IIOMetadataNode("Transparency");
IIOMetadataNode alpha = new IIOMetadataNode("Alpha");
alpha.setAttribute("value", header.getTransparency() == Transparency.OPAQUE ? "none" : "nonpremultiplied");
transparency.appendChild(alpha);
return transparency;
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.3</version>
<version>3.8.2</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -1,266 +0,0 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.lang.Validate;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
/**
* A decoder for data converted using "horizontal differencing predictor".
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
*/
final class HorizontalDeDifferencingStream extends InputStream {
/// TODO: Create shared version with TIFF, or see if we can avoid some duplication?
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
private final int columns;
// NOTE: PlanarConfiguration == 2 may be treated as samplesPerPixel == 1
private final int samplesPerPixel;
private final int bitsPerSample;
private final ReadableByteChannel channel;
private final ByteBuffer buffer;
public HorizontalDeDifferencingStream(final InputStream stream, final int columns, final int samplesPerPixel, final int bitsPerSample, final ByteOrder byteOrder) {
this.columns = Validate.isTrue(columns > 0, columns, "width must be greater than 0");
this.samplesPerPixel = Validate.isTrue(bitsPerSample >= 8 || samplesPerPixel == 1, samplesPerPixel, "Unsupported samples per pixel for < 8 bit samples: %s");
this.bitsPerSample = Validate.isTrue(isValidBPS(bitsPerSample), bitsPerSample, "Unsupported bits per sample value: %s");
channel = Channels.newChannel(Validate.notNull(stream, "stream"));
buffer = ByteBuffer.allocate((columns * samplesPerPixel * bitsPerSample + 7) / 8).order(byteOrder);
buffer.flip();
}
static boolean isValidBPS(final int bitsPerSample) {
switch (bitsPerSample) {
case 1:
case 2:
case 4:
case 8:
case 16:
case 32:
case 64:
return true;
default:
return false;
}
}
@SuppressWarnings("StatementWithEmptyBody")
private boolean fetch() throws IOException {
buffer.clear();
// This *SHOULD* read an entire row of pixels (or nothing at all) into the buffer,
// otherwise we will throw EOFException below
while (channel.read(buffer) > 0);
if (buffer.position() > 0) {
if (buffer.hasRemaining()) {
throw new EOFException("Unexpected end of stream");
}
decodeRow();
buffer.flip();
return true;
}
else {
buffer.position(buffer.capacity());
return false;
}
}
private void decodeRow() {
// Un-apply horizontal predictor
byte original;
int sample = 0;
byte temp;
// Optimization:
// Access array directly for <= 8 bits per sample, as buffer does extra index bounds check for every
// put/get operation... (Measures to about 100 ms difference for 4000 x 3000 image)
final byte[] array = buffer.array();
switch (bitsPerSample) {
case 1:
for (int b = 0; b < (columns + 7) / 8; b++) {
original = array[b];
sample += (original >> 7) & 0x1;
temp = (byte) ((sample << 7) & 0x80);
sample += (original >> 6) & 0x1;
temp |= (byte) ((sample << 6) & 0x40);
sample += (original >> 5) & 0x1;
temp |= (byte) ((sample << 5) & 0x20);
sample += (original >> 4) & 0x1;
temp |= (byte) ((sample << 4) & 0x10);
sample += (original >> 3) & 0x1;
temp |= (byte) ((sample << 3) & 0x08);
sample += (original >> 2) & 0x1;
temp |= (byte) ((sample << 2) & 0x04);
sample += (original >> 1) & 0x1;
temp |= (byte) ((sample << 1) & 0x02);
sample += original & 0x1;
array[b] = (byte) (temp | sample & 0x1);
}
break;
case 2:
for (int b = 0; b < (columns + 3) / 4; b++) {
original = array[b];
sample += (original >> 6) & 0x3;
temp = (byte) ((sample << 6) & 0xc0);
sample += (original >> 4) & 0x3;
temp |= (byte) ((sample << 4) & 0x30);
sample += (original >> 2) & 0x3;
temp |= (byte) ((sample << 2) & 0x0c);
sample += original & 0x3;
array[b] = (byte) (temp | sample & 0x3);
}
break;
case 4:
for (int b = 0; b < (columns + 1) / 2; b++) {
original = array[b];
sample += (original >> 4) & 0xf;
temp = (byte) ((sample << 4) & 0xf0);
sample += original & 0x0f;
array[b] = (byte) (temp | sample & 0xf);
}
break;
case 8:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
array[off] = (byte) (array[off - samplesPerPixel] + array[off]);
}
}
break;
case 16:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
buffer.putShort(2 * off, (short) (buffer.getShort(2 * (off - samplesPerPixel)) + buffer.getShort(2 * off)));
}
}
break;
case 32:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
buffer.putInt(4 * off, buffer.getInt(4 * (off - samplesPerPixel)) + buffer.getInt(4 * off));
}
}
break;
case 64:
for (int x = 1; x < columns; x++) {
for (int b = 0; b < samplesPerPixel; b++) {
int off = x * samplesPerPixel + b;
buffer.putLong(8 * off, buffer.getLong(8 * (off - samplesPerPixel)) + buffer.getLong(8 * off));
}
}
break;
default:
throw new AssertionError(String.format("Unsupported bits per sample value: %d", bitsPerSample));
}
}
@Override
public int read() throws IOException {
if (!buffer.hasRemaining()) {
if (!fetch()) {
return -1;
}
}
return buffer.get() & 0xff;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (!buffer.hasRemaining()) {
if (!fetch()) {
return -1;
}
}
int read = Math.min(buffer.remaining(), len);
buffer.get(b, off, read);
return read;
}
@Override
public long skip(long n) throws IOException {
if (n < 0) {
return 0;
}
if (!buffer.hasRemaining()) {
if (!fetch()) {
return 0; // SIC
}
}
int skipped = (int) Math.min(buffer.remaining(), n);
buffer.position(buffer.position() + skipped);
return skipped;
}
@Override
public void close() throws IOException {
try {
super.close();
}
finally {
if (channel.isOpen()) {
channel.close();
}
}
}
}
@@ -31,10 +31,10 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* PSDDirectoryResource
@@ -69,25 +69,16 @@ abstract class PSDDirectoryResource extends PSDImageResource {
public String toString() {
StringBuilder builder = toStringBuilder();
if (directory != null) {
builder.append(", ").append(directory);
builder.append("]");
}
else {
int length = Math.min(256, data.length);
String data = new String(this.data, 0, length, StandardCharsets.US_ASCII)
.replace('\uFFFD', '.')
.replaceAll("\\s+", " ")
.replaceAll("[^\\p{Print}]", ".");
builder.append(", data: \"").append(data);
int length = Math.min(256, data.length);
String data = StringUtil.decode(this.data, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
builder.append(", data: \"").append(data);
if (length < this.data.length) {
builder.append("...");
}
builder.append("\"]");
if (length < this.data.length) {
builder.append("...");
}
builder.append("\"]");
return builder.toString();
}
}
@@ -85,4 +85,19 @@ final class PSDEXIF1Data extends PSDDirectoryResource {
output.writeInt((int) (afterExif - beforeExif));
output.seek(afterExif);
}
@Override
public String toString() {
Directory directory = getDirectory();
if (directory == null) {
return super.toString();
}
StringBuilder builder = toStringBuilder();
builder.append(", ").append(directory);
builder.append("]");
return builder.toString();
}
}
@@ -53,4 +53,19 @@ final class PSDIPTCData extends PSDDirectoryResource {
Directory parseDirectory() throws IOException {
return new IPTCReader().read(new ByteArrayImageInputStream(data));
}
@Override
public String toString() {
Directory directory = getDirectory();
if (directory == null) {
return super.toString();
}
StringBuilder builder = toStringBuilder();
builder.append(", ").append(directory);
builder.append("]");
return builder.toString();
}
}
@@ -43,19 +43,15 @@ import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace;
import java.awt.color.ICC_Profile;
import java.awt.image.*;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
import java.util.*;
/**
* ImageReader for Adobe Photoshop Document (PSD) format.
@@ -66,11 +62,7 @@ import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorSt
* @see <a href="http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/">Adobe Photoshop File Formats Specification</a>
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary</a>
*/
// TODO: Rethink metadata impl.
// * We should probably move most of the current impl to stream metadata as it belongs to the document, not the individual layers
// * Make each layer's metadata contain correct data, name, sub-image type, position etc.
// * Retain some information in the merged image/layers?
// * Completely skip the non-pixel layers in the reader (no longer return null, that's just ugly)
// TODO: Implement ImageIO meta data interface
// TODO: Figure out of we should assume Adobe RGB (1998) color model, if no embedded profile?
// TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0)
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
@@ -78,7 +70,6 @@ import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorSt
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
// Done: Allow reading the extra alpha channels (index after composite data)
// Done: Implement ImageIO meta data interface
public final class PSDImageReader extends ImageReaderBase {
final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.psd.debug"));
@@ -399,21 +390,21 @@ public final class PSDImageReader extends ImageReaderBase {
int compression = imageInput.readShort();
metadata.compression = compression;
int[][] byteCounts = null;
int[] byteCounts = null;
switch (compression) {
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
case PSD.COMPRESSION_NONE:
break;
case PSD.COMPRESSION_RLE:
// NOTE: Byte counts will allow us to easily skip rows before AOI
byteCounts = new int[header.channels][header.height];
for (int c = 0; c < header.channels; c++) {
for (int y = 0; y < header.height; y++) {
byteCounts[c][y] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
}
byteCounts = new int[header.channels * header.height];
for (int i = 0; i < byteCounts.length; i++) {
byteCounts[i] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
}
break;
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here.. Look at TIFF prediction reading
throw new IIOException("PSD with ZIP compression not supported");
default:
throw new IIOException(
String.format(
@@ -455,7 +446,7 @@ public final class PSDImageReader extends ImageReaderBase {
private void readImageData(final BufferedImage destination,
final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int[][] byteCounts, final int compression) throws IOException {
final int[] pByteCounts, final int pCompression) throws IOException {
WritableRaster destRaster = destination.getRaster();
ColorModel destCM = destination.getColorModel();
@@ -467,33 +458,31 @@ public final class PSDImageReader extends ImageReaderBase {
int interleavedBands = banded ? 1 : destRaster.getNumBands();
for (int c = 0; c < channels; c++) {
try (ImageInputStream stream = createDecompressorStream(imageInput, compression, header.width, header.bits, byteCounts != null ? byteCounts[c] : null, -1)) {
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read1bitChannel(stream, c, destRaster.getDataBuffer(), row1, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read8bitChannel(stream, c, channels, destRaster.getDataBuffer(), c, interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
read16bitChannel(stream, c, channels, destRaster.getDataBuffer(), c, interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
read32bitChannel(stream, c, channels, destRaster.getDataBuffer(), c, interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height);
break;
default:
throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits));
}
if (abortRequested()) {
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read1bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
break;
}
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read8bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
read16bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
read32bitChannel(c, channels, destRaster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row32, pSource, pDest, pXSub, pYSub, header.width, header.height, pByteCounts, c * header.height, pCompression == PSD.COMPRESSION_RLE);
break;
default:
throw new IIOException(String.format("Unsupported PSD bit depth: %s", header.bits));
}
if (abortRequested()) {
break;
}
}
@@ -540,182 +529,222 @@ public final class PSDImageReader extends ImageReaderBase {
processImageProgress(100f * channel / channelCount + 100f * y / (height * channelCount));
}
private void read32bitChannel(final ImageInputStream stream,
final int channel, final int channelCount,
final DataBuffer data,
final int band, final int bandCount, final int bandOffset,
final ColorModel sourceColorModel,
final int[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
private void read32bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final int[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = sourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && band < colorComponents;
final boolean banded = data.getNumBanks() > 1;
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 4 * pChannelWidth);
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, channelWidth);
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
for (int x = 0; x < pChannelWidth; x++) {
pRow[x] = input.readInt();
}
}
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
}
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - sourceRect.y) / ySub * destRect.width * bandCount + bandOffset;
for (int x = 0; x < destRect.width; x++) {
int value = rowData[sourceRect.x + x * xSub];
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
int value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
if (invert) {
value = 0xffffffff - value;
}
data.setElem(banded ? band : 0, offset + x * bandCount, value);
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
}
}
else {
stream.skipBytes(4 * channelWidth);
imageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(channel, channelCount, y, channelHeight);
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
}
}
private void read16bitChannel(final ImageInputStream stream,
final int channel, final int channelCount,
final DataBuffer data,
final int band, final int bandCount, final int bandOffset,
final ColorModel sourceColorModel,
final short[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
private void read16bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final short[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = sourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && band < colorComponents;
final boolean banded = data.getNumBanks() > 1;
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = (pRLECompressed ? pRowByteCounts[pRowOffset + y] : 2 * pChannelWidth);
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, channelWidth);
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
for (int x = 0; x < pChannelWidth; x++) {
pRow[x] = input.readShort();
}
}
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
}
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - sourceRect.y) / ySub * destRect.width * bandCount + bandOffset;
for (int x = 0; x < destRect.width; x++) {
short value = rowData[sourceRect.x + x * xSub];
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
short value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
if (invert) {
value = (short) (0xffff - value & 0xffff);
}
data.setElem(banded ? band : 0, offset + x * bandCount, value);
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
}
}
else {
stream.skipBytes(2 * channelWidth);
imageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(channel, channelCount, y, channelHeight);
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
}
}
private void read8bitChannel(final ImageInputStream stream,
final int channel, final int channelCount,
final DataBuffer data,
final int band, final int bandCount, final int bandOffset,
final ColorModel sourceColorModel,
final byte[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
private void read8bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final byte[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, final int pRowOffset,
final boolean pRLECompressed) throws IOException {
boolean isCMYK = sourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = sourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && band < colorComponents;
final boolean banded = data.getNumBanks() > 1;
boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
final boolean invert = isCMYK && pChannel < colorComponents;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth;
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, channelWidth);
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
input.readFully(pRow, 0, pChannelWidth);
}
}
else {
imageInput.readFully(pRow, 0, pChannelWidth);
}
// TODO: Destination offset...??
// Copy line sub sampled into real data
int offset = (y - sourceRect.y) / ySub * destRect.width * bandCount + bandOffset;
for (int x = 0; x < destRect.width; x++) {
byte value = rowData[sourceRect.x + x * xSub];
int offset = (y - pSource.y) / pYSub * pDest.width * pBands + pBandOffset;
for (int x = 0; x < pDest.width; x++) {
byte value = pRow[pSource.x + x * pXSub];
// CMYK values are stored inverted, but alpha is not
if (invert) {
value = (byte) (0xff - value & 0xff);
}
data.setElem(banded ? band : 0, offset + x * bandCount, value);
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
}
}
else {
stream.skipBytes(channelWidth);
imageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(channel, channelCount, y, channelHeight);
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
}
}
private void read1bitChannel(final ImageInputStream stream,
final int channel,
final DataBuffer data,
final byte[] rowData,
final Rectangle sourceRect, final Rectangle destRect,
final int xSub, final int ySub,
final int channelWidth, final int channelHeight) throws IOException {
@SuppressWarnings({"UnusedDeclaration"})
private void read1bitChannel(final int pChannel, final int pChannelCount,
final DataBuffer pData, final int pBands, final int pBandOffset,
final ColorModel pSourceColorModel,
final byte[] pRow,
final Rectangle pSource, final Rectangle pDest,
final int pXSub, final int pYSub,
final int pChannelWidth, final int pChannelHeight,
final int[] pRowByteCounts, boolean pRLECompressed) throws IOException {
// NOTE: 1 bit channels only occurs once
if (channel > 0) {
throw new IIOException("Multiple channels not supported for 1 bit data");
}
final int destWidth = (destRect.width + 7) / 8;
final boolean banded = data.getNumBanks() > 1;
final int destWidth = (pDest.width + 7) / 8;
final boolean banded = pData.getNumBanks() > 1;
for (int y = 0; y < pChannelHeight; y++) {
int length = pRLECompressed ? pRowByteCounts[y] : pChannelWidth;
for (int y = 0; y < channelHeight; y++) {
// TODO: Sometimes need to read the line y == source.y + source.height...
// Read entire line, if within source region and sampling
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
stream.readFully(rowData, 0, rowData.length);
if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) {
if (pRLECompressed) {
try (DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length)) {
input.readFully(pRow, 0, pRow.length);
}
}
else {
imageInput.readFully(pRow, 0, pRow.length);
}
// TODO: Destination offset...??
int offset = (y - sourceRect.y) / ySub * destWidth;
if (xSub == 1 && sourceRect.x % 8 == 0) {
int offset = (y - pSource.y) / pYSub * destWidth;
if (pXSub == 1 && pSource.x % 8 == 0) {
// Fast normal case, no sub sampling
for (int i = 0; i < destWidth; i++) {
byte value = rowData[sourceRect.x / 8 + i * xSub];
byte value = pRow[pSource.x / 8 + i * pXSub];
// NOTE: Invert bits to match Java's default monochrome
data.setElem(banded ? channel : 0, offset + i, (byte) (~value & 0xff));
pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~value & 0xff));
}
}
else {
// Copy line sub sampled into real data
final int maxX = sourceRect.x + sourceRect.width;
int x = sourceRect.x;
final int maxX = pSource.x + pSource.width;
int x = pSource.x;
for (int i = 0; i < destWidth; i++) {
byte result = 0;
@@ -727,25 +756,25 @@ public final class PSDImageReader extends ImageReaderBase {
int destBitOff = 7 - j;
// Shift bit into place
result |= ((rowData[bytePos] & mask) >> sourceBitOff) << destBitOff;
result |= ((pRow[bytePos] & mask) >> sourceBitOff) << destBitOff;
x += xSub;
x += pXSub;
}
// NOTE: Invert bits to match Java's default monochrome
data.setElem(banded ? channel : 0, offset + i, (byte) (~result & 0xff));
pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~result & 0xff));
}
}
}
else {
stream.skipBytes((channelWidth + 7) / 8);
imageInput.skipBytes(length);
}
if (abortRequested()) {
break;
}
processImageProgressForChannel(channel, 1, y, channelHeight);
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
}
}
@@ -891,13 +920,13 @@ public final class PSDImageReader extends ImageReaderBase {
// TODO: Flags or list of interesting resources to parse
// TODO: Obey ignoreMetadata
private void readLayerAndMaskInfo(final boolean parseData) throws IOException {
private void readLayerAndMaskInfo(final boolean pParseData) throws IOException {
readImageResources(false);
if (parseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
if (pParseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
imageInput.seek(metadata.layerAndMaskInfoStart);
long layerAndMaskInfoLength = readLength(imageInput);
long layerAndMaskInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
// NOTE: The spec says that if this section is empty, the length should be 0.
// Yet I have a PSB file that has size 12, and both contained lengths set to 0 (which
@@ -907,7 +936,7 @@ public final class PSDImageReader extends ImageReaderBase {
if (layerAndMaskInfoLength > 0) {
long pos = imageInput.getStreamPosition();
long layerInfoLength = readLength(imageInput);
long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
if (layerInfoLength > 0) {
// "Layer count. If it is a negative number, its absolute value is the number of
@@ -916,7 +945,7 @@ public final class PSDImageReader extends ImageReaderBase {
int layerCount = imageInput.readShort();
metadata.layerCount = layerCount;
if (metadata.layerInfo == null) {
if (pParseData && metadata.layerInfo == null) {
metadata.layerInfo = readLayerInfo(Math.abs(layerCount));
metadata.layersStart = imageInput.getStreamPosition();
}
@@ -926,13 +955,16 @@ public final class PSDImageReader extends ImageReaderBase {
imageInput.skipBytes(diff);
}
else {
metadata.layerInfo = Collections.emptyList();
}
// Global LayerMaskInfo (18 bytes or more..?)
// 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad)
long globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB!
if (globalLayerMaskInfoLength > 0) {
if (parseData && metadata.globalLayerMask == null) {
if (pParseData && metadata.globalLayerMask == null) {
metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength);
}
// TODO: Else skip?
@@ -941,52 +973,13 @@ public final class PSDImageReader extends ImageReaderBase {
metadata.globalLayerMask = PSDGlobalLayerMask.NULL_MASK;
}
if (metadata.layerInfo == null) {
while (imageInput.getStreamPosition() + 12 < metadata.layerAndMaskInfoStart + layerAndMaskInfoLength) {
int resSig = imageInput.readInt();
if (resSig != PSD.RESOURCE_TYPE) {
processWarningOccurred(String.format("Bad resource alignment, expected: '8BIM' was '%s'", PSDUtil.intToStr(resSig)));
break;
}
int resId = imageInput.readInt();
long resLength = readLength(imageInput, resId); // In this section, resource lengths *vary* based on the resource...
// Calculate next offset, for some reason lengths are padded to 32 bit...
long nextOffset = imageInput.getStreamPosition() + 4 * ((resLength + 3) / 4);
if (DEBUG) {
System.out.println("resId: " + PSDUtil.intToStr(resId));
System.out.println("length = " + resLength);
System.out.printf("nextOffset = %d%n", nextOffset);
}
switch (resId) {
case PSD.Layr:
case PSD.Lr16:
case PSD.Lr32:
short layerCount = imageInput.readShort();
metadata.layerCount = layerCount;
metadata.layerInfo = readLayerInfo(Math.abs(layerCount));
metadata.layersStart = imageInput.getStreamPosition();
break;
default:
}
imageInput.seek(nextOffset);
}
}
if (parseData && metadata.layerInfo == null) {
// We have parsed but didn't find any layers
metadata.layerInfo = Collections.emptyList();
}
// TODO: Parse "Additional layer information"
// TODO: We should now be able to flush input
// imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
if (parseData && DEBUG) {
if (pParseData && DEBUG) {
System.out.println("layerInfo: " + metadata.layerInfo);
System.out.println("globalLayerMask: " + (metadata.globalLayerMask != PSDGlobalLayerMask.NULL_MASK ? metadata.globalLayerMask : null));
}
@@ -996,39 +989,6 @@ public final class PSDImageReader extends ImageReaderBase {
}
}
private long readLength(final ImageInputStream stream) throws IOException {
return header.largeFormat
? stream.readLong()
: stream.readUnsignedInt();
}
private long readLength(final ImageInputStream stream, final int resId) throws IOException {
// Only the following resources use long (64 bit) lengths:
// LMsk, Lr16, Lr32, Layr, Mt16, Mt32, Mtrn, Alph, FMsk, lnk2, FEid, FXid, PxSD
if (header.largeFormat) {
switch (resId) {
case PSD.LMsk:
case PSD.Lr16:
case PSD.Lr32:
case PSD.Layr:
case PSD.Mt16:
case PSD.Mt32:
case PSD.Mtrn:
case PSD.Alph:
case PSD.FMsk:
case PSD.lnk2:
case PSD.FEid:
case PSD.FXid:
case PSD.PxSD:
return stream.readLong();
default:
// Fall through to 32 bit length
}
}
return stream.readUnsignedInt();
}
private List<PSDLayerInfo> readLayerInfo(int layerCount) throws IOException {
PSDLayerInfo[] layerInfos = new PSDLayerInfo[layerCount];
@@ -1092,22 +1052,21 @@ public final class PSDImageReader extends ImageReaderBase {
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
final int interleavedBands = banded ? 1 : raster.getNumBands();
processImageStarted(1 + layerIndex);
// TODO: progress for layers!
// TODO: Consider creating a method in PSDLayerInfo that can tell how many channels we really want to decode
for (int channel = 0; channel < layerInfo.channelInfo.length; channel++) {
PSDChannelInfo channelInfo = layerInfo.channelInfo[channel];
for (PSDChannelInfo channelInfo : layerInfo.channelInfo) {
int compression = imageInput.readUnsignedShort();
// Skip layer if we can't read it
// channelId -1 = transparency mask; -2 = user supplied layer mask, -3 = real user supplied layer mask (when both a user mask and a vector mask are present)
if (channelInfo.channelId < -1) {
processWarningOccurred(String.format("Skipping channel %s (%s)", channelInfo.channelId, channelInfo.channelId >= -3 ? "user supplied layer mask" : "unknown channel data"));
// channelId
// -1 = transparency mask; -2 = user supplied layer mask, -3 = real user supplied layer mask (when both a user mask and a vector mask are present)
if (channelInfo.channelId < -1 || (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions!
imageInput.skipBytes(channelInfo.length - 2);
} else {
// 0 = red, 1 = green, etc -1 = transparency mask
int band = channelInfo.channelId == -1 ? rowRaster.getNumBands() - 1 : channelInfo.channelId;
}
else {
// 0 = red, 1 = green, etc
// -1 = transparency mask; -2 = user supplied layer mask, -3 = real user supplied layer mask (when both a user mask and a vector mask are present)
int c = channelInfo.channelId == -1 ? rowRaster.getNumBands() - 1 : channelInfo.channelId;
// NOTE: For layers, byte counts are written per channel, while for the composite data
// byte counts are written for all channels before the image data.
@@ -1117,51 +1076,52 @@ public final class PSDImageReader extends ImageReaderBase {
// 0: None, 1: PackBits RLE, 2: Zip, 3: Zip w/prediction
switch (compression) {
case PSD.COMPRESSION_NONE:
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
break;
case PSD.COMPRESSION_RLE:
// If RLE, the image data starts with the byte counts
// If RLE, the the image data starts with the byte counts
// for all the scan lines in the channel (LayerBottom-LayerTop), with
// each count stored as a two-byte (four for PSB) value.
// each count stored as a two*byte (four for PSB) value.
byteCounts = new int[layerInfo.bottom - layerInfo.top];
for (int i = 0; i < byteCounts.length; i++) {
byteCounts[i] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
}
break;
case PSD.COMPRESSION_ZIP:
case PSD.COMPRESSION_ZIP_PREDICTION:
default:
// Explicitly skipped above
throw new AssertionError(String.format("Unsupported layer data. Compression: %d", compression));
}
try (ImageInputStream stream = createDecompressorStream(imageInput, compression, width, header.bits, byteCounts, channelInfo.length - 2)) {
int bandOffset = banded ? 0 : interleavedBands - 1 - band;
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read1bitChannel(stream, channel, raster.getDataBuffer(), row1, area, area, xsub, ysub, width, height);
break;
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read8bitChannel(stream, channel, imageType.getNumBands(), raster.getDataBuffer(), band, interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
read16bitChannel(stream, channel, imageType.getNumBands(), raster.getDataBuffer(), band, interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
read32bitChannel(stream, channel, imageType.getNumBands(), raster.getDataBuffer(), band, interleavedBands, bandOffset, sourceCM, row32, area, area, xsub, ysub, width, height);
break;
default:
throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits));
}
if (abortRequested()) {
switch (header.bits) {
case 1:
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read1bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE);
break;
}
case 8:
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub,
ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
break;
case 16:
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub,
ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
break;
case 32:
int[] row32 = ((DataBufferInt) rowRaster.getDataBuffer()).getData();
read32bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row32, area, area, xsub,
ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
break;
default:
throw new IIOException(String.format("Unknown PSD bit depth: %s", header.bits));
}
if (abortRequested()) {
break;
}
}
}
@@ -1170,8 +1130,6 @@ public final class PSDImageReader extends ImageReaderBase {
convertToDestinationCS(sourceCM, destCM, raster);
}
processImageComplete();
return layer;
}
@@ -1219,7 +1177,6 @@ public final class PSDImageReader extends ImageReaderBase {
// But that makes no sense for a format (like PSD) that does not need to search, right?
readLayerAndMaskInfo(false);
// TODO: Do we really want to include the layers that doesn't have pixel data?
return metadata.getLayerCount() + 1; // TODO: Only plus one, if "has real merged data"?
}
@@ -1354,43 +1311,38 @@ public final class PSDImageReader extends ImageReaderBase {
int idx = 0;
while (pArgs[idx].charAt(0) == '-') {
switch (pArgs[idx]) {
case "-s":
case "--subsampling":
subsampleFactor = Integer.parseInt(pArgs[++idx]);
break;
case "-r":
case "--sourceregion":
int xw = Integer.parseInt(pArgs[++idx]);
int yh = Integer.parseInt(pArgs[++idx]);
if (pArgs[idx].equals("-s") || pArgs[idx].equals("--subsampling")) {
subsampleFactor = Integer.parseInt(pArgs[++idx]);
}
else if (pArgs[idx].equals("-r") || pArgs[idx].equals("--sourceregion")) {
int xw = Integer.parseInt(pArgs[++idx]);
int yh = Integer.parseInt(pArgs[++idx]);
try {
int w = Integer.parseInt(pArgs[idx + 1]);
int h = Integer.parseInt(pArgs[idx + 2]);
try {
int w = Integer.parseInt(pArgs[idx + 1]);
int h = Integer.parseInt(pArgs[idx + 2]);
idx += 2;
idx += 2;
// x y w h
sourceRegion = new Rectangle(xw, yh, w, h);
}
catch (NumberFormatException e) {
// w h
sourceRegion = new Rectangle(xw, yh);
}
// x y w h
sourceRegion = new Rectangle(xw, yh, w, h);
}
catch (NumberFormatException e) {
// w h
sourceRegion = new Rectangle(xw, yh);
}
System.out.println("sourceRegion: " + sourceRegion);
break;
case "-l":
case "--layers":
readLayers = true;
break;
case "-t":
case "--thumbnails":
readThumbnails = true;
break;
default:
System.err.println("Usage: java PSDImageReader [-s <subsample factor>] [-r [<x y>] <w h>] [-t -l] <image file>");
System.exit(1);
System.out.println("sourceRegion: " + sourceRegion);
}
else if (pArgs[idx].equals("-l") || pArgs[idx].equals("--layers")) {
readLayers = true;
}
else if (pArgs[idx].equals("-t") || pArgs[idx].equals("--thumbnails")) {
readThumbnails = true;
}
else {
System.err.println("Usage: java PSDImageReader [-s <subsample factor>] [-r [<x y>] <w h>] <image file>");
System.exit(1);
}
idx++;
@@ -41,7 +41,7 @@ import com.twelvemonkeys.util.FilterIterator;
import org.w3c.dom.Node;
import javax.imageio.metadata.IIOMetadataNode;
import java.awt.image.*;
import java.awt.image.IndexColorModel;
import java.io.IOException;
import java.util.Arrays;
import java.util.Iterator;
@@ -101,8 +101,6 @@ public final class PSDMetadata extends AbstractMetadata {
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
}
// TODO: Allow creating correct metadata for layers too!
/// Native format support
@Override
@@ -150,7 +148,7 @@ public final class PSDMetadata extends AbstractMetadata {
for (PSDImageResource imageResource : imageResources) {
// TODO: Always add name (if set) and id (as resourceId) to all nodes?
// Resource Id is useful for people with access to the PSD spec...
// Resource Id is useful for people with access to the PSD spec..
if (imageResource instanceof ICCProfile) {
ICCProfile profile = (ICCProfile) imageResource;
@@ -677,13 +675,6 @@ public final class PSDMetadata extends AbstractMetadata {
formatVersion.setAttribute("value", header.largeFormat ? "2" : "1"); // PSD format version is always 1, PSB is 2
document_node.appendChild(formatVersion);
// TODO: For images other than image 0
// IIOMetadataNode subimageInterpretation = new IIOMetadataNode("SubimageInterpretation");
// subimageInterpretation.setAttribute("value", "CompositingLayer");
// document_node.appendChild(subimageInterpretation);
// TODO: Layer name?
// Get EXIF data if present
Iterator<PSDEXIF1Data> exif = getResources(PSDEXIF1Data.class);
if (exif.hasNext()) {
@@ -34,7 +34,7 @@ import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.color.ColorSpace;
import java.awt.image.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@@ -77,7 +77,7 @@ final class PSDThumbnail extends PSDImageResource {
// This data isn't really useful, unless we're dealing with raw bytes
widthBytes = pInput.readInt();
int totalSize = pInput.readInt(); // Hmm... Is this really useful at all?
int totalSize = pInput.readInt(); // Hmm.. Is this really useful at all?
// Consistency check
int sizeCompressed = pInput.readInt();
@@ -30,22 +30,16 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
import com.twelvemonkeys.imageio.util.IIOUtil;
import com.twelvemonkeys.io.enc.DecoderStream;
import com.twelvemonkeys.io.enc.PackBitsDecoder;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.stream.ImageInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.zip.InflaterInputStream;
import static com.twelvemonkeys.imageio.util.IIOUtil.createStreamAdapter;
import static java.nio.ByteOrder.BIG_ENDIAN;
import java.util.zip.ZipInputStream;
/**
* PSDUtil
@@ -95,49 +89,19 @@ final class PSDUtil {
return StringUtil.decode(bytes, 0, bytes.length, "UTF-16");
}
static DataInputStream createPackBitsStream(final ImageInputStream pInput, long pLength) {
return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new PackBitsDecoder()));
}
static DataInputStream createZipStream(final ImageInputStream pInput, long pLength) {
return new DataInputStream(new ZipInputStream(IIOUtil.createStreamAdapter(pInput, pLength)));
}
static DataInputStream createZipPredictorStream(final ImageInputStream pInput, long pLength) {
throw new UnsupportedOperationException("Method createZipPredictonStream not implemented");
}
public static float fixedPointToFloat(int pFP) {
return ((pFP & 0xffff0000) >> 16) + (pFP & 0xffff) / (float) 0xffff;
}
static ImageInputStream createDecompressorStream(final ImageInputStream stream, int compression, int columns, int bitsPerSample,
final int[] byteCounts, long compressedLength) throws IOException {
switch (compression) {
case PSD.COMPRESSION_NONE:
return new SubImageInputStream(stream, stream.length());
case PSD.COMPRESSION_RLE:
return new DirectImageInputStream(new SequenceInputStream(new LazyPackBitsStreamEnumeration(byteCounts, stream)));
case PSD.COMPRESSION_ZIP:
return new DirectImageInputStream(new InflaterInputStream(createStreamAdapter(stream, compressedLength)));
case PSD.COMPRESSION_ZIP_PREDICTION:
return new DirectImageInputStream(new HorizontalDeDifferencingStream(new InflaterInputStream(createStreamAdapter(stream, compressedLength)), columns, 1, bitsPerSample, BIG_ENDIAN));
default:
}
throw new IllegalArgumentException("Unknown PSD compression: " + compression);
}
private static class LazyPackBitsStreamEnumeration implements Enumeration<InputStream> {
private final ImageInputStream stream;
private final int[] byteCounts;
private int index;
public LazyPackBitsStreamEnumeration(int[] byteCounts, ImageInputStream stream) {
this.byteCounts = byteCounts;
this.stream = stream;
}
@Override
public boolean hasMoreElements() {
return index < byteCounts.length;
}
@Override
public InputStream nextElement() {
return new DecoderStream(createStreamAdapter(stream, byteCounts[index++]), new PackBitsDecoder());
}
}
}
@@ -1,579 +0,0 @@
/*
* Copyright (c) 2013, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.io.FastByteArrayOutputStream;
import com.twelvemonkeys.io.LittleEndianDataInputStream;
import com.twelvemonkeys.io.LittleEndianDataOutputStream;
import org.junit.Test;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
/**
* HorizontalDeDifferencingStreamTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: HorizontalDeDifferencingStreamTest.java,v 1.0 13.03.13 12:46 haraldk Exp$
*/
public class HorizontalDeDifferencingStreamTest {
@Test
public void testRead1SPP1BPS() throws IOException {
// 1 sample per pixel, 1 bits per sample (mono/indexed)
byte[] data = {
(byte) 0x80, 0x00, 0x00,
0x71, 0x11, 0x44,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 24, 1, 1, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x5e, stream.read());
assertEquals(0x1e, stream.read());
assertEquals(0x78, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP2BPS() throws IOException {
// 1 sample per pixel, 2 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xc0, 0x00, 0x00, 0x00,
0x71, 0x11, 0x44, (byte) 0xcc,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 16, 1, 2, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x41, stream.read());
assertEquals(0x6b, stream.read());
assertEquals(0x05, stream.read());
assertEquals(0x0f, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP4BPS() throws IOException {
// 1 sample per pixel, 4 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xf0, 0x00, 0x00, 0x00,
0x70, 0x11, 0x44, (byte) 0xcc,
0x00, 0x01, 0x10, (byte) 0xe0
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 8, 1, 4, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x77, stream.read());
assertEquals(0x89, stream.read());
assertEquals(0xd1, stream.read());
assertEquals(0xd9, stream.read());
// Row 3
assertEquals(0x00, stream.read());
assertEquals(0x01, stream.read());
assertEquals(0x22, stream.read());
assertEquals(0x00, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP8BPS() throws IOException {
// 1 sample per pixel, 8 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xff, 0, 0, 0,
0x7f, 1, 4, -4,
0x00, 127, 127, -127
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x7f, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x80, stream.read());
// Row 3
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0x7f, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testReadArray1SPP8BPS() throws IOException {
// 1 sample per pixel, 8 bits per sample (gray/indexed)
byte[] data = {
(byte) 0xff, 0, 0, 0,
0x7f, 1, 4, -4,
0x00, 127, 127, -127
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 1, 8, ByteOrder.BIG_ENDIAN);
byte[] result = new byte[data.length];
new DataInputStream(stream).readFully(result);
assertArrayEquals(
new byte[] {
(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
0x7f, (byte) 0x80, (byte) 0x84, (byte) 0x80,
0x00, 0x7f, (byte) 0xfe, 0x7f,
},
result
);
// EOF
assertEquals(-1, stream.read(new byte[16]));
assertEquals(-1, stream.read());
}
@Test
public void testRead1SPP32BPS() throws IOException {
// 1 sample per pixel, 32 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(16);
DataOutput dataOut = new DataOutputStream(out);
dataOut.writeInt(0x00000000);
dataOut.writeInt(305419896);
dataOut.writeInt(305419896);
dataOut.writeInt(-610839792);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.BIG_ENDIAN);
DataInput dataIn = new DataInputStream(in);
// Row 1
assertEquals(0, dataIn.readInt());
assertEquals(305419896, dataIn.readInt());
assertEquals(610839792, dataIn.readInt());
assertEquals(0, dataIn.readInt());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead1SPP32BPSLittleEndian() throws IOException {
// 1 sample per pixel, 32 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(16);
DataOutput dataOut = new LittleEndianDataOutputStream(out);
dataOut.writeInt(0x00000000);
dataOut.writeInt(305419896);
dataOut.writeInt(305419896);
dataOut.writeInt(-610839792);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 32, ByteOrder.LITTLE_ENDIAN);
DataInput dataIn = new LittleEndianDataInputStream(in);
// Row 1
assertEquals(0, dataIn.readInt());
assertEquals(305419896, dataIn.readInt());
assertEquals(610839792, dataIn.readInt());
assertEquals(0, dataIn.readInt());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead1SPP64BPS() throws IOException {
// 1 sample per pixel, 64 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(32);
DataOutput dataOut = new DataOutputStream(out);
dataOut.writeLong(0x00000000);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(-163971058432973790L);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.BIG_ENDIAN);
DataInput dataIn = new DataInputStream(in);
// Row 1
assertEquals(0, dataIn.readLong());
assertEquals(81985529216486895L, dataIn.readLong());
assertEquals(163971058432973790L, dataIn.readLong());
assertEquals(0, dataIn.readLong());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead1SPP64BPSLittleEndian() throws IOException {
// 1 sample per pixel, 64 bits per sample (gray)
FastByteArrayOutputStream out = new FastByteArrayOutputStream(32);
DataOutput dataOut = new LittleEndianDataOutputStream(out);
dataOut.writeLong(0x00000000);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(81985529216486895L);
dataOut.writeLong(-163971058432973790L);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 1, 64, ByteOrder.LITTLE_ENDIAN);
DataInput dataIn = new LittleEndianDataInputStream(in);
// Row 1
assertEquals(0, dataIn.readLong());
assertEquals(81985529216486895L, dataIn.readLong());
assertEquals(163971058432973790L, dataIn.readLong());
assertEquals(0, dataIn.readLong());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead3SPP8BPS() throws IOException {
// 3 samples per pixel, 8 bits per sample (RGB)
byte[] data = {
(byte) 0xff, (byte) 0x00, (byte) 0x7f, -1, -1, -1, -4, -4, -4, 4, 4, 4,
0x7f, 0x7f, 0x7f, 1, 1, 1, 4, 4, 4, -4, -4, -4,
0x00, 0x00, 0x00, 127, -127, 0, -127, 127, 0, 0, 0, 127,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 3, 8, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
assertEquals(0xfa, stream.read());
assertEquals(0xfb, stream.read());
assertEquals(0x7a, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
// Row 2
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
// Row 3
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x81, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testRead3SPP16BPS() throws IOException {
FastByteArrayOutputStream out = new FastByteArrayOutputStream(24);
DataOutput dataOut = new DataOutputStream(out);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(-9320);
dataOut.writeShort(-60584);
dataOut.writeShort(-9320);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.BIG_ENDIAN);
DataInput dataIn = new DataInputStream(in);
// Row 1
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// Row 2
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead3SPP16BPSLittleEndian() throws IOException {
FastByteArrayOutputStream out = new FastByteArrayOutputStream(24);
DataOutput dataOut = new LittleEndianDataOutputStream(out);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(4660);
dataOut.writeShort(30292);
dataOut.writeShort(4660);
dataOut.writeShort(-9320);
dataOut.writeShort(-60584);
dataOut.writeShort(-9320);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(0x0000);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(30292);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
dataOut.writeShort(-60584);
InputStream in = new HorizontalDeDifferencingStream(out.createInputStream(), 4, 3, 16, ByteOrder.LITTLE_ENDIAN);
DataInput dataIn = new LittleEndianDataInputStream(in);
// Row 1
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(4660, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(9320, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// Row 2
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(30292, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(60584, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
assertEquals(0, dataIn.readUnsignedShort());
// EOF
assertEquals(-1, in.read());
}
@Test
public void testRead4SPP8BPS() throws IOException {
// 4 samples per pixel, 8 bits per sample (RGBA)
byte[] data = {
(byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN);
// Row 1
assertEquals(0xff, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x00, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0xfa, stream.read());
assertEquals(0xfb, stream.read());
assertEquals(0x7a, stream.read());
assertEquals(0xfb, stream.read());
assertEquals(0xfe, stream.read());
assertEquals(0xff, stream.read());
assertEquals(0x7e, stream.read());
assertEquals(0xff, stream.read());
// Row 2
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x7f, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x84, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
assertEquals(0x80, stream.read());
// EOF
assertEquals(-1, stream.read());
}
@Test
public void testReadArray4SPP8BPS() throws IOException {
// 4 samples per pixel, 8 bits per sample (RGBA)
byte[] data = {
(byte) 0xff, (byte) 0x00, (byte) 0x7f, 0x00, -1, -1, -1, -1, -4, -4, -4, -4, 4, 4, 4, 4,
0x7f, 0x7f, 0x7f, 0x7f, 1, 1, 1, 1, 4, 4, 4, 4, -4, -4, -4, -4,
};
InputStream stream = new HorizontalDeDifferencingStream(new ByteArrayInputStream(data), 4, 4, 8, ByteOrder.BIG_ENDIAN);
byte[] result = new byte[data.length];
new DataInputStream(stream).readFully(result);
assertArrayEquals(
new byte[] {
(byte) 0xff, 0x00, 0x7f, 0x00,
(byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
(byte) 0xfa, (byte) 0xfb, 0x7a, (byte) 0xfb,
(byte) 0xfe, (byte) 0xff, 0x7e, (byte) 0xff,
0x7f, 0x7f, 0x7f, 0x7f,
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
(byte) 0x84, (byte) 0x84, (byte) 0x84, (byte) 0x84,
(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
},
result
);
// EOF
assertEquals(-1, stream.read(new byte[16]));
assertEquals(-1, stream.read());
}
}
@@ -45,14 +45,10 @@ import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import static org.junit.Assert.*;
@@ -622,102 +618,4 @@ public class PSDImageReaderTest extends ImageReaderAbstractTest<PSDImageReader>
assertFalse(layer1.isDivider);
}
}
@Test
public void test16bitLr16AndZIPPredictor() throws IOException {
PSDImageReader imageReader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psd/fruit-cmyk-MeSa-resource.psd"))) {
imageReader.setInput(stream);
assertEquals(5, imageReader.getNumImages(true));
assertEquals(400, imageReader.getWidth(2));
assertEquals(191, imageReader.getHeight(2));
BufferedImage layer2 = imageReader.read(2);// Read the 16 bit ZIP Predictor based layer
assertNotNull(layer2);
assertEquals(400, layer2.getWidth());
assertEquals(191, layer2.getHeight());
assertEquals(ColorSpace.TYPE_CMYK, layer2.getColorModel().getColorSpace().getType());
assertEquals(5, layer2.getColorModel().getNumComponents());
// For cross-platform testing: as the PSD does not have embedded CMYK profile, we'll use built-in RGB conversion
ColorModel cmykAlpha = new ComponentColorModel(new FakeCMYKColorSpace(), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
layer2 = new BufferedImage(cmykAlpha, layer2.getRaster(), cmykAlpha.isAlphaPremultiplied(), null);
assertRGBEquals("RGB differ at (0,0)", 0xff060808, layer2.getRGB(0, 0), 4);
assertRGBEquals("RGB differ at (399,0)", 0xff060808, layer2.getRGB(399, 0), 4);
assertRGBEquals("RGB differ at (200,95)", 0x00ffffff, layer2.getRGB(200, 95), 4); // Transparent
assertRGBEquals("RGB differ at (0,191)", 0xff060808, layer2.getRGB(0, 190), 4);
assertRGBEquals("RGB differ at (399,191)", 0xff060808, layer2.getRGB(399, 190), 4);
assertEquals(400, imageReader.getWidth(3));
assertEquals(191, imageReader.getHeight(3));
BufferedImage layer3 = imageReader.read(3);// Read the 16 bit ZIP Predictor based layer
assertNotNull(layer3);
assertEquals(400, layer3.getWidth());
assertEquals(191, layer3.getHeight());
assertEquals(ColorSpace.TYPE_CMYK, layer3.getColorModel().getColorSpace().getType());
assertEquals(5, layer3.getColorModel().getNumComponents());
// For cross-platform testing: as the PSD does not have embedded CMYK profile, we'll use built-in RGB conversion
layer3 = new BufferedImage(cmykAlpha, layer3.getRaster(), cmykAlpha.isAlphaPremultiplied(), null);
assertRGBEquals("RGB differ at (0,0)", 0xfff5cb0c, layer3.getRGB(0, 0), 4);
assertRGBEquals("RGB differ at (399,0)", 0xfff5cb0c, layer3.getRGB(399, 0), 4);
assertRGBEquals("RGB differ at (200,95)", 0xffff152a, layer3.getRGB(200, 95), 4); // Red
assertRGBEquals("RGB differ at (0,191)", 0xfff5cb0c, layer3.getRGB(0, 190), 4);
assertRGBEquals("RGB differ at (399,191)", 0xfff5cb0c, layer3.getRGB(399, 190), 4);
}
}
@Test
public void test32bitLr32AndZIPPredictor() throws IOException {
PSDImageReader imageReader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/psd/32bit5x5.psd"))) {
imageReader.setInput(stream);
assertEquals(4, imageReader.getNumImages(true));
assertEquals(5, imageReader.getWidth(1));
assertEquals(5, imageReader.getHeight(1));
BufferedImage image = imageReader.read(1);// Read the 32 bit ZIP Predictor based layer
assertNotNull(image);
assertEquals(5, image.getWidth());
assertEquals(5, image.getHeight());
assertRGBEquals("RGB differ at (0,0)", 0xff888888, image.getRGB(0, 0), 4);
assertRGBEquals("RGB differ at (4,4)", 0xff888888, image.getRGB(4, 4), 4);
}
}
final static class FakeCMYKColorSpace extends ColorSpace {
FakeCMYKColorSpace() {
super(ColorSpace.TYPE_CMYK, 4);
}
public float[] toRGB(float[] cmyk) {
return new float[] {
(1 - cmyk[0]) * (1 - cmyk[3]),
(1 - cmyk[1]) * (1 - cmyk[3]),
(1 - cmyk[2]) * (1 - cmyk[3])
};
}
public float[] fromRGB(float[] rgb) {
throw new UnsupportedOperationException();
}
public float[] toCIEXYZ(float[] cmyk) {
throw new UnsupportedOperationException();
}
public float[] fromCIEXYZ(float[] cieXYZ) {
throw new UnsupportedOperationException();
}
}
}
@@ -1,303 +0,0 @@
/*
* Copyright (c) 2022, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* * Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import org.junit.Test;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
import static org.junit.Assert.assertEquals;
public class PSDUtilDecompressorStreamTest {
@Test
public void testUncompressed() throws IOException {
// Data represents 3 x 3 raster with 8 bit samples, all 0x7f's
byte[] data = new byte[] {
0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f,
0x7f, 0x7f, 0x7f
};
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(data), PSD.COMPRESSION_NONE, 3, 8, null, 9)) {
byte[] row = new byte[3];
for (int y = 0; y < 3; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 0x7f, b);
}
}
assertEquals(-1, input.read());
}
}
@Test
public void testPackBits() throws IOException {
// Data represents 3 x 3 raster with 8 bit samples, all 42's
byte[] packBitsData = {
-2, 42, // 3 byte run
2, 42, 42, 42, // 3 byte literal
0, 42, -1, 42 // 1 byte literal + 2 byte run
};
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(packBitsData), PSD.COMPRESSION_RLE, 3, 8, new int[] {2, 4, 4}, packBitsData.length)) {
byte[] row = new byte[3];
for (int y = 0; y < 3; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 42, b);
}
}
assertEquals(-1, input.read());
}
}
@Test
public void testZIP() throws IOException {
// Data represents 710 x 512 raster with 16 bit samples, first two 0xFF samples, then all 0x00's
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(ZIP_DATA), PSD.COMPRESSION_ZIP, 710, 16, null, ZIP_DATA.length)) {
byte[] row = new byte[710 * 2];
for (int y = 0; y < 512; y++) {
input.readFully(row);
for (int i = 0; i < 2; i++) {
assertEquals((byte) 0xff, row[i]);
}
for (int i = 2; i < row.length; i++) {
assertEquals((byte) 0x00, row[i]);
}
}
assertEquals(-1, input.read());
}
}
@Test
public void testZIPPredictor() throws IOException {
// Data represents 710 x 512 raster with 16 bit samples, all 0xFF's
try (ImageInputStream input = createDecompressorStream(new ByteArrayImageInputStream(ZIP_DATA), PSD.COMPRESSION_ZIP_PREDICTION, 710, 16, null, ZIP_DATA.length)) {
byte[] row = new byte[710 * 2];
for (int y = 0; y < 512; y++) {
input.readFully(row);
for (byte b : row) {
assertEquals((byte) 0xff, b);
}
}
assertEquals(-1, input.read());
}
}
private static final byte[] ZIP_DATA = new byte[] {
(byte) 0x48, (byte) 0x89, (byte) 0xEC, (byte) 0xD4, (byte) 0x31, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x0C, (byte) 0xC3,
(byte) 0xA0, (byte) 0xF9, (byte) 0x37, (byte) 0xDD, (byte) 0xC9, (byte) 0xC8, (byte) 0x03, (byte) 0x22, (byte) 0xD8, (byte) 0x0E,
(byte) 0x80, (byte) 0xD8, (byte) 0x5C, (byte) 0x0C, (byte) 0x90, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0,
(byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4,
(byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D,
(byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF,
(byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73,
(byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C,
(byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17,
(byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5,
(byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31,
(byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C,
(byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0x03,
(byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40, (byte) 0xCF, (byte) 0xC5, (byte) 0x00,
(byte) 0x3D, (byte) 0x17, (byte) 0x03, (byte) 0xF4, (byte) 0x5C, (byte) 0x0C, (byte) 0xD0, (byte) 0x73, (byte) 0x31, (byte) 0x40,
(byte) 0xCF, (byte) 0xC5, (byte) 0x00, (byte) 0x3D, (byte) 0x17, (byte) 0xC3, (byte) 0xB3, (byte) 0x53, (byte) 0xC7, (byte) 0x02,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x83, (byte) 0xFC, (byte) 0xAD, (byte) 0x87, (byte) 0xB1, (byte) 0xA7,
(byte) 0x20, (byte) 0x82, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62,
(byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18,
(byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06,
(byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01,
(byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80,
(byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0,
(byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8,
(byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E,
(byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F,
(byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7,
(byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9,
(byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B, (byte) 0x01, (byte) 0x7E, (byte) 0x2E,
(byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0xE7, (byte) 0x62, (byte) 0x80, (byte) 0x9F, (byte) 0x8B,
(byte) 0x01, (byte) 0x7E, (byte) 0x2E, (byte) 0x06, (byte) 0xF8, (byte) 0xB9, (byte) 0x18, (byte) 0xE0, (byte) 0x97, (byte) 0x00,
(byte) 0x03, (byte) 0x00, (byte) 0x3E, (byte) 0xEE, (byte) 0xFC, (byte) 0x2E
};
}

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