mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 488d6da71a | |||
| b19e45a911 | |||
| a5b6cf898d | |||
| ce597c437d | |||
| fa4b3787d0 | |||
| 3c18e8a510 | |||
| 206481038e | |||
| cff4d88991 | |||
| 8ea8e061a9 | |||
| 101ad18f71 | |||
| 08b441a17e | |||
| b6c76d8566 | |||
| 3f74b2ddf3 | |||
| 46b48f32c3 | |||
| a07d0285fe | |||
| 98de4ad4ec | |||
| aa82612765 | |||
| 9213da3184 | |||
| a5e2226a5a | |||
| 773bedccca | |||
| 6bcc17a020 | |||
| 37d1da9b9d | |||
| 8cf1405dfc | |||
| 8c37d19928 | |||
| 87cd506fdd | |||
| e0c7edebbd | |||
| 5d13bd653f | |||
| 2d974874a9 | |||
| f625622b10 | |||
| dbdd7ae3f1 | |||
| 73883ebf99 | |||
| 970b238066 | |||
| 6cb8ac4b68 | |||
| 1a2a4edfe8 | |||
| a12a1f73b5 | |||
| 46bfdd93d8 |
@@ -7,14 +7,14 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem or use case is.
|
||||
**Is your feature request related to a use case or a problem you are working on? Please describe.**
|
||||
A clear and concise description of what the problem or use case is. Understanding the rationale is key, to be able to implemeent the right solution.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
A clear and concise description of any alternative solutions or features you've already considered, and why they won't work.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here, like links to specifications or sample files.
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>com.twelvemonkeys.bom</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-image</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
@@ -31,7 +31,7 @@
|
||||
<dependency>
|
||||
<groupId>jmagick</groupId>
|
||||
<artifactId>jmagick</artifactId>
|
||||
<version>6.2.4</version>
|
||||
<version>6.6.9</version>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-io</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
@@ -34,6 +34,7 @@ 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
|
||||
@@ -42,11 +43,8 @@ import java.io.OutputStream;
|
||||
* @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.
|
||||
@@ -97,10 +95,8 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
|
||||
private void growIfNeeded(int pNewCount) {
|
||||
if (pNewCount > buf.length) {
|
||||
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;
|
||||
int newSize = Math.max(buf.length << 1, pNewCount);
|
||||
buf = Arrays.copyOf(buf, newSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,10 +109,7 @@ public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
// Non-synchronized version of toByteArray
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
byte[] newBuf = new byte[count];
|
||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||
|
||||
return newBuf;
|
||||
return Arrays.copyOf(buf, count);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
protected final ByteBuffer buffer;
|
||||
protected final Decoder decoder;
|
||||
private final ByteBuffer buffer;
|
||||
private final Decoder decoder;
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
* input stream specified by the {@code stream} argument.
|
||||
* The stream will use a default decode buffer size.
|
||||
*
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
* @param stream the underlying input stream.
|
||||
* @param decoder the decoder that will be used to decode the underlying stream
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
||||
public DecoderStream(final InputStream stream, final Decoder decoder) {
|
||||
// TODO: Let the decoder decide preferred buffer size
|
||||
this(pStream, pDecoder, 1024);
|
||||
this(stream, decoder, 1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
* input stream specified by the {@code stream} argument.
|
||||
*
|
||||
* @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
|
||||
* @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
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
||||
super(pStream);
|
||||
public DecoderStream(final InputStream stream, final Decoder decoder, final int bufferSize) {
|
||||
super(stream);
|
||||
|
||||
decoder = pDecoder;
|
||||
buffer = ByteBuffer.allocate(pBufferSize);
|
||||
this.decoder = decoder;
|
||||
buffer = ByteBuffer.allocate(bufferSize);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
@@ -95,15 +95,15 @@ public final class DecoderStream extends FilterInputStream {
|
||||
return buffer.get() & 0xff;
|
||||
}
|
||||
|
||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
||||
if (pBytes == null) {
|
||||
public int read(final byte[] bytes, final int offset, final int length) throws IOException {
|
||||
if (bytes == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
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 ((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 (pLength == 0) {
|
||||
else if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -114,11 +114,11 @@ public final class DecoderStream extends FilterInputStream {
|
||||
}
|
||||
}
|
||||
|
||||
// Read until we have read pLength bytes, or have reached EOF
|
||||
// Read until we have read length bytes, or have reached EOF
|
||||
int count = 0;
|
||||
int off = pOffset;
|
||||
int off = offset;
|
||||
|
||||
while (pLength > count) {
|
||||
while (length > 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(pLength - count, buffer.remaining());
|
||||
buffer.get(pBytes, off, dstLen);
|
||||
int dstLen = Math.min(length - count, buffer.remaining());
|
||||
buffer.get(bytes, off, dstLen);
|
||||
|
||||
// Update offset (rest)
|
||||
off += dstLen;
|
||||
@@ -139,7 +139,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
return count;
|
||||
}
|
||||
|
||||
public long skip(final long pLength) throws IOException {
|
||||
public long skip(final long length) 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 pLength bytes, or have reached EOF
|
||||
// Skip until we have skipped length bytes, or have reached EOF
|
||||
long total = 0;
|
||||
|
||||
while (total < pLength) {
|
||||
while (total < length) {
|
||||
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(pLength - total, buffer.remaining());
|
||||
int skipped = (int) Math.min(length - 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
|
||||
*/
|
||||
protected int fill() throws IOException {
|
||||
private int fill() throws IOException {
|
||||
buffer.clear();
|
||||
int read = decoder.decode(in, buffer);
|
||||
|
||||
|
||||
@@ -45,41 +45,39 @@ 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!!!
|
||||
|
||||
protected final Encoder encoder;
|
||||
private final Encoder encoder;
|
||||
private final boolean flushOnWrite;
|
||||
|
||||
protected final ByteBuffer buffer;
|
||||
private final ByteBuffer buffer;
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param pStream the underlying output stream
|
||||
* @param pEncoder the encoder to use
|
||||
* @param stream the underlying output stream
|
||||
* @param encoder the encoder to use
|
||||
*/
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
|
||||
this(pStream, pEncoder, false);
|
||||
public EncoderStream(final OutputStream stream, final Encoder encoder) {
|
||||
this(stream, encoder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param pStream the underlying output stream
|
||||
* @param pEncoder the encoder to use
|
||||
* @param pFlushOnWrite if {@code true}, calls to the byte-array
|
||||
* @param stream the underlying output stream
|
||||
* @param encoder the encoder to use
|
||||
* @param flushOnWrite if {@code true}, calls to the byte-array
|
||||
* {@code write} methods will automatically flush the buffer.
|
||||
*/
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
||||
super(pStream);
|
||||
public EncoderStream(final OutputStream stream, final Encoder encoder, final boolean flushOnWrite) {
|
||||
super(stream);
|
||||
|
||||
encoder = pEncoder;
|
||||
flushOnWrite = pFlushOnWrite;
|
||||
this.encoder = encoder;
|
||||
this.flushOnWrite = flushOnWrite;
|
||||
|
||||
buffer = ByteBuffer.allocate(1024);
|
||||
buffer.flip();
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
@@ -104,33 +102,33 @@ public final class EncoderStream extends FilterOutputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public final void write(final byte[] pBytes) throws IOException {
|
||||
write(pBytes, 0, pBytes.length);
|
||||
public void write(final byte[] bytes) throws IOException {
|
||||
write(bytes, 0, bytes.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[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (!flushOnWrite && pLength < buffer.remaining()) {
|
||||
// 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()) {
|
||||
// Buffer data
|
||||
buffer.put(pBytes, pOffset, pLength);
|
||||
buffer.put(values, offset, length);
|
||||
}
|
||||
else {
|
||||
// Encode data already in the buffer
|
||||
encodeBuffer();
|
||||
|
||||
// Encode rest without buffering
|
||||
encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
|
||||
encoder.encode(out, ByteBuffer.wrap(values, offset, length));
|
||||
}
|
||||
}
|
||||
|
||||
public void write(final int pByte) throws IOException {
|
||||
public void write(final int value) throws IOException {
|
||||
if (!buffer.hasRemaining()) {
|
||||
encodeBuffer(); // Resets bufferPos to 0
|
||||
}
|
||||
|
||||
buffer.put((byte) pByte);
|
||||
buffer.put((byte) value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.enc;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class EncoderStreamTest {
|
||||
|
||||
private final Random rng = new Random(5467809876546L);
|
||||
|
||||
private byte[] createData(final int length) {
|
||||
byte[] data = new byte[length];
|
||||
rng.nextBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeSingleBytes() throws IOException {
|
||||
byte[] data = createData(1327);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||
for (byte datum : data) {
|
||||
stream.write(datum);
|
||||
}
|
||||
}
|
||||
|
||||
assertArrayEquals(data, result.toByteArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeArray() throws IOException {
|
||||
byte[] data = createData(1793);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
stream.write(data);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] encoded = result.toByteArray();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertArrayEquals(data, Arrays.copyOfRange(encoded, i * data.length, (i + 1) * data.length));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeArrayOffset() throws IOException {
|
||||
byte[] data = createData(87);
|
||||
|
||||
ByteArrayOutputStream result = new ByteArrayOutputStream();
|
||||
try (OutputStream stream = new EncoderStream(result, new NullEncoder())) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
stream.write(data, 13, 59);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] original = Arrays.copyOfRange(data, 13, 13 + 59);
|
||||
byte[] encoded = result.toByteArray();
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
assertArrayEquals(original, Arrays.copyOfRange(encoded, i * original.length, (i + 1) * original.length));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NullEncoder implements Encoder {
|
||||
@Override
|
||||
public void encode(OutputStream stream, ByteBuffer buffer) throws IOException {
|
||||
stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>common-lang</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.common</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<groupId>com.twelvemonkeys.contrib</groupId>
|
||||
<artifactId>contrib</artifactId>
|
||||
|
||||
@@ -30,9 +30,14 @@ 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.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||
* {@code null}.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final URL input) throws IOException {
|
||||
@@ -43,9 +48,14 @@ 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.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||
* {@code null}.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final InputStream input) throws IOException {
|
||||
@@ -56,9 +66,14 @@ 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.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info or
|
||||
* {@code null}.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final File input) throws IOException {
|
||||
@@ -69,9 +84,14 @@ 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.
|
||||
* @return an {@code IIOImage} containing the correctly oriented image and metadata including rotation info, or
|
||||
* {@code null}.
|
||||
* @throws IOException if an error occurs during reading.
|
||||
*/
|
||||
public static IIOImage readWithOrientation(final ImageInputStream input) throws IOException {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-batik</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
|
||||
@@ -27,9 +27,9 @@
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<systemPropertyVariables>
|
||||
<com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
||||
<com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||
true
|
||||
</com.twelvemonkeys.imageio.plugins.svg.allowexternalresources>
|
||||
</com.twelvemonkeys.imageio.plugins.svg.allowExternalResources>
|
||||
</systemPropertyVariables>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
+109
-103
@@ -30,21 +30,10 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
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 com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import org.apache.batik.anim.dom.SVGDOMImplementation;
|
||||
import org.apache.batik.anim.dom.SVGOMDocument;
|
||||
@@ -68,10 +57,19 @@ import org.w3c.dom.DOMImplementation;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.svg.SVGSVGElement;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Image reader for SVG document fragments.
|
||||
@@ -79,12 +77,13 @@ import com.twelvemonkeys.lang.StringUtil;
|
||||
* @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"));
|
||||
"true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowExternalResources",
|
||||
System.getProperty("com.twelvemonkeys.imageio.plugins.svg.allowexternalresources")));
|
||||
|
||||
private Rasterizer rasterizer;
|
||||
private boolean allowExternalResources = DEFAULT_ALLOW_EXTERNAL_RESOURCES;
|
||||
@@ -150,29 +149,23 @@ 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 {
|
||||
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();
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
g.setComposite(AlphaComposite.Src);
|
||||
g.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE);
|
||||
g.drawImage(image, 0, 0, null); // TODO: Dest offset?
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
Throwable cause = unwrapException(e);
|
||||
throw new IIOException(cause.getMessage(), cause);
|
||||
finally {
|
||||
g.dispose();
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return destination;
|
||||
}
|
||||
|
||||
private static Throwable unwrapException(TranscoderException ex) {
|
||||
@@ -187,11 +180,11 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
// Set dimensions
|
||||
Dimension size = pParam.getSourceRenderSize();
|
||||
Dimension origSize = new Dimension(getWidth(0), getHeight(0));
|
||||
Rectangle viewBox = rasterizer.getViewBox();
|
||||
if (size == null) {
|
||||
// SVG is not a pixel based format, but we'll scale it, according to
|
||||
// the subsampling for compatibility
|
||||
size = getSourceRenderSizeFromSubsamping(pParam, origSize);
|
||||
size = getSourceRenderSizeFromSubsamping(pParam, viewBox.getSize());
|
||||
}
|
||||
|
||||
if (size != null) {
|
||||
@@ -211,8 +204,8 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
// Need to resize here...
|
||||
double xScale = size.getWidth() / origSize.getWidth();
|
||||
double yScale = size.getHeight() / origSize.getHeight();
|
||||
double xScale = size.getWidth() / viewBox.getWidth();
|
||||
double yScale = size.getHeight() / viewBox.getHeight();
|
||||
|
||||
hints.put(ImageTranscoder.KEY_WIDTH, (float) (region.getWidth() * xScale));
|
||||
hints.put(ImageTranscoder.KEY_HEIGHT, (float) (region.getHeight() * yScale));
|
||||
@@ -220,7 +213,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
else if (size != null) {
|
||||
// Allow non-uniform scaling
|
||||
hints.put(ImageTranscoder.KEY_AOI, new Rectangle(origSize));
|
||||
hints.put(ImageTranscoder.KEY_AOI, viewBox);
|
||||
}
|
||||
|
||||
// Background color
|
||||
@@ -235,7 +228,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;
|
||||
}
|
||||
@@ -246,22 +239,13 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
public int getWidth(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
try {
|
||||
return rasterizer.getDefaultWidth();
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
|
||||
return rasterizer.getDefaultWidth();
|
||||
}
|
||||
|
||||
public int getHeight(int pIndex) throws IOException {
|
||||
checkBounds(pIndex);
|
||||
try {
|
||||
return rasterizer.getDefaultHeight();
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
return rasterizer.getDefaultHeight();
|
||||
}
|
||||
|
||||
public Iterator<ImageTypeSpecifier> getImageTypes(int imageIndex) {
|
||||
@@ -275,12 +259,11 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
* and needs major refactoring!
|
||||
* </p>
|
||||
*/
|
||||
private class Rasterizer extends SVGAbstractTranscoder /*ImageTranscoder*/ {
|
||||
|
||||
private class Rasterizer extends SVGAbstractTranscoder {
|
||||
private BufferedImage image;
|
||||
private TranscoderInput transcoderInput;
|
||||
private float defaultWidth;
|
||||
private float defaultHeight;
|
||||
private final Rectangle2D viewBox = new Rectangle2D.Float();
|
||||
private final Dimension defaultSize = new Dimension();
|
||||
private boolean initialized = false;
|
||||
private SVGOMDocument document;
|
||||
private String uri;
|
||||
@@ -341,54 +324,66 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
// ----
|
||||
SVGSVGElement rootElement = svgDoc.getRootElement();
|
||||
|
||||
// get the 'width' and 'height' attributes of the SVG document
|
||||
UnitProcessor.Context uctx
|
||||
= UnitProcessor.createContext(ctx, rootElement);
|
||||
// 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);
|
||||
String widthStr = rootElement.getAttributeNS(null, SVGConstants.SVG_WIDTH_ATTRIBUTE);
|
||||
String heightStr = rootElement.getAttributeNS(null, SVGConstants.SVG_HEIGHT_ATTRIBUTE);
|
||||
if (!StringUtil.isEmpty(widthStr)) {
|
||||
defaultWidth = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
||||
width = UnitProcessor.svgToUserSpace(widthStr, SVGConstants.SVG_WIDTH_ATTRIBUTE, UnitProcessor.HORIZONTAL_LENGTH, uctx);
|
||||
}
|
||||
if(!StringUtil.isEmpty(heightStr)){
|
||||
defaultHeight = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
||||
if (!StringUtil.isEmpty(heightStr)) {
|
||||
height = UnitProcessor.svgToUserSpace(heightStr, SVGConstants.SVG_HEIGHT_ATTRIBUTE, UnitProcessor.VERTICAL_LENGTH, uctx);
|
||||
}
|
||||
|
||||
boolean hasWidth = defaultWidth > 0.0;
|
||||
boolean hasHeight = defaultHeight > 0.0;
|
||||
boolean hasWidth = width > 0.0;
|
||||
boolean hasHeight = height > 0.0;
|
||||
|
||||
if (!hasWidth || !hasHeight) {
|
||||
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 (!viewBox.isEmpty()) {
|
||||
// If one dimension is given, calculate other by aspect ratio in viewBox
|
||||
if (hasWidth) {
|
||||
defaultHeight = defaultWidth * rect[3] / rect[2];
|
||||
height = width * viewBox.getHeight() / viewBox.getWidth();
|
||||
}
|
||||
else if (hasHeight) {
|
||||
defaultWidth = defaultHeight * rect[2] / rect[3];
|
||||
width = height * viewBox.getWidth() / viewBox.getHeight();
|
||||
}
|
||||
else {
|
||||
defaultWidth = rect[2];
|
||||
defaultHeight = rect[3];
|
||||
// ...or use viewBox if no dimension is given
|
||||
width = viewBox.getWidth();
|
||||
height = viewBox.getHeight();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// No viewBox, just assume square size
|
||||
if (hasHeight) {
|
||||
defaultWidth = defaultHeight;
|
||||
width = height;
|
||||
}
|
||||
else if (hasWidth) {
|
||||
defaultHeight = defaultWidth;
|
||||
height = width;
|
||||
}
|
||||
else {
|
||||
// fallback to batik default sizes
|
||||
defaultWidth = 400;
|
||||
defaultHeight = 400;
|
||||
// ...or finally fall back to Batik default sizes
|
||||
width = 400;
|
||||
height = 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;
|
||||
@@ -401,7 +396,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
ctx = null;
|
||||
}
|
||||
|
||||
private BufferedImage readImage() throws TranscoderException {
|
||||
private BufferedImage readImage() throws IOException {
|
||||
init();
|
||||
|
||||
if (abortRequested()) {
|
||||
@@ -426,7 +421,8 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
if (gvtRoot == null) {
|
||||
throw exception;
|
||||
Throwable cause = unwrapException(exception);
|
||||
throw new IIOException(cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
ctx = context;
|
||||
@@ -444,7 +440,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
|
||||
// ----
|
||||
setImageSize(defaultWidth, defaultHeight);
|
||||
setImageSize(defaultSize.width, defaultSize.height);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -458,18 +454,17 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
try {
|
||||
Px = ViewBox.getViewTransform(ref, root, width, height, null);
|
||||
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
throw new TranscoderException(ex);
|
||||
throw new IIOException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
if (Px.isIdentity() && (width != defaultWidth || height != defaultHeight)) {
|
||||
if (Px.isIdentity() && (width != defaultSize.width || height != defaultSize.height)) {
|
||||
// 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 / defaultWidth;
|
||||
yscale = height / defaultHeight;
|
||||
xscale = width / defaultSize.width;
|
||||
yscale = height / defaultSize.height;
|
||||
float scale = Math.min(xscale, yscale);
|
||||
Px = AffineTransform.getScaleInstance(scale, scale);
|
||||
}
|
||||
@@ -519,7 +514,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
catch (BridgeException ex) {
|
||||
throw new TranscoderException(ex);
|
||||
throw new IIOException(ex.getMessage(), ex);
|
||||
}
|
||||
|
||||
this.root = gvtRoot;
|
||||
@@ -588,7 +583,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
return dest;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new TranscoderException(ex.getMessage(), ex);
|
||||
throw new IIOException(ex.getMessage(), ex);
|
||||
}
|
||||
finally {
|
||||
if (context != null) {
|
||||
@@ -597,7 +592,7 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void init() throws TranscoderException {
|
||||
private synchronized void init() throws IIOException {
|
||||
if (!initialized) {
|
||||
if (transcoderInput == null) {
|
||||
throw new IllegalStateException("input == null");
|
||||
@@ -605,11 +600,17 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
|
||||
initialized = true;
|
||||
|
||||
super.transcode(transcoderInput, null);
|
||||
try {
|
||||
super.transcode(transcoderInput, null);
|
||||
}
|
||||
catch (TranscoderException e) {
|
||||
Throwable cause = unwrapException(e);
|
||||
throw new IIOException(cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage getImage() throws TranscoderException {
|
||||
private BufferedImage getImage() throws IOException {
|
||||
if (image == null) {
|
||||
image = readImage();
|
||||
}
|
||||
@@ -617,14 +618,19 @@ public class SVGImageReader extends ImageReaderBase {
|
||||
return image;
|
||||
}
|
||||
|
||||
int getDefaultWidth() throws TranscoderException {
|
||||
int getDefaultWidth() throws IOException {
|
||||
init();
|
||||
return (int) Math.ceil(defaultWidth);
|
||||
return defaultSize.width;
|
||||
}
|
||||
|
||||
int getDefaultHeight() throws TranscoderException {
|
||||
int getDefaultHeight() throws IOException {
|
||||
init();
|
||||
return (int) Math.ceil(defaultHeight);
|
||||
return defaultSize.height;
|
||||
}
|
||||
|
||||
Rectangle getViewBox() throws IOException {
|
||||
init();
|
||||
return viewBox.getBounds();
|
||||
}
|
||||
|
||||
void setInput(final TranscoderInput pInput) {
|
||||
|
||||
+143
-141
@@ -1,141 +1,143 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
+72
-5
@@ -43,8 +43,7 @@ import javax.imageio.event.IIOReadWarningListener;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ImagingOpException;
|
||||
import java.awt.image.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
@@ -53,7 +52,10 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
@@ -192,12 +194,12 @@ public class SVGImageReaderTest extends ImageReaderAbstractTest<SVGImageReader>
|
||||
TestData redSquare = new TestData(getClassLoaderResource("/svg/red-square.svg"), dim);
|
||||
reader.setInput(redSquare.getInputStream());
|
||||
BufferedImage imageRed = reader.read(0, param);
|
||||
assertEquals(0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF);
|
||||
assertRGBEquals("Expected all red", 0xFF0000, imageRed.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||
|
||||
TestData blueSquare = new TestData(getClassLoaderResource("/svg/blue-square.svg"), dim);
|
||||
reader.setInput(blueSquare.getInputStream());
|
||||
BufferedImage imageBlue = reader.read(0, param);
|
||||
assertEquals(0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF);
|
||||
assertRGBEquals("Expected all blue", 0x0000FF, imageBlue.getRGB(50, 50) & 0xFFFFFF, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -337,4 +339,69 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 436 B |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-bmp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
|
||||
|
||||
+16
-9
@@ -39,7 +39,11 @@ import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.event.IIOReadUpdateListener;
|
||||
import javax.imageio.event.IIOReadWarningListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
@@ -47,7 +51,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.DataInput;
|
||||
import java.io.File;
|
||||
@@ -77,7 +81,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
super(new BMPImageReaderSpi());
|
||||
}
|
||||
|
||||
protected BMPImageReader(final ImageReaderSpi pProvider) {
|
||||
BMPImageReader(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
@@ -358,14 +362,18 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
|
||||
processImageStarted(imageIndex);
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getBitCount()) {
|
||||
int bitCount = header.getBitCount();
|
||||
switch (bitCount) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
case 8:
|
||||
case 24:
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
readRowByte(input, height, srcRegion, xSub, ySub, rowDataByte, destRaster, clippedRow, y);
|
||||
int bitsPerSample = bitCount == 24 ? 8 : bitCount;
|
||||
int samplesPerPixel = bitCount == 24 ? 3 : 1;
|
||||
|
||||
readRowByte(input, height, srcRegion, xSub, ySub, bitsPerSample, samplesPerPixel, rowDataByte, destRaster, clippedRow, y);
|
||||
break;
|
||||
|
||||
case 16:
|
||||
@@ -379,7 +387,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new AssertionError("Unsupported pixel depth: " + header.getBitCount());
|
||||
throw new AssertionError("Unsupported pixel depth: " + bitCount);
|
||||
}
|
||||
|
||||
processImageProgress(100f * y / height);
|
||||
@@ -476,6 +484,7 @@ 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;
|
||||
@@ -492,9 +501,7 @@ public final class BMPImageReader extends ImageReaderBase {
|
||||
|
||||
// Subsample horizontal
|
||||
if (xSub != 1) {
|
||||
for (int x = 0; x < srcRegion.width / xSub; x++) {
|
||||
rowDataByte[srcRegion.x + x] = rowDataByte[srcRegion.x + x * xSub];
|
||||
}
|
||||
IIOUtil.subsampleRow(rowDataByte, srcRegion.x, srcRegion.width, rowDataByte, 0, samplesPerPixel, bitsPerSample, xSub);
|
||||
}
|
||||
|
||||
destChannel.setDataElements(0, dstY, srcChannel);
|
||||
|
||||
+1
-1
@@ -116,7 +116,7 @@ public final class BMPImageReaderSpi extends ImageReaderSpiBase {
|
||||
}
|
||||
}
|
||||
|
||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
||||
public ImageReader createReaderInstance(final Object pExtension) {
|
||||
return new BMPImageReader(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-clippath</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Core</name>
|
||||
|
||||
+20
-20
@@ -54,8 +54,8 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
// Our IndexColorModel delegate
|
||||
private final IndexColorModel icm;
|
||||
|
||||
private final int extraSamples;
|
||||
private final int samples;
|
||||
private final boolean hasAlpha;
|
||||
|
||||
/**
|
||||
* Creates a {@code DiscreteAlphaIndexColorModel}, delegating color map look-ups
|
||||
@@ -86,33 +86,33 @@ public final class DiscreteAlphaIndexColorModel extends ColorModel {
|
||||
);
|
||||
|
||||
this.icm = icm;
|
||||
this.extraSamples = extraSamples;
|
||||
this.samples = 1 + extraSamples;
|
||||
this.hasAlpha = hasAlpha;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumComponents() {
|
||||
return samples;
|
||||
return getNumColorComponents() + extraSamples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getRed(final int pixel) {
|
||||
public int getRed(final int pixel) {
|
||||
return icm.getRed(pixel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getGreen(final int pixel) {
|
||||
public int getGreen(final int pixel) {
|
||||
return icm.getGreen(pixel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getBlue(final int pixel) {
|
||||
public int getBlue(final int pixel) {
|
||||
return icm.getBlue(pixel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getAlpha(final int pixel) {
|
||||
return hasAlpha ? (int) ((((float) pixel) / ((1 << getComponentSize(3))-1)) * 255.0f + 0.5f) : 0xff;
|
||||
public 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 final int getRed(final Object inData) {
|
||||
public int getRed(final Object inData) {
|
||||
return getRed(getSample(inData, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getGreen(final Object inData) {
|
||||
public int getGreen(final Object inData) {
|
||||
return getGreen(getSample(inData, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getBlue(final Object inData) {
|
||||
public int getBlue(final Object inData) {
|
||||
return getBlue(getSample(inData, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getAlpha(final Object inData) {
|
||||
return hasAlpha ? getAlpha(getSample(inData, 1)) : 0xff;
|
||||
public int getAlpha(final Object inData) {
|
||||
return hasAlpha() ? getAlpha(getSample(inData, 1)) : 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final SampleModel createCompatibleSampleModel(final int w, final int h) {
|
||||
public 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 final boolean isCompatibleSampleModel(final SampleModel sm) {
|
||||
public boolean isCompatibleSampleModel(final SampleModel sm) {
|
||||
return sm instanceof PixelInterleavedSampleModel && sm.getNumBands() == samples;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
||||
public WritableRaster createCompatibleWritableRaster(final int w, final int h) {
|
||||
return Raster.createWritableRaster(createCompatibleSampleModel(w, h), new Point(0, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isCompatibleRaster(final Raster raster) {
|
||||
public boolean isCompatibleRaster(final Raster raster) {
|
||||
int size = raster.getSampleModel().getSampleSize(0);
|
||||
return ((raster.getTransferType() == transferType) &&
|
||||
(raster.getNumBands() == samples) && ((1 << size) >= icm.getMapSize()));
|
||||
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (c) 2022, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice, this
|
||||
* list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of the copyright holder nor the names of its
|
||||
* contributors may be used to endorse or promote products derived from
|
||||
* this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static com.twelvemonkeys.lang.Validate.isTrue;
|
||||
import static com.twelvemonkeys.lang.Validate.notNull;
|
||||
|
||||
/**
|
||||
* An {@code ImageInputStream} that adapts an {@code InputSteam},
|
||||
* by reading directly from the stream without and form of caching or buffering.
|
||||
* <p>
|
||||
* Note: This is <em>not</em> a general-purpose {@code ImageInputStream}, and is designed for reading large chunks,
|
||||
* typically of pixel data, from an {@code InputStream}.
|
||||
* It does <em>not</em> support backwards seeking, or reading bits.
|
||||
* </p>
|
||||
*/
|
||||
public final class DirectImageInputStream extends ImageInputStreamImpl {
|
||||
private final InputStream stream;
|
||||
private final long length;
|
||||
|
||||
public DirectImageInputStream(final InputStream stream) {
|
||||
this(stream, -1L);
|
||||
}
|
||||
|
||||
public DirectImageInputStream(final InputStream stream, long length) {
|
||||
this.stream = notNull(stream, "stream");
|
||||
this.length = isTrue(length >= 0L || length == -1L, length, "negative length: %d");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
bitOffset = 0;
|
||||
streamPos++;
|
||||
return stream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] bytes, int off, int len) throws IOException {
|
||||
bitOffset = 0;
|
||||
|
||||
int read = stream.read(bytes, off, len);
|
||||
if (read > 0) {
|
||||
streamPos += read;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long pos) throws IOException {
|
||||
checkClosed();
|
||||
|
||||
if (pos < streamPos) {
|
||||
// Handle as if flushedPos == streamPos at any time
|
||||
throw new IndexOutOfBoundsException("pos < flushedPos");
|
||||
}
|
||||
|
||||
bitOffset = 0;
|
||||
|
||||
while (streamPos < pos) {
|
||||
long skipped = stream.skip(pos - streamPos);
|
||||
|
||||
if (skipped <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
streamPos += skipped;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFlushedPosition() {
|
||||
// Handle as if flushedPos == streamPos at any time
|
||||
return streamPos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return length;
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Override
|
||||
public int readBit() throws IOException {
|
||||
throw new UnsupportedOperationException("Bit reading not supported");
|
||||
}
|
||||
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
@Override
|
||||
public long readBits(int numBits) throws IOException {
|
||||
throw new UnsupportedOperationException("Bit reading not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// We could seek to EOF here, but the usual case
|
||||
|
||||
stream.close();
|
||||
super.close();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -81,7 +81,7 @@ class IIOInputStreamAdapter extends InputStream {
|
||||
|
||||
private IIOInputStreamAdapter(ImageInputStream pInput, long pLength, boolean pHasLength) {
|
||||
Validate.notNull(pInput, "stream");
|
||||
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %f");
|
||||
Validate.isTrue(!pHasLength || pLength >= 0, pLength, "length < 0: %d");
|
||||
|
||||
input = pInput;
|
||||
left = pLength;
|
||||
|
||||
+22
-1
@@ -37,7 +37,9 @@ import java.awt.image.*;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DiscreteAlphaIndexColorModelTest {
|
||||
|
||||
@@ -204,6 +206,25 @@ public class DiscreteAlphaIndexColorModelTest {
|
||||
assertThat(raster.getTransferType(), CoreMatchers.equalTo(DataBuffer.TYPE_BYTE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumComponents() {
|
||||
int[] colors = createIntLut(1 << 8);
|
||||
IndexColorModel icm = new IndexColorModel(8, colors.length, colors, 0, false, -1, DataBuffer.TYPE_BYTE);
|
||||
|
||||
ColorModel colorModelDiscreteAlpha = new DiscreteAlphaIndexColorModel(icm, 1, true);
|
||||
ColorModel colorModelDiscreteAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 2, true);
|
||||
ColorModel colorModelNoAlphaExtra = new DiscreteAlphaIndexColorModel(icm, 42, false);
|
||||
|
||||
assertEquals(3, colorModelDiscreteAlpha.getNumColorComponents());
|
||||
assertEquals(4, colorModelDiscreteAlpha.getNumComponents());
|
||||
|
||||
assertEquals(3, colorModelDiscreteAlphaExtra.getNumColorComponents());
|
||||
assertEquals(5, colorModelDiscreteAlphaExtra.getNumComponents()); // Questionable
|
||||
|
||||
assertEquals(3, colorModelNoAlphaExtra.getNumColorComponents());
|
||||
assertEquals(45, colorModelNoAlphaExtra.getNumComponents()); // Questionable
|
||||
}
|
||||
|
||||
private static int[] createIntLut(final int count) {
|
||||
int[] lut = new int[count];
|
||||
|
||||
|
||||
+2
-1
@@ -53,6 +53,7 @@ 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);
|
||||
|
||||
@@ -433,7 +434,7 @@ public class BufferedImageInputStreamTest {
|
||||
* and {@code pFirstOffset == pSecondOffset}.
|
||||
* Otherwise {@code false}.
|
||||
*/
|
||||
static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
|
||||
public static boolean rangeEquals(byte[] pFirst, int pFirstOffset, byte[] pSecond, int pSecondOffset, int pLength) {
|
||||
if (pFirst == pSecond && pFirstOffset == pSecondOffset) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Executable
+380
@@ -0,0 +1,380 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-hdr</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-icns</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-iff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg-jep262-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-jpeg</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
|
||||
+4
@@ -173,10 +173,14 @@ 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:
|
||||
|
||||
+6
-6
@@ -1,16 +1,16 @@
|
||||
package com.twelvemonkeys.imageio.metadata.tiff;
|
||||
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
/**
|
||||
* HalfTest.
|
||||
@@ -97,7 +97,7 @@ public class HalfTest {
|
||||
}
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testParseHAlfNull() {
|
||||
public void testParseHalfNull() {
|
||||
Half.parseHalf(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pcx</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
|
||||
|
||||
+4
-6
@@ -213,14 +213,12 @@ public final class PCXImageReader extends ImageReaderBase {
|
||||
byte[] rowDataByte = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
switch (header.getBitsPerPixel()) {
|
||||
case 1:
|
||||
readRowByte(input, srcRegion, xSub, ySub, planeData, 0, planeWidth * header.getChannels(), destRaster, clippedRow, y);
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError();
|
||||
if (header.getBitsPerPixel() != 1) {
|
||||
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);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pdf</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pict</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
|
||||
|
||||
+10
-9
@@ -30,19 +30,20 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.pict;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ImageReaderSpiBase;
|
||||
|
||||
/**
|
||||
* 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 haku Exp$
|
||||
* @version $Id: PICTImageReaderSpi.java,v 1.0 28.feb.2006 19:21:05 haraldk Exp$
|
||||
*/
|
||||
public final class PICTImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
@@ -61,7 +62,7 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase {
|
||||
|
||||
ImageInputStream stream = (ImageInputStream) pSource;
|
||||
|
||||
// PICT format don't have good magic and our method often gives false positives,
|
||||
// PICT format doesn'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;
|
||||
@@ -76,7 +77,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();
|
||||
|
||||
@@ -149,8 +150,8 @@ public final class PICTImageReaderSpi extends ImageReaderSpiBase {
|
||||
}
|
||||
|
||||
static void skipNullHeader(final ImageInputStream pStream) throws IOException {
|
||||
// NOTE: Only skip if FILE FORMAT, not needed for Mac OS DnD
|
||||
// Spec says "platform dependent", may not be all nulls..
|
||||
// NOTE: Only skip if FILE FORMAT, not needed for macOS DnD
|
||||
// Spec says "platform dependent", may not be all nulls...
|
||||
pStream.skipBytes(PICT.PICT_NULL_HEADER_SIZE);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-pnm</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-psd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
|
||||
|
||||
+266
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-8
@@ -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,15 +69,24 @@ abstract class PSDDirectoryResource extends PSDImageResource {
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
|
||||
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("...");
|
||||
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);
|
||||
|
||||
builder.append("\"]");
|
||||
if (length < this.data.length) {
|
||||
builder.append("...");
|
||||
}
|
||||
|
||||
builder.append("\"]");
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
-15
@@ -85,19 +85,4 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
-15
@@ -53,19 +53,4 @@ 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();
|
||||
}
|
||||
}
|
||||
|
||||
+297
-249
@@ -43,15 +43,19 @@ import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.color.*;
|
||||
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.*;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
|
||||
|
||||
/**
|
||||
* ImageReader for Adobe Photoshop Document (PSD) format.
|
||||
@@ -62,7 +66,11 @@ import java.util.*;
|
||||
* @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: Implement ImageIO meta data interface
|
||||
// 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: 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
|
||||
@@ -70,6 +78,7 @@ import java.util.*;
|
||||
// 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"));
|
||||
@@ -390,21 +399,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 i = 0; i < byteCounts.length; i++) {
|
||||
byteCounts[i] = header.largeFormat ? imageInput.readInt() : imageInput.readUnsignedShort();
|
||||
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();
|
||||
}
|
||||
}
|
||||
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(
|
||||
@@ -446,7 +455,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[] pByteCounts, final int pCompression) throws IOException {
|
||||
final int[][] byteCounts, final int compression) throws IOException {
|
||||
|
||||
WritableRaster destRaster = destination.getRaster();
|
||||
ColorModel destCM = destination.getColorModel();
|
||||
@@ -458,31 +467,33 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
int interleavedBands = banded ? 1 : destRaster.getNumBands();
|
||||
|
||||
for (int c = 0; c < channels; c++) {
|
||||
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
|
||||
try (ImageInputStream stream = createDecompressorStream(imageInput, compression, header.width, header.bits, byteCounts != null ? byteCounts[c] : null, -1)) {
|
||||
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
|
||||
|
||||
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));
|
||||
}
|
||||
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()) {
|
||||
break;
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -529,222 +540,182 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
processImageProgress(100f * channel / channelCount + 100f * y / (height * channelCount));
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
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 >= 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);
|
||||
}
|
||||
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
|
||||
stream.readFully(rowData, 0, channelWidth);
|
||||
|
||||
// TODO: Destination offset...??
|
||||
// Copy line sub sampled into real data
|
||||
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];
|
||||
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];
|
||||
|
||||
// CMYK values are stored inverted, but alpha is not
|
||||
if (invert) {
|
||||
value = 0xffffffff - value;
|
||||
}
|
||||
|
||||
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
|
||||
data.setElem(banded ? band : 0, offset + x * bandCount, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.skipBytes(length);
|
||||
stream.skipBytes(4 * channelWidth);
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
|
||||
processImageProgressForChannel(channel, channelCount, y, channelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
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 >= 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);
|
||||
}
|
||||
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
|
||||
stream.readFully(rowData, 0, channelWidth);
|
||||
|
||||
// TODO: Destination offset...??
|
||||
// Copy line sub sampled into real data
|
||||
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];
|
||||
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];
|
||||
|
||||
// CMYK values are stored inverted, but alpha is not
|
||||
if (invert) {
|
||||
value = (short) (0xffff - value & 0xffff);
|
||||
}
|
||||
|
||||
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
|
||||
data.setElem(banded ? band : 0, offset + x * bandCount, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.skipBytes(length);
|
||||
stream.skipBytes(2 * channelWidth);
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
|
||||
processImageProgressForChannel(channel, channelCount, y, channelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
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 >= 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);
|
||||
}
|
||||
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
|
||||
stream.readFully(rowData, 0, channelWidth);
|
||||
|
||||
// TODO: Destination offset...??
|
||||
// Copy line sub sampled into real data
|
||||
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];
|
||||
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];
|
||||
|
||||
// CMYK values are stored inverted, but alpha is not
|
||||
if (invert) {
|
||||
value = (byte) (0xff - value & 0xff);
|
||||
}
|
||||
|
||||
pData.setElem(banded ? pChannel : 0, offset + x * pBands, value);
|
||||
data.setElem(banded ? band : 0, offset + x * bandCount, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.skipBytes(length);
|
||||
stream.skipBytes(channelWidth);
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
|
||||
processImageProgressForChannel(channel, channelCount, y, channelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@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 {
|
||||
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 {
|
||||
// NOTE: 1 bit channels only occurs once
|
||||
if (channel > 0) {
|
||||
throw new IIOException("Multiple channels not supported for 1 bit data");
|
||||
}
|
||||
|
||||
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;
|
||||
final int destWidth = (destRect.width + 7) / 8;
|
||||
final boolean banded = data.getNumBanks() > 1;
|
||||
|
||||
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 >= 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);
|
||||
}
|
||||
if (y >= sourceRect.y && y < sourceRect.y + sourceRect.height && y % ySub == 0) {
|
||||
stream.readFully(rowData, 0, rowData.length);
|
||||
|
||||
// TODO: Destination offset...??
|
||||
int offset = (y - pSource.y) / pYSub * destWidth;
|
||||
if (pXSub == 1 && pSource.x % 8 == 0) {
|
||||
int offset = (y - sourceRect.y) / ySub * destWidth;
|
||||
if (xSub == 1 && sourceRect.x % 8 == 0) {
|
||||
// Fast normal case, no sub sampling
|
||||
for (int i = 0; i < destWidth; i++) {
|
||||
byte value = pRow[pSource.x / 8 + i * pXSub];
|
||||
byte value = rowData[sourceRect.x / 8 + i * xSub];
|
||||
// NOTE: Invert bits to match Java's default monochrome
|
||||
pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~value & 0xff));
|
||||
data.setElem(banded ? channel : 0, offset + i, (byte) (~value & 0xff));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Copy line sub sampled into real data
|
||||
final int maxX = pSource.x + pSource.width;
|
||||
int x = pSource.x;
|
||||
final int maxX = sourceRect.x + sourceRect.width;
|
||||
int x = sourceRect.x;
|
||||
for (int i = 0; i < destWidth; i++) {
|
||||
byte result = 0;
|
||||
|
||||
@@ -756,25 +727,25 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
int destBitOff = 7 - j;
|
||||
|
||||
// Shift bit into place
|
||||
result |= ((pRow[bytePos] & mask) >> sourceBitOff) << destBitOff;
|
||||
result |= ((rowData[bytePos] & mask) >> sourceBitOff) << destBitOff;
|
||||
|
||||
x += pXSub;
|
||||
x += xSub;
|
||||
}
|
||||
|
||||
// NOTE: Invert bits to match Java's default monochrome
|
||||
pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~result & 0xff));
|
||||
data.setElem(banded ? channel : 0, offset + i, (byte) (~result & 0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
imageInput.skipBytes(length);
|
||||
stream.skipBytes((channelWidth + 7) / 8);
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
|
||||
processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight);
|
||||
processImageProgressForChannel(channel, 1, y, channelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -920,13 +891,13 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
// TODO: Flags or list of interesting resources to parse
|
||||
// TODO: Obey ignoreMetadata
|
||||
private void readLayerAndMaskInfo(final boolean pParseData) throws IOException {
|
||||
private void readLayerAndMaskInfo(final boolean parseData) throws IOException {
|
||||
readImageResources(false);
|
||||
|
||||
if (pParseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
|
||||
if (parseData && (metadata.layerInfo == null || metadata.globalLayerMask == null) || metadata.imageDataStart == 0) {
|
||||
imageInput.seek(metadata.layerAndMaskInfoStart);
|
||||
|
||||
long layerAndMaskInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
|
||||
long layerAndMaskInfoLength = readLength(imageInput);
|
||||
|
||||
// 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
|
||||
@@ -936,7 +907,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
if (layerAndMaskInfoLength > 0) {
|
||||
long pos = imageInput.getStreamPosition();
|
||||
|
||||
long layerInfoLength = header.largeFormat ? imageInput.readLong() : imageInput.readUnsignedInt();
|
||||
long layerInfoLength = readLength(imageInput);
|
||||
|
||||
if (layerInfoLength > 0) {
|
||||
// "Layer count. If it is a negative number, its absolute value is the number of
|
||||
@@ -945,7 +916,7 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
int layerCount = imageInput.readShort();
|
||||
metadata.layerCount = layerCount;
|
||||
|
||||
if (pParseData && metadata.layerInfo == null) {
|
||||
if (metadata.layerInfo == null) {
|
||||
metadata.layerInfo = readLayerInfo(Math.abs(layerCount));
|
||||
metadata.layersStart = imageInput.getStreamPosition();
|
||||
}
|
||||
@@ -955,16 +926,13 @@ 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 (pParseData && metadata.globalLayerMask == null) {
|
||||
if (parseData && metadata.globalLayerMask == null) {
|
||||
metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength);
|
||||
}
|
||||
// TODO: Else skip?
|
||||
@@ -973,13 +941,52 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
metadata.globalLayerMask = PSDGlobalLayerMask.NULL_MASK;
|
||||
}
|
||||
|
||||
// TODO: Parse "Additional layer information"
|
||||
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: 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 (pParseData && DEBUG) {
|
||||
if (parseData && DEBUG) {
|
||||
System.out.println("layerInfo: " + metadata.layerInfo);
|
||||
System.out.println("globalLayerMask: " + (metadata.globalLayerMask != PSDGlobalLayerMask.NULL_MASK ? metadata.globalLayerMask : null));
|
||||
}
|
||||
@@ -989,6 +996,39 @@ 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];
|
||||
|
||||
@@ -1052,21 +1092,22 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
|
||||
final int interleavedBands = banded ? 1 : raster.getNumBands();
|
||||
|
||||
// TODO: progress for layers!
|
||||
processImageStarted(1 + layerIndex);
|
||||
|
||||
// TODO: Consider creating a method in PSDLayerInfo that can tell how many channels we really want to decode
|
||||
for (PSDChannelInfo channelInfo : layerInfo.channelInfo) {
|
||||
for (int channel = 0; channel < layerInfo.channelInfo.length; channel++) {
|
||||
PSDChannelInfo channelInfo = layerInfo.channelInfo[channel];
|
||||
|
||||
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 || (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { // TODO: ZIP Compressions!
|
||||
// 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"));
|
||||
imageInput.skipBytes(channelInfo.length - 2);
|
||||
}
|
||||
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;
|
||||
} else {
|
||||
// 0 = red, 1 = green, etc -1 = transparency mask
|
||||
int band = 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.
|
||||
@@ -1076,52 +1117,51 @@ 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 the image data starts with the byte counts
|
||||
// If RLE, 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));
|
||||
}
|
||||
|
||||
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
|
||||
try (ImageInputStream stream = createDecompressorStream(imageInput, compression, width, header.bits, byteCounts, channelInfo.length - 2)) {
|
||||
int bandOffset = banded ? 0 : interleavedBands - 1 - band;
|
||||
|
||||
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));
|
||||
}
|
||||
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()) {
|
||||
break;
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1130,6 +1170,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
convertToDestinationCS(sourceCM, destCM, raster);
|
||||
}
|
||||
|
||||
processImageComplete();
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
@@ -1177,6 +1219,7 @@ 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"?
|
||||
}
|
||||
|
||||
@@ -1311,38 +1354,43 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
int idx = 0;
|
||||
while (pArgs[idx].charAt(0) == '-') {
|
||||
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]);
|
||||
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]);
|
||||
|
||||
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);
|
||||
}
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
idx++;
|
||||
|
||||
+11
-2
@@ -41,7 +41,7 @@ import com.twelvemonkeys.util.FilterIterator;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.awt.image.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
@@ -101,6 +101,8 @@ 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
|
||||
@@ -148,7 +150,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;
|
||||
@@ -675,6 +677,13 @@ 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()) {
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.*;
|
||||
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();
|
||||
|
||||
+51
-15
@@ -30,16 +30,22 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
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.util.zip.ZipInputStream;
|
||||
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;
|
||||
|
||||
/**
|
||||
* PSDUtil
|
||||
@@ -89,19 +95,49 @@ 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+579
@@ -0,0 +1,579 @@
|
||||
/*
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
+104
-2
@@ -45,10 +45,14 @@ import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
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.*;
|
||||
|
||||
@@ -618,4 +622,102 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+303
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
* 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
|
||||
};
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-reference</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-sgi</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tga</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
|
||||
|
||||
+1
-20
@@ -33,10 +33,10 @@ package com.twelvemonkeys.imageio.plugins.tga;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Calendar;
|
||||
|
||||
import static com.twelvemonkeys.imageio.plugins.tga.TGA.EXT_AREA_SIZE;
|
||||
import static com.twelvemonkeys.imageio.plugins.tga.TGAHeader.readString;
|
||||
|
||||
/**
|
||||
* TGAExtensions.
|
||||
@@ -149,25 +149,6 @@ final class TGAExtensions {
|
||||
return calendar;
|
||||
}
|
||||
|
||||
private static String readString(final ImageInputStream stream, final int maxLength) throws IOException {
|
||||
byte[] data = new byte[maxLength];
|
||||
stream.readFully(data);
|
||||
|
||||
return asZeroTerminatedASCIIString(data);
|
||||
}
|
||||
|
||||
private static String asZeroTerminatedASCIIString(final byte[] data) {
|
||||
int len = data.length;
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == 0) {
|
||||
len = i;
|
||||
}
|
||||
}
|
||||
|
||||
return new String(data, 0, len, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
public boolean hasAlpha() {
|
||||
switch (attributeType) {
|
||||
case 3:
|
||||
|
||||
+21
-4
@@ -253,10 +253,7 @@ final class TGAHeader {
|
||||
|
||||
// Image ID section, not *really* part of the header, but let's get rid of it...
|
||||
if (imageIdLength > 0) {
|
||||
byte[] idBytes = new byte[imageIdLength];
|
||||
imageInput.readFully(idBytes);
|
||||
|
||||
header.identification = new String(idBytes, StandardCharsets.US_ASCII);
|
||||
header.identification = readString(imageInput, imageIdLength);
|
||||
}
|
||||
|
||||
// Color map, not *really* part of the header
|
||||
@@ -267,6 +264,26 @@ final class TGAHeader {
|
||||
return header;
|
||||
}
|
||||
|
||||
static String readString(final ImageInputStream stream, final int maxLength) throws IOException {
|
||||
byte[] data = new byte[maxLength];
|
||||
stream.readFully(data);
|
||||
|
||||
return asZeroTerminatedASCIIString(data);
|
||||
}
|
||||
|
||||
private static String asZeroTerminatedASCIIString(final byte[] data) {
|
||||
int len = data.length;
|
||||
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
if (data[i] == 0) {
|
||||
len = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new String(data, 0, len, StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private static IndexColorModel readColorMap(final DataInput stream, final TGAHeader header) throws IOException {
|
||||
int size = header.colorMapSize;
|
||||
int depth = header.colorMapDepth;
|
||||
|
||||
+7
-12
@@ -136,7 +136,7 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
case TGA.IMAGETYPE_TRUECOLOR_RLE:
|
||||
ColorSpace sRGB = ColorSpace.getInstance(ColorSpace.CS_sRGB);
|
||||
|
||||
boolean hasAlpha = header.getAttributeBits() > 0 && extensions != null && extensions.hasAlpha();
|
||||
boolean hasAlpha = header.getAttributeBits() > 0 && (extensions == null || extensions.hasAlpha());
|
||||
boolean isAlphaPremultiplied = extensions != null && extensions.isAlphaPremultiplied();
|
||||
|
||||
switch (header.getPixelDepth()) {
|
||||
@@ -393,27 +393,22 @@ final class TGAImageReader extends ImageReaderBase {
|
||||
|
||||
// Read header
|
||||
header = TGAHeader.read(imageInput);
|
||||
|
||||
// System.err.println("header: " + header);
|
||||
|
||||
imageInput.flushBefore(imageInput.getStreamPosition());
|
||||
|
||||
// Read footer, if 2.0 format (ends with TRUEVISION-XFILE\0)
|
||||
skipToEnd(imageInput);
|
||||
imageInput.seek(imageInput.getStreamPosition() - 26);
|
||||
|
||||
long extOffset = imageInput.readInt();
|
||||
/*long devOffset = */imageInput.readInt(); // Ignored for now
|
||||
long extOffset = imageInput.readUnsignedInt();
|
||||
/*long devOffset = */imageInput.readUnsignedInt(); // Ignored for now
|
||||
|
||||
byte[] magic = new byte[18];
|
||||
imageInput.readFully(magic);
|
||||
|
||||
if (Arrays.equals(magic, TGA.MAGIC)) {
|
||||
if (extOffset > 0) {
|
||||
imageInput.seek(extOffset);
|
||||
int extSize = imageInput.readUnsignedShort();
|
||||
extensions = extSize == 0 ? null : TGAExtensions.read(imageInput, extSize);
|
||||
}
|
||||
if (Arrays.equals(magic, TGA.MAGIC) && extOffset > 0) {
|
||||
imageInput.seek(extOffset);
|
||||
int extSize = imageInput.readUnsignedShort();
|
||||
extensions = extSize == 0 ? null : TGAExtensions.read(imageInput, extSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-5
@@ -291,12 +291,10 @@ final class TGAMetadata extends AbstractMetadata {
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
|
||||
// NOTE: Names corresponds to equivalent fields in TIFF
|
||||
if (header.getIdentification() != null && !header.getIdentification().isEmpty()) {
|
||||
appendTextEntry(text, "DocumentName", header.getIdentification());
|
||||
}
|
||||
appendTextEntry(text, "DocumentName", header.getIdentification());
|
||||
|
||||
if (extensions != null) {
|
||||
appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : extensions.getSoftware() + " " + extensions.getSoftwareVersion());
|
||||
appendTextEntry(text, "Software", extensions.getSoftwareVersion() == null ? extensions.getSoftware() : (extensions.getSoftware() + " " + extensions.getSoftwareVersion()));
|
||||
appendTextEntry(text, "Artist", extensions.getAuthorName());
|
||||
appendTextEntry(text, "UserComment", extensions.getAuthorComments());
|
||||
}
|
||||
@@ -305,7 +303,7 @@ final class TGAMetadata extends AbstractMetadata {
|
||||
}
|
||||
|
||||
private void appendTextEntry(final IIOMetadataNode parent, final String keyword, final String value) {
|
||||
if (value != null) {
|
||||
if (value != null && !value.isEmpty()) {
|
||||
IIOMetadataNode textEntry = new IIOMetadataNode("TextEntry");
|
||||
parent.appendChild(textEntry);
|
||||
textEntry.setAttribute("keyword", keyword);
|
||||
|
||||
+80
-7
@@ -33,17 +33,23 @@ package com.twelvemonkeys.imageio.plugins.tga;
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* TGAImageReaderTest
|
||||
@@ -76,20 +82,20 @@ public class TGAImageReaderTest extends ImageReaderAbstractTest<TGAImageReader>
|
||||
new TestData(getClassLoaderResource("/tga/CTC24.TGA"), new Dimension(128, 128)), // RLE compressed 24 bit BGR
|
||||
new TestData(getClassLoaderResource("/tga/CTC32.TGA"), new Dimension(128, 128)), // RLE compressed 32 bit BGRA
|
||||
|
||||
// Further samples from http://www.fileformat.info/format/tga/sample/index.htm
|
||||
// More samples from http://www.fileformat.info/format/tga/sample/index.htm
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_B16.TGA"), new Dimension(124, 124)), // Uncompressed 16 bit BGR bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_B24.TGA"), new Dimension(124, 124)), // Uncompressed 24 bit BGR bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_B32.TGA"), new Dimension(124, 124)), // Uncompressed 32 bit BGRA bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_B32.TGA"), new Dimension(124, 124)), // Uncompressed 32 bit BGRX bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_T16.TGA"), new Dimension(124, 124)), // Uncompressed 16 bit BGR top/down
|
||||
// new TestData(getClassLoaderResource("/tga/FLAG_T24.TGA"), new Dimension(124, 124)), // Uncompressed 24 bit BGR top/down (missing from file set)
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_T32.TGA"), new Dimension(124, 124)), // Uncompressed 32 bit BGRA top/down
|
||||
new TestData(getClassLoaderResource("/tga/FLAG_T32.TGA"), new Dimension(124, 124)), // Uncompressed 32 bit BGRX top/down
|
||||
|
||||
new TestData(getClassLoaderResource("/tga/XING_B16.TGA"), new Dimension(240, 164)), // Uncompressed 16 bit BGR bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/XING_B24.TGA"), new Dimension(240, 164)), // Uncompressed 24 bit BGR bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/XING_B32.TGA"), new Dimension(240, 164)), // Uncompressed 32 bit BGRA bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/XING_B32.TGA"), new Dimension(240, 164)), // Uncompressed 32 bit BGRX bottom/up
|
||||
new TestData(getClassLoaderResource("/tga/XING_T16.TGA"), new Dimension(240, 164)), // Uncompressed 16 bit BGR top/down
|
||||
new TestData(getClassLoaderResource("/tga/XING_T24.TGA"), new Dimension(240, 164)), // Uncompressed 24 bit BGR top/down
|
||||
new TestData(getClassLoaderResource("/tga/XING_T32.TGA"), new Dimension(240, 164)), // Uncompressed 32 bit BGRA top/down
|
||||
new TestData(getClassLoaderResource("/tga/XING_T32.TGA"), new Dimension(240, 164)), // Uncompressed 32 bit BGRX top/down
|
||||
|
||||
new TestData(getClassLoaderResource("/tga/autodesk-3dsmax-extsize494.tga"), new Dimension(440, 200)), // RLE compressed 32 bit BGRA bottom/up, with extension area size 494
|
||||
|
||||
@@ -97,7 +103,8 @@ public class TGAImageReaderTest extends ImageReaderAbstractTest<TGAImageReader>
|
||||
new TestData(getClassLoaderResource("/tga/monochrome16_top_left_rle.tga"), new Dimension(64, 64)), // RLE compressed 16 bit monochrome
|
||||
|
||||
new TestData(getClassLoaderResource("/tga/692c33d1-d0c3-4fe2-a059-f199d063bc7a.tga"), new Dimension(256, 256)), // Uncompressed BGR, with colorMapDepth set to 24
|
||||
new TestData(getClassLoaderResource("/tga/0112eccd-2c29-4368-bcef-59c823b6e5d1.tga"), new Dimension(256, 256)) // RLE compressed BGR, with extension area size 0
|
||||
new TestData(getClassLoaderResource("/tga/0112eccd-2c29-4368-bcef-59c823b6e5d1.tga"), new Dimension(256, 256)), // RLE compressed BGR, with extension area size 0
|
||||
new TestData(getClassLoaderResource("/tga/alpha-no-extension.tga"), new Dimension(167, 64)) // Uncompressed BGRA, without extension area
|
||||
);
|
||||
}
|
||||
|
||||
@@ -137,4 +144,70 @@ public class TGAImageReaderTest extends ImageReaderAbstractTest<TGAImageReader>
|
||||
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlpha() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
|
||||
// These samples have "attribute bits" that are *NOT* alpha, according to the extension area
|
||||
for (String sample : Arrays.asList("/tga/UTC16.TGA", "/tga/UTC32.TGA", "/tga/CTC16.TGA", "/tga/CTC32.TGA")) {
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource(sample))) {
|
||||
reader.setInput(input);
|
||||
|
||||
BufferedImage image = reader.read(0);
|
||||
|
||||
String message = String.format("RGB value differs for sample '%s'", sample);
|
||||
assertRGBEquals(message, 0xffff0000, image.getRGB(0, 0), 0);
|
||||
assertRGBEquals(message, 0xff00ff00, image.getRGB(8, 0), 0);
|
||||
assertRGBEquals(message, 0xff0000ff, image.getRGB(16, 0), 0);
|
||||
assertRGBEquals(message, 0xff000000, image.getRGB(24, 0), 0);
|
||||
assertRGBEquals(message, 0xffff0000, image.getRGB(32, 0), 0);
|
||||
assertRGBEquals(message, 0xff00ff00, image.getRGB(40, 0), 0);
|
||||
assertRGBEquals(message, 0xff0000ff, image.getRGB(48, 0), 0);
|
||||
assertRGBEquals(message, 0xffffffff, image.getRGB(56, 0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// This sample has "attribute bits" that *ARE* alpha, and there is no extension area
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tga/alpha-no-extension.tga"))) {
|
||||
reader.setInput(input);
|
||||
|
||||
BufferedImage image = reader.read(0);
|
||||
|
||||
String message = "RGB value differs for sample '/tga/alpha-no-extension.tga'";
|
||||
assertRGBEquals(message, 0x00ffffff, image.getRGB(0, 0), 0);
|
||||
assertRGBEquals(message, 0xffffffff, image.getRGB(48, 14), 0);
|
||||
}
|
||||
|
||||
reader.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMetadataTextEntries() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
|
||||
try (ImageInputStream input = ImageIO.createImageInputStream(getClassLoaderResource("/tga/autodesk-3dsmax-extsize494.tga"))) {
|
||||
reader.setInput(input);
|
||||
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
|
||||
NodeList textEntries = root.getElementsByTagName("TextEntry");
|
||||
boolean softwareFound = false;
|
||||
|
||||
for (int i = 0; i < root.getLength(); i++) {
|
||||
IIOMetadataNode software = (IIOMetadataNode) textEntries.item(i);
|
||||
|
||||
if ("Software".equals(software.getAttribute("keyword"))) {
|
||||
assertEquals("Autodesk 3ds max 1.0", software.getAttribute("value"));
|
||||
softwareFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assertTrue("No Software TextEntry", softwareFound);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-thumbsdb</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jai-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff-jdk-interop</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-tiff</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
|
||||
|
||||
+2
-1
@@ -63,7 +63,7 @@ final class BitPaddingStream extends FilterInputStream {
|
||||
|
||||
private final byte[] inputBuffer;
|
||||
private final ByteBuffer buffer;
|
||||
private int componentSize;
|
||||
private final int componentSize;
|
||||
|
||||
BitPaddingStream(final InputStream stream, int samplesPerPixel, final int bitsPerSample, final int colsInTile, final ByteOrder byteOrder) {
|
||||
super(notNull(stream, "stream"));
|
||||
@@ -169,6 +169,7 @@ final class BitPaddingStream extends FilterInputStream {
|
||||
return length;
|
||||
}
|
||||
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
private boolean fillBuffer() throws IOException {
|
||||
if (!readFully(inputBuffer)) {
|
||||
return false;
|
||||
|
||||
+2
-1
@@ -50,6 +50,7 @@ import static com.twelvemonkeys.imageio.plugins.tiff.HorizontalDifferencingStrea
|
||||
* @version $Id: HorizontalDeDifferencingStream.java,v 1.0 11.03.13 14:20 haraldk Exp$
|
||||
*/
|
||||
final class HorizontalDeDifferencingStream extends InputStream {
|
||||
/// TODO: Create shared version with PSD, or see if we can avoid some duplication?
|
||||
// See TIFF 6.0 Specification, Section 14: "Differencing Predictor", page 64.
|
||||
|
||||
private final int columns;
|
||||
@@ -96,7 +97,7 @@ final class HorizontalDeDifferencingStream extends InputStream {
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeRow() throws EOFException {
|
||||
private void decodeRow() {
|
||||
// Un-apply horizontal predictor
|
||||
byte original;
|
||||
int sample = 0;
|
||||
|
||||
+37
-1
@@ -1004,12 +1004,12 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
// - If set, set res unit to pixels per cm as this better reflects values?
|
||||
// - Or, convert to DPI, if we already had values in DPI??
|
||||
// Also, if we have only aspect, set these values, and use unknown as unit?
|
||||
// TODO: ImageOrientation => Orientation
|
||||
NodeList children = dimensionNode.getChildNodes();
|
||||
|
||||
Float aspect = null;
|
||||
Float xRes = null;
|
||||
Float yRes = null;
|
||||
Integer orientation = null;
|
||||
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
Node child = children.item(i);
|
||||
@@ -1024,6 +1024,9 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
else if ("VerticalPixelSize".equals(nodeName)) {
|
||||
yRes = Float.parseFloat(getAttribute(child, "value"));
|
||||
}
|
||||
else if ("ImageOrientation".equals(nodeName)) {
|
||||
orientation = toTIFFOrientation(getAttribute(child, "value"));
|
||||
}
|
||||
}
|
||||
|
||||
// If we have one size compute the other
|
||||
@@ -1070,6 +1073,11 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
new TIFFEntry(TIFF.TAG_RESOLUTION_UNIT, TIFF.TYPE_SHORT, TIFFBaseline.RESOLUTION_UNIT_NONE));
|
||||
}
|
||||
// Else give up...
|
||||
|
||||
if (orientation != null) {
|
||||
entries.put(TIFF.TAG_ORIENTATION,
|
||||
new TIFFEntry(TIFF.TAG_ORIENTATION, TIFF.TYPE_SHORT, orientation.shortValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeFromStandardDocumentNode(final Node documentNode, final Map<Integer, Entry> entries) {
|
||||
@@ -1195,6 +1203,34 @@ public final class TIFFImageMetadata extends AbstractMetadata {
|
||||
}
|
||||
}
|
||||
|
||||
private Integer toTIFFOrientation(String imageOrientation) {
|
||||
if (imageOrientation == null) {
|
||||
// malformed, empty or not readable value
|
||||
return null;
|
||||
}
|
||||
switch (imageOrientation.toLowerCase()) {
|
||||
case "normal":
|
||||
return TIFFBaseline.ORIENTATION_TOPLEFT;
|
||||
case "fliph":
|
||||
return TIFFExtension.ORIENTATION_TOPRIGHT;
|
||||
case "rotate180":
|
||||
return TIFFExtension.ORIENTATION_BOTRIGHT;
|
||||
case "flipv":
|
||||
return TIFFExtension.ORIENTATION_BOTLEFT;
|
||||
case "fliphrotate90":
|
||||
return TIFFExtension.ORIENTATION_LEFTTOP;
|
||||
case "rotate270":
|
||||
return TIFFExtension.ORIENTATION_RIGHTTOP;
|
||||
case "flipvrotate90":
|
||||
return TIFFExtension.ORIENTATION_RIGHTBOT;
|
||||
case "rotate90":
|
||||
return TIFFExtension.ORIENTATION_LEFTBOT;
|
||||
default:
|
||||
// malformed, invalid value
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private short getTIFFType(final Node node) throws IIOInvalidTreeException {
|
||||
Node containerNode = node.getFirstChild();
|
||||
if (containerNode == null) {
|
||||
|
||||
+254
-155
@@ -49,40 +49,50 @@ import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.io.FileUtil;
|
||||
import com.twelvemonkeys.io.LittleEndianDataInputStream;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.event.IIOReadWarningListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.plugins.jpeg.JPEGImageReadParam;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.CMMException;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.SequenceInputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
@@ -571,8 +581,13 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
|
||||
return createImageTypeSpecifier(planarConfiguration, cs, dataType, significantSamples, samplesPerPixel, false, false);
|
||||
}
|
||||
else if (bitsPerSample == 2 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
|
||||
return ImageTypeSpecifiers.createPacked(cs, 0x30, 0xC, 0x3, 0, DataBuffer.TYPE_BYTE, false);
|
||||
}
|
||||
else if (bitsPerSample == 4 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
|
||||
return ImageTypeSpecifiers.createPacked(cs, 0xF00, 0xF0, 0xF, 0, DataBuffer.TYPE_USHORT, false);
|
||||
}
|
||||
else if (bitsPerSample > 8 && bitsPerSample % 2 == 0) {
|
||||
// TODO: Support variable bits/sample?
|
||||
ColorModel colorModel = new ComponentColorModel(cs, new int[] {bitsPerSample, bitsPerSample, bitsPerSample}, false, false, Transparency.OPAQUE, dataType);
|
||||
SampleModel sampleModel = planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY
|
||||
? colorModel.createCompatibleSampleModel(1, 1)
|
||||
@@ -583,11 +598,14 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
if (bitsPerSample == 8 || bitsPerSample == 16 || bitsPerSample == 32) {
|
||||
return createImageTypeSpecifier(planarConfiguration, cs, dataType, significantSamples, samplesPerPixel, true, isAlphaPremultiplied);
|
||||
}
|
||||
else if (significantSamples == 4 && bitsPerSample == 4) {
|
||||
else if (bitsPerSample == 2 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
|
||||
return ImageTypeSpecifiers.createPacked(cs, 0xC0, 0x30, 0xC, 0x3, DataBuffer.TYPE_BYTE, isAlphaPremultiplied);
|
||||
}
|
||||
else if (bitsPerSample == 4 && planarConfiguration == TIFFBaseline.PLANARCONFIG_CHUNKY) {
|
||||
return ImageTypeSpecifiers.createPacked(cs, 0xF000, 0xF00, 0xF0, 0xF, DataBuffer.TYPE_USHORT, isAlphaPremultiplied);
|
||||
}
|
||||
default:
|
||||
throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for RGB TIFF (expected 3/8, 4/8, 3/16 or 4/16): %d/%d", samplesPerPixel, bitsPerSample));
|
||||
throw new IIOException(String.format("Unsupported SamplesPerPixel/BitsPerSample combination for RGB TIFF (expected 3 or 4/a multiple of 2): %d/%d", samplesPerPixel, bitsPerSample));
|
||||
}
|
||||
case TIFFBaseline.PHOTOMETRIC_PALETTE:
|
||||
// Palette
|
||||
@@ -935,7 +953,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
final int compression = getValueAsIntWithDefault(TIFF.TAG_COMPRESSION, TIFFBaseline.COMPRESSION_NONE);
|
||||
final int predictor = getValueAsIntWithDefault(TIFF.TAG_PREDICTOR, 1);
|
||||
final int planarConfiguration = getValueAsIntWithDefault(TIFF.TAG_PLANAR_CONFIGURATION, TIFFBaseline.PLANARCONFIG_CHUNKY);
|
||||
final int numBands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? 1 : rawType.getNumBands();
|
||||
final int samplesInTile = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? 1 : rawType.getNumBands();
|
||||
|
||||
// NOTE: We handle strips as tiles of tileWidth == width by tileHeight == rowsPerStrip
|
||||
// Strips are top/down, tiles are left/right, top/down
|
||||
@@ -1055,7 +1073,9 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
int bands = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR ? rawType.getNumBands() : 1;
|
||||
int fillOrder = getValueAsIntWithDefault(TIFF.TAG_FILL_ORDER, TIFFBaseline.FILL_LEFT_TO_RIGHT);
|
||||
int bitsPerSample = getBitsPerSample();
|
||||
boolean needsBitPadding = bitsPerSample > 16 && bitsPerSample % 16 != 0 || bitsPerSample > 8 && bitsPerSample % 8 != 0 || bitsPerSample == 6;
|
||||
boolean needsBitPadding = bitsPerSample > 16 && bitsPerSample % 16 != 0 || bitsPerSample > 8 && bitsPerSample % 8 != 0
|
||||
|| samplesInTile == 1 && bitsPerSample == 6 // IndexColorModel or Gray
|
||||
|| samplesInTile == 3 && (bitsPerSample == 2 || bitsPerSample == 4); // RGB/YCbCr/etc.
|
||||
boolean needsAdapter = compression != TIFFBaseline.COMPRESSION_NONE || fillOrder != TIFFBaseline.FILL_LEFT_TO_RIGHT
|
||||
|| interpretation == TIFFExtension.PHOTOMETRIC_YCBCR || needsBitPadding;
|
||||
|
||||
@@ -1069,12 +1089,24 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
for (int b = 0; b < bands; b++) {
|
||||
int i = b * tilesDown * tilesAcross + y * tilesAcross + x;
|
||||
|
||||
// Clip the stripTile rowRaster to not exceed the srcRegion
|
||||
clip.width = Math.min(colsInTile, srcRegion.width);
|
||||
Raster clippedRow = clipRowToRect(rowRaster, clip,
|
||||
param != null ? param.getSourceBands() : null,
|
||||
param != null ? param.getSourceXSubsampling() : 1);
|
||||
|
||||
imageInput.seek(stripTileOffsets[i]);
|
||||
|
||||
DataInput input;
|
||||
ImageInputStream input;
|
||||
if (!needsAdapter) {
|
||||
// No need for transformation, fast-forward
|
||||
input = imageInput;
|
||||
long byteCount = stripTileHeight * (((long) stripTileWidth * bitsPerSample * samplesInTile + 7L) / 8L);
|
||||
|
||||
if (stripTileByteCounts != null && stripTileByteCounts[i] < byteCount) {
|
||||
processWarningOccurred("strip/tileByteCount < required ( " + byteCount + "):" + stripTileByteCounts[i]);
|
||||
}
|
||||
|
||||
input = new SubImageInputStream(imageInput, byteCount);
|
||||
}
|
||||
else {
|
||||
InputStream adapter = stripTileByteCounts != null
|
||||
@@ -1082,39 +1114,44 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
: createStreamAdapter(imageInput);
|
||||
|
||||
adapter = createFillOrderStream(fillOrder, adapter);
|
||||
adapter = createDecompressorStream(compression, stripTileWidth, numBands, adapter);
|
||||
adapter = createUnpredictorStream(predictor, stripTileWidth, numBands, bitsPerSample, adapter, imageInput.getByteOrder());
|
||||
|
||||
if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_BYTE) {
|
||||
adapter = new YCbCrUpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile);
|
||||
}
|
||||
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR && rowRaster.getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||
adapter = new YCbCr16UpsamplerStream(adapter, yCbCrSubsampling, yCbCrPos, colsInTile, imageInput.getByteOrder());
|
||||
}
|
||||
else if (interpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||
// Handled in getRawImageType
|
||||
throw new AssertionError();
|
||||
}
|
||||
// For subsampled planar, the compressed data will not be full width
|
||||
int compressedStripTileWidth = planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR && b > 0 && yCbCrSubsampling != null
|
||||
? ((stripTileWidth + yCbCrSubsampling[0] - 1) / yCbCrSubsampling[0])
|
||||
: stripTileWidth;
|
||||
adapter = createDecompressorStream(compression, compressedStripTileWidth, samplesInTile, adapter);
|
||||
adapter = createUnpredictorStream(predictor, compressedStripTileWidth, samplesInTile, bitsPerSample, adapter, imageInput.getByteOrder());
|
||||
adapter = createYCbCrUpsamplerStream(interpretation, planarConfiguration, b, rowRaster.getTransferType(), yCbCrSubsampling, yCbCrPos, colsInTile, adapter, imageInput.getByteOrder());
|
||||
|
||||
if (needsBitPadding) {
|
||||
// We'll pad "odd" bitsPerSample streams to the smallest data type (byte/short/int) larger than the input
|
||||
adapter = new BitPaddingStream(adapter, numBands, bitsPerSample, colsInTile, imageInput.getByteOrder());
|
||||
adapter = bitsPerSample < 8
|
||||
? new BitPaddingStream(adapter, 1, samplesInTile * bitsPerSample, colsInTile, imageInput.getByteOrder())
|
||||
: new BitPaddingStream(adapter, samplesInTile, bitsPerSample, colsInTile, imageInput.getByteOrder());
|
||||
}
|
||||
|
||||
// According to the spec, short/long/etc should follow order of containing stream
|
||||
input = imageInput.getByteOrder() == ByteOrder.BIG_ENDIAN
|
||||
? new DataInputStream(adapter)
|
||||
: new LittleEndianDataInputStream(adapter);
|
||||
input = new DirectImageInputStream(adapter);
|
||||
}
|
||||
|
||||
// Clip the stripTile rowRaster to not exceed the srcRegion
|
||||
clip.width = Math.min(colsInTile, srcRegion.width);
|
||||
Raster clippedRow = clipRowToRect(rowRaster, clip,
|
||||
param != null ? param.getSourceBands() : null,
|
||||
param != null ? param.getSourceXSubsampling() : 1);
|
||||
try (ImageInputStream stream = input) {
|
||||
// Temporary set byte order to match the color model for USHORT_4444/555/565/etc...
|
||||
if (rawType.getColorModel() instanceof DirectColorModel && rawType.getColorModel().getTransferType() == DataBuffer.TYPE_USHORT) {
|
||||
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
else {
|
||||
// ...otherwise keep the order from the parent stream
|
||||
stream.setByteOrder(imageInput.getByteOrder());
|
||||
}
|
||||
|
||||
// Read a full strip/tile
|
||||
readStripTileData(clippedRow, srcRegion, xSub, ySub, b, numBands, interpretation, destRaster, col, srcRow, colsInTile, rowsInTile, input);
|
||||
// Read a full strip/tile
|
||||
readStripTileData(clippedRow, srcRegion, xSub, ySub, b, samplesInTile, interpretation, destRaster, col, srcRow, colsInTile, rowsInTile, input);
|
||||
}
|
||||
}
|
||||
|
||||
// Need to do color normalization after reading all bands for planar
|
||||
if (planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR) {
|
||||
normalizeColorPlanar(interpretation, destRaster);
|
||||
}
|
||||
|
||||
col += colsInTile;
|
||||
@@ -1133,7 +1170,6 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
break;
|
||||
|
||||
case TIFFExtension.COMPRESSION_JPEG:
|
||||
@@ -1213,10 +1249,10 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
// TODO: Refactor + duplicate this for all JPEG-in-TIFF cases
|
||||
switch (raster.getTransferType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||
break;
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
normalizeColor(interpretation, ((DataBufferUShort) raster.getDataBuffer()).getData());
|
||||
normalizeColor(interpretation, samplesInTile, ((DataBufferUShort) raster.getDataBuffer()).getData());
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported transfer type: " + raster.getTransferType());
|
||||
@@ -1379,7 +1415,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
|
||||
// We'll have to use readAsRaster and later apply color space conversion ourselves
|
||||
Raster raster = jpegReader.readRaster(0, jpegParam);
|
||||
normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||
destination.getRaster().setDataElements(offset.x, offset.y, raster);
|
||||
}
|
||||
}
|
||||
@@ -1529,7 +1565,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
// Otherwise, it's likely CMYK or some other interpretation we don't need to convert.
|
||||
// We'll have to use readAsRaster and later apply color space conversion ourselves
|
||||
Raster raster = jpegReader.readRaster(0, jpegParam);
|
||||
normalizeColor(interpretation, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||
normalizeColor(interpretation, samplesInTile, ((DataBufferByte) raster.getDataBuffer()).getData());
|
||||
destination.getRaster().setDataElements(offset.x, offset.y, raster);
|
||||
}
|
||||
}
|
||||
@@ -1555,7 +1591,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
break;
|
||||
|
||||
// Known, but unsupported compression types
|
||||
// Known, but unsupported compression types
|
||||
case TIFFCustom.COMPRESSION_NEXT:
|
||||
case TIFFCustom.COMPRESSION_CCITTRLEW:
|
||||
case TIFFCustom.COMPRESSION_THUNDERSCAN:
|
||||
@@ -1570,8 +1606,8 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
case TIFFCustom.COMPRESSION_SGILOG:
|
||||
case TIFFCustom.COMPRESSION_SGILOG24:
|
||||
case TIFFCustom.COMPRESSION_JPEG2000: // Doable with JPEG2000 plugin?
|
||||
|
||||
throw new IIOException("Unsupported TIFF Compression value: " + compression);
|
||||
|
||||
default:
|
||||
throw new IIOException("Unknown TIFF Compression value: " + compression);
|
||||
}
|
||||
@@ -1583,6 +1619,28 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
return destination;
|
||||
}
|
||||
|
||||
private InputStream createYCbCrUpsamplerStream(int photometricInterpretation, int planarConfiguration, int plane, int transferType,
|
||||
int[] yCbCrSubsampling, int yCbCrPos, int colsInTile, InputStream stream, ByteOrder byteOrder) {
|
||||
if (photometricInterpretation == TIFFExtension.PHOTOMETRIC_YCBCR) {
|
||||
if (planarConfiguration == TIFFExtension.PLANARCONFIG_PLANAR && transferType == DataBuffer.TYPE_BYTE) {
|
||||
// For planar YCbCr, only the chroma planes are subsampled
|
||||
return plane > 0 && (yCbCrSubsampling[0] != 1 || yCbCrSubsampling[1] != 1)
|
||||
? new YCbCrPlanarUpsamplerStream(stream, yCbCrSubsampling, yCbCrPos, colsInTile) : stream;
|
||||
}
|
||||
else if (transferType == DataBuffer.TYPE_BYTE) {
|
||||
return new YCbCrUpsamplerStream(stream, yCbCrSubsampling, yCbCrPos, colsInTile);
|
||||
}
|
||||
else if (transferType == DataBuffer.TYPE_USHORT) {
|
||||
return new YCbCr16UpsamplerStream(stream, yCbCrSubsampling, yCbCrPos, colsInTile, byteOrder);
|
||||
}
|
||||
|
||||
// Handled in getRawImageType
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
private boolean containsZero(long[] byteCounts) {
|
||||
for (long byteCount : byteCounts) {
|
||||
if (byteCount <= 0) {
|
||||
@@ -1757,7 +1815,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
private IIOMetadataNode getNode(final IIOMetadataNode parent, final String tagName) {
|
||||
NodeList nodes = parent.getElementsByTagName(tagName);
|
||||
return nodes != null && nodes.getLength() >= 1 ? (IIOMetadataNode) nodes.item(0) : null;
|
||||
return nodes.getLength() >= 1 ? (IIOMetadataNode) nodes.item(0) : null;
|
||||
}
|
||||
|
||||
private ImageReader createJPEGDelegate() throws IOException {
|
||||
@@ -1863,14 +1921,20 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
private void readStripTileData(final Raster tileRowRaster, final Rectangle srcRegion, final int xSub, final int ySub,
|
||||
final int band, final int numBands, final int interpretation,
|
||||
final WritableRaster raster, final int startCol, final int startRow,
|
||||
final int colsInTile, final int rowsInTile, final DataInput input)
|
||||
final int colsInTile, final int rowsInTile, final ImageInputStream input)
|
||||
throws IOException {
|
||||
|
||||
DataBuffer dataBuffer = tileRowRaster.getDataBuffer();
|
||||
int bands = dataBuffer.getNumBanks();
|
||||
boolean banded = bands > 1;
|
||||
boolean banded = dataBuffer.getNumBanks() > 1;
|
||||
int bitsPerSample = getBitsPerSample();
|
||||
|
||||
WritableRaster destChannel = banded
|
||||
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
|
||||
: raster;
|
||||
Raster srcChannel = banded
|
||||
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
|
||||
: tileRowRaster;
|
||||
|
||||
switch (tileRowRaster.getTransferType()) {
|
||||
case DataBuffer.TYPE_BYTE:
|
||||
|
||||
@@ -1878,12 +1942,6 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
int bank = banded ? ((BandedSampleModel) tileRowRaster.getSampleModel()).getBankIndices()[band] : band;
|
||||
|
||||
byte[] rowDataByte = ((DataBufferByte) dataBuffer).getData(bank);
|
||||
WritableRaster destChannel = banded
|
||||
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
|
||||
: raster;
|
||||
Raster srcChannel = banded
|
||||
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
|
||||
: tileRowRaster;
|
||||
|
||||
for (int row = startRow; row < startRow + rowsInTile; row++) {
|
||||
if (row >= srcRegion.y + srcRegion.height) {
|
||||
@@ -1894,7 +1952,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
if (row % ySub == 0 && row >= srcRegion.y) {
|
||||
if (!banded) {
|
||||
normalizeColor(interpretation, rowDataByte);
|
||||
normalizeColor(interpretation, numBands, rowDataByte);
|
||||
}
|
||||
|
||||
// Subsample horizontal
|
||||
@@ -1907,12 +1965,8 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
// if (banded) {
|
||||
// // TODO: Normalize colors for tile (need to know tile region and sample model)
|
||||
// // Unfortunately, this will disable acceleration...
|
||||
// }
|
||||
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
case DataBuffer.TYPE_SHORT:
|
||||
/*for (int band = 0; band < bands; band++)*/ {
|
||||
@@ -1920,22 +1974,15 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
? ((DataBufferUShort) dataBuffer).getData(band)
|
||||
: ((DataBufferShort) dataBuffer).getData(band);
|
||||
|
||||
WritableRaster destChannel = banded
|
||||
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
|
||||
: raster;
|
||||
Raster srcChannel = banded
|
||||
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
|
||||
: tileRowRaster;
|
||||
|
||||
for (int row = startRow; row < startRow + rowsInTile; row++) {
|
||||
if (row >= srcRegion.y + srcRegion.height) {
|
||||
break; // We're done with this tile
|
||||
}
|
||||
|
||||
readFully(input, rowDataShort);
|
||||
input.readFully(rowDataShort, 0, rowDataShort.length);
|
||||
|
||||
if (row >= srcRegion.y) {
|
||||
normalizeColor(interpretation, rowDataShort);
|
||||
normalizeColor(interpretation, numBands, rowDataShort);
|
||||
|
||||
// Subsample horizontal
|
||||
subsampleRow(rowDataShort, srcRegion.x * numBands, colsInTile,
|
||||
@@ -1950,26 +1997,20 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_INT:
|
||||
/*for (int band = 0; band < bands; band++)*/ {
|
||||
int[] rowDataInt = ((DataBufferInt) dataBuffer).getData(band);
|
||||
|
||||
WritableRaster destChannel = banded
|
||||
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
|
||||
: raster;
|
||||
Raster srcChannel = banded
|
||||
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
|
||||
: tileRowRaster;
|
||||
|
||||
for (int row = startRow; row < startRow + rowsInTile; row++) {
|
||||
if (row >= srcRegion.y + srcRegion.height) {
|
||||
break; // We're done with this tile
|
||||
}
|
||||
|
||||
readFully(input, rowDataInt);
|
||||
input.readFully(rowDataInt, 0, rowDataInt.length);
|
||||
|
||||
if (row >= srcRegion.y) {
|
||||
normalizeColor(interpretation, rowDataInt);
|
||||
normalizeColor(interpretation, numBands, rowDataInt);
|
||||
|
||||
// Subsample horizontal
|
||||
subsampleRow(rowDataInt, srcRegion.x * numBands, colsInTile,
|
||||
@@ -1989,29 +2030,21 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
float[] rowDataFloat = ((DataBufferFloat) tileRowRaster.getDataBuffer()).getData(band);
|
||||
short[] rowDataShort = needsWidening ? new short[rowDataFloat.length] : null;
|
||||
|
||||
WritableRaster destChannel = banded
|
||||
? raster.createWritableChild(raster.getMinX(), raster.getMinY(), raster.getWidth(), raster.getHeight(), 0, 0, new int[] {band})
|
||||
: raster;
|
||||
Raster srcChannel = banded
|
||||
? tileRowRaster.createChild(tileRowRaster.getMinX(), 0, tileRowRaster.getWidth(), 1, 0, 0, new int[] {band})
|
||||
: tileRowRaster;
|
||||
|
||||
|
||||
for (int row = startRow; row < startRow + rowsInTile; row++) {
|
||||
if (row >= srcRegion.y + srcRegion.height) {
|
||||
break; // We're done with this tile
|
||||
}
|
||||
|
||||
if (needsWidening) {
|
||||
readFully(input, rowDataShort);
|
||||
toFloat(rowDataFloat, rowDataShort);
|
||||
input.readFully(rowDataShort, 0, rowDataShort.length);
|
||||
toFloat(rowDataShort, rowDataFloat);
|
||||
}
|
||||
else {
|
||||
readFully(input, rowDataFloat);
|
||||
input.readFully(rowDataFloat, 0, rowDataFloat.length);
|
||||
}
|
||||
|
||||
if (row >= srcRegion.y) {
|
||||
normalizeColor(interpretation, rowDataFloat);
|
||||
normalizeColor(interpretation, numBands, rowDataFloat);
|
||||
|
||||
// Subsample horizontal
|
||||
if (xSub != 1) {
|
||||
@@ -2033,7 +2066,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void toFloat(final float[] rowDataFloat, final short[] rowDataShort) {
|
||||
private void toFloat(final short[] rowDataShort, final float[] rowDataFloat) {
|
||||
for (int i = 0; i < rowDataFloat.length; i++) {
|
||||
rowDataFloat[i] = Half.shortBitsToFloat(rowDataShort[i]);
|
||||
}
|
||||
@@ -2050,46 +2083,103 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Candidate util method (with off/len + possibly byte order)
|
||||
private void readFully(final DataInput input, final float[] rowDataFloat) throws IOException {
|
||||
if (input instanceof ImageInputStream) {
|
||||
ImageInputStream imageInputStream = (ImageInputStream) input;
|
||||
imageInputStream.readFully(rowDataFloat, 0, rowDataFloat.length);
|
||||
private void normalizeColorPlanar(int photometricInterpretation, WritableRaster raster) throws IIOException {
|
||||
// TODO: Other transfer types?
|
||||
if (raster.getTransferType() != DataBuffer.TYPE_BYTE) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
for (int k = 0; k < rowDataFloat.length; k++) {
|
||||
rowDataFloat[k] = input.readFloat();
|
||||
}
|
||||
|
||||
byte[] pixel = null;
|
||||
|
||||
switch (photometricInterpretation) {
|
||||
case TIFFExtension.PHOTOMETRIC_YCBCR:
|
||||
|
||||
// Default: CCIR Recommendation 601-1: 299/1000, 587/1000 and 114/1000
|
||||
double[] coefficients = getValueAsDoubleArray(TIFF.TAG_YCBCR_COEFFICIENTS, "YCbCrCoefficients", false, 3);
|
||||
|
||||
// "Default" [0, 255, 128, 255, 128, 255] for YCbCr (real default is [0, 255, 0, 255, 0, 255] for RGB)
|
||||
double[] referenceBW = getValueAsDoubleArray(TIFF.TAG_REFERENCE_BLACK_WHITE, "ReferenceBlackWhite", false, 6);
|
||||
|
||||
if ((coefficients == null || Arrays.equals(coefficients, CCIR_601_1_COEFFICIENTS))
|
||||
&& (referenceBW == null || Arrays.equals(referenceBW, REFERENCE_BLACK_WHITE_YCC_DEFAULT))) {
|
||||
|
||||
// Fast, default conversion
|
||||
for (int y = 0; y < raster.getHeight(); y++) {
|
||||
for (int x = 0; x < raster.getWidth(); x++) {
|
||||
pixel = (byte[]) raster.getDataElements(x, y, pixel);
|
||||
YCbCrConverter.convertJPEGYCbCr2RGB(pixel, pixel, 0);
|
||||
raster.setDataElements(x, y, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If one of the values are null, we'll need the other here...
|
||||
if (coefficients == null) {
|
||||
coefficients = CCIR_601_1_COEFFICIENTS;
|
||||
}
|
||||
|
||||
if (referenceBW != null && Arrays.equals(referenceBW, REFERENCE_BLACK_WHITE_YCC_DEFAULT)) {
|
||||
referenceBW = null;
|
||||
}
|
||||
|
||||
for (int y = 0; y < raster.getHeight(); y++) {
|
||||
for (int x = 0; x < raster.getWidth(); x++) {
|
||||
pixel = (byte[]) raster.getDataElements(x, y, pixel);
|
||||
YCbCrConverter.convertYCbCr2RGB(pixel, pixel, coefficients, referenceBW, 0);
|
||||
raster.setDataElements(x, y, pixel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TIFFExtension.PHOTOMETRIC_CIELAB:
|
||||
case TIFFExtension.PHOTOMETRIC_ICCLAB:
|
||||
case TIFFExtension.PHOTOMETRIC_ITULAB:
|
||||
// TODO: White point may be encoded in separate tag
|
||||
CIELabColorConverter converter = new CIELabColorConverter(
|
||||
photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB
|
||||
? Illuminant.D65
|
||||
: Illuminant.D50
|
||||
);
|
||||
|
||||
float[] temp = new float[3];
|
||||
|
||||
for (int y = 0; y < raster.getHeight(); y++) {
|
||||
for (int x = 0; x < raster.getWidth(); x++) {
|
||||
pixel = (byte[]) raster.getDataElements(x, y, pixel);
|
||||
|
||||
float LStar = (pixel[0] & 0xff) * 100f / 255.0f;
|
||||
float aStar;
|
||||
float bStar;
|
||||
|
||||
if (photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB) {
|
||||
// -128...127
|
||||
aStar = pixel[1];
|
||||
bStar = pixel[2];
|
||||
}
|
||||
else {
|
||||
// Assumes same data for ICC and ITU (unsigned)
|
||||
// 0...255
|
||||
aStar = (pixel[1] & 0xff) - 128;
|
||||
bStar = (pixel[2] & 0xff) - 128;
|
||||
}
|
||||
|
||||
converter.toRGB(LStar, aStar, bStar, temp);
|
||||
|
||||
pixel[0] = (byte) temp[0];
|
||||
pixel[1] = (byte) temp[1];
|
||||
pixel[2] = (byte) temp[2];
|
||||
|
||||
raster.setDataElements(x, y, pixel);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Candidate util method (with off/len + possibly byte order)
|
||||
private void readFully(final DataInput input, final int[] rowDataInt) throws IOException {
|
||||
if (input instanceof ImageInputStream) {
|
||||
ImageInputStream imageInputStream = (ImageInputStream) input;
|
||||
imageInputStream.readFully(rowDataInt, 0, rowDataInt.length);
|
||||
}
|
||||
else {
|
||||
for (int k = 0; k < rowDataInt.length; k++) {
|
||||
rowDataInt[k] = input.readInt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Candidate util method (with off/len + possibly byte order)
|
||||
private void readFully(final DataInput input, final short[] rowDataShort) throws IOException {
|
||||
if (input instanceof ImageInputStream) {
|
||||
ImageInputStream imageInputStream = (ImageInputStream) input;
|
||||
imageInputStream.readFully(rowDataShort, 0, rowDataShort.length);
|
||||
}
|
||||
else {
|
||||
for (int k = 0; k < rowDataShort.length; k++) {
|
||||
rowDataShort[k] = input.readShort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeColor(int photometricInterpretation, byte[] data) throws IOException {
|
||||
private void normalizeColor(int photometricInterpretation, int numBands, byte[] data) throws IOException {
|
||||
switch (photometricInterpretation) {
|
||||
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
||||
// NOTE: Preserve WhiteIsZero for 1 bit monochrome, for CCITT compatibility
|
||||
@@ -2113,7 +2203,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
);
|
||||
float[] temp = new float[3];
|
||||
|
||||
for (int i = 0; i < data.length; i += 3) {
|
||||
for (int i = 0; i < data.length; i += numBands) {
|
||||
// Unsigned scaled form 0...100
|
||||
float LStar = (data[i] & 0xff) * 100f / 255.0f;
|
||||
float aStar;
|
||||
@@ -2174,7 +2264,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeColor(int photometricInterpretation, short[] data) throws IIOException {
|
||||
private void normalizeColor(int photometricInterpretation, int numBands, short[] data) throws IIOException {
|
||||
switch (photometricInterpretation) {
|
||||
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
||||
// Inverse values
|
||||
@@ -2197,7 +2287,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
float[] temp = new float[3];
|
||||
float scaleL = photometricInterpretation == TIFFExtension.PHOTOMETRIC_CIELAB ? 65535f : 65280f; // Is for ICC lab, assumes the same for ITU....
|
||||
|
||||
for (int i = 0; i < data.length; i += 3) {
|
||||
for (int i = 0; i < data.length; i += numBands) {
|
||||
// Unsigned scaled form 0...100
|
||||
float LStar = (data[i] & 0xffff) * 100.0f / scaleL;
|
||||
float aStar;
|
||||
@@ -2245,7 +2335,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
private void normalizeColor(int photometricInterpretation, int[] data) {
|
||||
private void normalizeColor(int photometricInterpretation, @SuppressWarnings("unused") int numBands, int[] data) {
|
||||
switch (photometricInterpretation) {
|
||||
case TIFFBaseline.PHOTOMETRIC_WHITE_IS_ZERO:
|
||||
// Inverse values
|
||||
@@ -2264,7 +2354,7 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void normalizeColor(int photometricInterpretation, float[] data) {
|
||||
private void normalizeColor(int photometricInterpretation, @SuppressWarnings("unused") int numBands, float[] data) {
|
||||
// TODO: Allow param to decide tone mapping strategy, like in the HDRImageReader
|
||||
clamp(data);
|
||||
|
||||
@@ -2643,10 +2733,11 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
});
|
||||
reader.addIIOReadProgressListener(new ProgressListenerBase() {
|
||||
private static final int MAX_W = 78;
|
||||
int lastProgress = 0;
|
||||
int lastProgress;
|
||||
|
||||
@Override
|
||||
public void imageStarted(ImageReader source, int imageIndex) {
|
||||
lastProgress = 0;
|
||||
System.out.print("[");
|
||||
}
|
||||
|
||||
@@ -2654,12 +2745,14 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
public void imageProgress(ImageReader source, float percentageDone) {
|
||||
int steps = ((int) (percentageDone * MAX_W) / 100);
|
||||
|
||||
for (int i = lastProgress; i < steps; i++) {
|
||||
System.out.print(".");
|
||||
}
|
||||
if (steps > lastProgress) {
|
||||
for (int i = lastProgress; i < steps; i++) {
|
||||
System.out.print(".");
|
||||
}
|
||||
|
||||
System.out.flush();
|
||||
lastProgress = steps;
|
||||
System.out.flush();
|
||||
lastProgress = steps;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -2693,8 +2786,14 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
int width = reader.getWidth(imageNo);
|
||||
int height = reader.getHeight(imageNo);
|
||||
// int width = reader.getWidth(imageNo);
|
||||
// int height = reader.getHeight(imageNo);
|
||||
if (param.canSetSourceRenderSize()) {
|
||||
int thumbSize = 512;
|
||||
float aspectRatio = reader.getAspectRatio(imageNo);
|
||||
param.setSourceRenderSize(aspectRatio > 1f ? new Dimension(thumbSize, (int) Math.ceil(thumbSize / aspectRatio))
|
||||
: new Dimension((int) Math.ceil(thumbSize * aspectRatio), thumbSize));
|
||||
}
|
||||
// param.setSourceRegion(new Rectangle(width / 4, height / 4, width / 2, height / 2));
|
||||
// param.setSourceRegion(new Rectangle(100, 300, 400, 400));
|
||||
// param.setSourceRegion(new Rectangle(95, 105, 100, 100));
|
||||
@@ -2706,18 +2805,18 @@ public final class TIFFImageReader extends ImageReaderBase {
|
||||
BufferedImage image = reader.read(imageNo, param);
|
||||
System.err.println("Read time: " + (System.currentTimeMillis() - start) + " ms");
|
||||
|
||||
IIOMetadata metadata = reader.getImageMetadata(imageNo);
|
||||
if (metadata != null) {
|
||||
if (metadata.getNativeMetadataFormatName() != null) {
|
||||
Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
|
||||
replaceBytesWithUndefined((IIOMetadataNode) tree);
|
||||
new XMLSerializer(System.out, "UTF-8").serialize(tree, false);
|
||||
}
|
||||
/*else*/
|
||||
if (metadata.isStandardMetadataFormatSupported()) {
|
||||
new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
}
|
||||
}
|
||||
// IIOMetadata metadata = reader.getImageMetadata(imageNo);
|
||||
// if (metadata != null) {
|
||||
// if (metadata.getNativeMetadataFormatName() != null) {
|
||||
// Node tree = metadata.getAsTree(metadata.getNativeMetadataFormatName());
|
||||
// replaceBytesWithUndefined((IIOMetadataNode) tree);
|
||||
// new XMLSerializer(System.out, "UTF-8").serialize(tree, false);
|
||||
// }
|
||||
// /*else*/
|
||||
// if (metadata.isStandardMetadataFormatSupported()) {
|
||||
// new XMLSerializer(System.out, "UTF-8").serialize(metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName), false);
|
||||
// }
|
||||
// }
|
||||
|
||||
System.err.println("image: " + image);
|
||||
|
||||
|
||||
+47
-65
@@ -35,7 +35,6 @@ import com.twelvemonkeys.imageio.ImageWriterBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFWriter;
|
||||
@@ -55,12 +54,18 @@ import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.*;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.io.DataOutput;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
@@ -104,8 +109,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// Support storing multiple images in one stream (multi-page TIFF)
|
||||
// Support more of the ImageIO metadata (ie. compression from metadata, etc)
|
||||
|
||||
private static final Rational STANDARD_DPI = new Rational(72);
|
||||
|
||||
/**
|
||||
* Flag for active sequence writing
|
||||
*/
|
||||
@@ -176,6 +179,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
throw new IllegalArgumentException("Unknown bit/bandOffsets for sample model: " + sampleModel);
|
||||
}
|
||||
|
||||
short offsetType = tiffWriter.offsetSize() == 4 ? TIFF.TYPE_LONG : TIFF.TYPE_LONG8;
|
||||
|
||||
Map<Integer, Entry> entries = new LinkedHashMap<>();
|
||||
// Copy metadata to output
|
||||
Directory metadataIFD = metadata.getIFD();
|
||||
@@ -189,9 +194,9 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
// TODO: RowsPerStrip - can be entire image (or even 2^32 -1), but it's recommended to write "about 8K bytes" per strip
|
||||
entries.put(TIFF.TAG_ROWS_PER_STRIP, new TIFFEntry(TIFF.TAG_ROWS_PER_STRIP, renderedImage.getHeight()));
|
||||
// StripByteCounts - for no compression, entire image data...
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, -1)); // Updated later
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, offsetType, -1)); // Updated later
|
||||
// StripOffsets - can be offset to single strip only
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, -1)); // Updated later
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, offsetType, -1)); // Updated later
|
||||
|
||||
// TODO: If tiled, write tile indexes etc
|
||||
// Depending on param.getTilingMode
|
||||
@@ -205,10 +210,10 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
long ifdSize = tiffWriter.computeIFDSize(entries.values());
|
||||
long stripOffset = streamPosition + tiffWriter.offsetSize() + ifdSize + tiffWriter.offsetSize();
|
||||
long stripByteCount = ((long) renderedImage.getWidth() * renderedImage.getHeight() * pixelSize + 7L) / 8L;
|
||||
long stripByteCount = renderedImage.getHeight() * (((long) renderedImage.getWidth() * pixelSize + 7L) / 8L);
|
||||
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, offsetType, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, offsetType, stripByteCount));
|
||||
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
|
||||
nextIFDPointerOffset = imageOutput.getStreamPosition();
|
||||
@@ -259,8 +264,8 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
// Update IFD0-pointer, and write IFD
|
||||
if (compression != TIFFBaseline.COMPRESSION_NONE) {
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, TIFF.TYPE_LONG, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, TIFF.TYPE_LONG, stripByteCount));
|
||||
entries.put(TIFF.TAG_STRIP_OFFSETS, new TIFFEntry(TIFF.TAG_STRIP_OFFSETS, offsetType, stripOffset));
|
||||
entries.put(TIFF.TAG_STRIP_BYTE_COUNTS, new TIFFEntry(TIFF.TAG_STRIP_BYTE_COUNTS, offsetType, stripByteCount));
|
||||
|
||||
long ifdPointer = tiffWriter.writeIFD(entries.values(), imageOutput); // NOTE: Writer takes care of ordering tags
|
||||
|
||||
@@ -411,7 +416,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
case TIFFExtension.COMPRESSION_LZW:
|
||||
stream = IIOUtil.createStreamAdapter(imageOutput);
|
||||
stream = new EncoderStream(stream, new LZWEncoder(((image.getTileWidth() * samplesPerPixel * bitPerSample + 7) / 8) * image.getTileHeight()));
|
||||
stream = new EncoderStream(stream, new LZWEncoder((((long) image.getTileWidth() * samplesPerPixel * bitPerSample + 7) / 8) * image.getTileHeight()));
|
||||
if (entries.containsKey(TIFF.TAG_PREDICTOR) && entries.get(TIFF.TAG_PREDICTOR).getValue().equals(TIFFExtension.PREDICTOR_HORIZONTAL_DIFFERENCING)) {
|
||||
stream = new HorizontalDifferencingStream(stream, image.getTileWidth(), samplesPerPixel, bitPerSample, imageOutput.getByteOrder());
|
||||
}
|
||||
@@ -526,8 +531,6 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
final int sampleSize = renderedImage.getSampleModel().getSampleSize(0);
|
||||
final int numBands = renderedImage.getSampleModel().getNumBands();
|
||||
|
||||
final ByteBuffer buffer = ByteBuffer.allocate((tileWidth * numBands * sampleSize + 7) / 8);
|
||||
|
||||
for (int yTile = minTileY; yTile < maxYTiles; yTile++) {
|
||||
for (int xTile = minTileX; xTile < maxXTiles; xTile++) {
|
||||
final Raster tile = renderedImage.getTile(xTile, yTile);
|
||||
@@ -565,21 +568,17 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
for (int s = 0; s < numBands; s++) {
|
||||
if (sampleSize == 8 || shift == 0) {
|
||||
// Normal interleaved/planar case
|
||||
buffer.put((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff));
|
||||
stream.writeByte((byte) (dataBuffer.getElem(b, xOff + bandOffsets[s]) & 0xff));
|
||||
|
||||
}
|
||||
else {
|
||||
// "Packed" case
|
||||
buffer.put((byte) (rowBuffer.getElem(b, x - offsetX + bandOffsets[s]) & 0xff));
|
||||
stream.writeByte((byte) (rowBuffer.getElem(b, x - offsetX + bandOffsets[s]) & 0xff));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flushBuffer(buffer, stream);
|
||||
|
||||
if (stream instanceof DataOutputStream) {
|
||||
DataOutputStream dataOutputStream = (DataOutputStream) stream;
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
flushStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,25 +586,21 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
|
||||
case DataBuffer.TYPE_USHORT:
|
||||
case DataBuffer.TYPE_SHORT:
|
||||
final int shortStride = stride / 2;
|
||||
if (numComponents == 1) {
|
||||
// System.err.println("Writing USHORT -> " + numBands * 2 + "_BYTES");
|
||||
|
||||
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
|
||||
for (int y = offsetY; y < tileHeight + offsetY; y++) {
|
||||
int yOff = y * stride / 2;
|
||||
final int yOff = y * shortStride;
|
||||
|
||||
for (int x = offsetX; x < tileWidth + offsetX; x++) {
|
||||
final int xOff = yOff + x;
|
||||
int xOff = yOff + x;
|
||||
|
||||
buffer.putShort((short) (dataBuffer.getElem(b, xOff) & 0xffff));
|
||||
stream.writeShort((short) (dataBuffer.getElem(b, xOff) & 0xffff));
|
||||
}
|
||||
|
||||
flushBuffer(buffer, stream);
|
||||
|
||||
if (stream instanceof DataOutputStream) {
|
||||
DataOutputStream dataOutputStream = (DataOutputStream) stream;
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
flushStream(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -636,26 +631,21 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
break;
|
||||
|
||||
case DataBuffer.TYPE_INT:
|
||||
// TODO: This is incorrect for 32 bits/sample, only works for packed (INT_(A)RGB)
|
||||
// TODO: This is incorrect for general 32 bits/sample, only works for packed (INT_(A)RGB) and single channel
|
||||
final int intStride = stride / 4;
|
||||
if (1 == numComponents) {
|
||||
// System.err.println("Writing INT -> " + numBands * 4 + "_BYTES");
|
||||
|
||||
for (int b = 0; b < dataBuffer.getNumBanks(); b++) {
|
||||
for (int y = offsetY; y < tileHeight + offsetY; y++) {
|
||||
int yOff = y * stride / 4;
|
||||
int yOff = y * intStride;
|
||||
|
||||
for (int x = offsetX; x < tileWidth + offsetX; x++) {
|
||||
final int xOff = yOff + x;
|
||||
int xOff = yOff + x;
|
||||
|
||||
buffer.putInt(dataBuffer.getElem(b, xOff));
|
||||
stream.writeInt(dataBuffer.getElem(b, xOff));
|
||||
}
|
||||
|
||||
flushBuffer(buffer, stream);
|
||||
|
||||
if (stream instanceof DataOutputStream) {
|
||||
DataOutputStream dataOutputStream = (DataOutputStream) stream;
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
flushStream(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -671,15 +661,11 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
int element = dataBuffer.getElem(b, xOff);
|
||||
|
||||
for (int s = 0; s < numBands; s++) {
|
||||
buffer.put((byte) ((element >> bitOffsets[s]) & 0xff));
|
||||
stream.writeByte((byte) ((element >> bitOffsets[s]) & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
flushBuffer(buffer, stream);
|
||||
if (stream instanceof DataOutputStream) {
|
||||
DataOutputStream dataOutputStream = (DataOutputStream) stream;
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
flushStream(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,11 +676,7 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Need to flush/start new compression for each row, for proper LZW/PackBits/Deflate/ZLib
|
||||
if (stream instanceof DataOutputStream) {
|
||||
DataOutputStream dataOutputStream = (DataOutputStream) stream;
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
flushStream(stream);
|
||||
|
||||
// TODO: Report better progress
|
||||
processImageProgress((100f * (yTile + 1)) / maxYTiles);
|
||||
@@ -708,12 +690,12 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
processImageComplete();
|
||||
}
|
||||
|
||||
// TODO: Would be better to solve this on stream level... But writers would then have to explicitly flush the buffer before done.
|
||||
private void flushBuffer(final ByteBuffer buffer, final DataOutput stream) throws IOException {
|
||||
buffer.flip();
|
||||
stream.write(buffer.array(), buffer.arrayOffset(), buffer.remaining());
|
||||
|
||||
buffer.clear();
|
||||
private void flushStream(DataOutput stream) throws IOException {
|
||||
// Need to flush/start new compression for each row, for proper LZW/PackBits/Deflate/ZLib compression
|
||||
if (stream instanceof DataOutputStream) {
|
||||
DataOutputStream dataOutputStream = (DataOutputStream) stream;
|
||||
dataOutputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
// Metadata
|
||||
@@ -893,14 +875,14 @@ public final class TIFFImageWriter extends ImageWriterBase {
|
||||
case TIFF.TAG_ARTIST:
|
||||
case TIFF.TAG_HOST_COMPUTER:
|
||||
case TIFF.TAG_COPYRIGHT:
|
||||
// Extension
|
||||
// Extension
|
||||
case TIFF.TAG_DOCUMENT_NAME:
|
||||
case TIFF.TAG_PAGE_NAME:
|
||||
case TIFF.TAG_X_POSITION:
|
||||
case TIFF.TAG_Y_POSITION:
|
||||
case TIFF.TAG_PAGE_NUMBER:
|
||||
case TIFF.TAG_XMP:
|
||||
// Private/Custom
|
||||
// Private/Custom
|
||||
case TIFF.TAG_IPTC:
|
||||
case TIFF.TAG_PHOTOSHOP:
|
||||
case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA:
|
||||
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* 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.tiff;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Input stream that provides on-the-fly upsampling of TIFF subsampled YCbCr samples.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: YCbCrUpsamplerStream.java,v 1.0 31.01.13 09:25 haraldk Exp$
|
||||
*/
|
||||
final class YCbCrPlanarUpsamplerStream extends FilterInputStream {
|
||||
|
||||
private final int horizChromaSub;
|
||||
private final int vertChromaSub;
|
||||
private final int yCbCrPos;
|
||||
private final int columns;
|
||||
|
||||
private final int units;
|
||||
private final byte[] decodedRows;
|
||||
int decodedLength;
|
||||
int decodedPos;
|
||||
|
||||
private final byte[] buffer;
|
||||
int bufferLength;
|
||||
int bufferPos;
|
||||
|
||||
public YCbCrPlanarUpsamplerStream(final InputStream stream, final int[] chromaSub, final int yCbCrPos, final int columns) {
|
||||
super(Validate.notNull(stream, "stream"));
|
||||
|
||||
Validate.notNull(chromaSub, "chromaSub");
|
||||
Validate.isTrue(chromaSub.length == 2, "chromaSub.length != 2");
|
||||
|
||||
this.horizChromaSub = chromaSub[0];
|
||||
this.vertChromaSub = chromaSub[1];
|
||||
this.yCbCrPos = yCbCrPos;
|
||||
this.columns = columns;
|
||||
|
||||
units = (columns + horizChromaSub - 1) / horizChromaSub; // If columns % horizChromasSub != 0...
|
||||
// ...each coded row will be padded to fill unit
|
||||
decodedRows = new byte[columns * vertChromaSub];
|
||||
buffer = new byte[units];
|
||||
}
|
||||
|
||||
private void fetch() throws IOException {
|
||||
if (bufferPos >= bufferLength) {
|
||||
int pos = 0;
|
||||
int read;
|
||||
|
||||
// This *SHOULD* read an entire row of units into the buffer, otherwise decodeRows will throw EOFException
|
||||
while (pos < buffer.length && (read = in.read(buffer, pos, buffer.length - pos)) > 0) {
|
||||
pos += read;
|
||||
}
|
||||
|
||||
bufferLength = pos;
|
||||
bufferPos = 0;
|
||||
}
|
||||
|
||||
if (bufferLength > 0) {
|
||||
decodeRows();
|
||||
}
|
||||
else {
|
||||
decodedLength = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private void decodeRows() throws EOFException {
|
||||
decodedLength = decodedRows.length;
|
||||
|
||||
for (int u = 0; u < units; u++) {
|
||||
if (u >= bufferLength) {
|
||||
throw new EOFException("Unexpected end of stream");
|
||||
}
|
||||
|
||||
// Decode one unit
|
||||
byte c = buffer[u];
|
||||
|
||||
for (int y = 0; y < vertChromaSub; y++) {
|
||||
for (int x = 0; x < horizChromaSub; x++) {
|
||||
// Skip padding at end of row
|
||||
int column = horizChromaSub * u + x;
|
||||
if (column >= columns) {
|
||||
break;
|
||||
}
|
||||
|
||||
int pixelOff = column + columns * y;
|
||||
decodedRows[pixelOff] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bufferPos = bufferLength;
|
||||
decodedPos = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return decodedRows[decodedPos++] & 0xff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int read = Math.min(decodedLength - decodedPos, len);
|
||||
System.arraycopy(decodedRows, decodedPos, b, off, read);
|
||||
decodedPos += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long n) throws IOException {
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (decodedPos >= decodedLength) {
|
||||
fetch();
|
||||
|
||||
if (decodedLength < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int skipped = (int) Math.min(decodedLength - decodedPos, n);
|
||||
decodedPos += skipped;
|
||||
|
||||
return skipped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
}
|
||||
-4
@@ -213,8 +213,4 @@ final class YCbCrUpsamplerStream extends FilterInputStream {
|
||||
public synchronized void reset() throws IOException {
|
||||
throw new IOException("mark/reset not supported");
|
||||
}
|
||||
|
||||
private static byte clamp(int val) {
|
||||
return (byte) Math.max(0, Math.min(255, val));
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -324,6 +324,10 @@ public class TIFFImageMetadataTest {
|
||||
dimensionNode.appendChild(verticalPixelSize);
|
||||
verticalPixelSize.setAttribute("value", String.valueOf(300 / 25.4));
|
||||
|
||||
IIOMetadataNode orientation = new IIOMetadataNode("ImageOrientation");
|
||||
dimensionNode.appendChild(orientation);
|
||||
orientation.setAttribute("value", "FlipV");
|
||||
|
||||
metadata.mergeTree(standardFormat, newTree);
|
||||
|
||||
Directory ifd = metadata.getIFD();
|
||||
@@ -332,6 +336,8 @@ public class TIFFImageMetadataTest {
|
||||
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_X_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_Y_RESOLUTION));
|
||||
assertEquals(new Rational(300), ifd.getEntryById(TIFF.TAG_Y_RESOLUTION).getValue());
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_ORIENTATION));
|
||||
assertEquals(TIFFExtension.ORIENTATION_BOTLEFT, ((Number) ifd.getEntryById(TIFF.TAG_ORIENTATION).getValue()).intValue());
|
||||
|
||||
// Should keep DPI as unit
|
||||
assertNotNull(ifd.getEntryById(TIFF.TAG_RESOLUTION_UNIT));
|
||||
|
||||
+52
-7
@@ -44,7 +44,7 @@ import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ColorSpace;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
@@ -56,7 +56,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
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.mockito.AdditionalMatchers.and;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@@ -142,6 +145,8 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample
|
||||
// RGB Interleaved (PlanarConfiguration: 1)
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-04.tif"), new Dimension(73, 43)), // RGB 4 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample
|
||||
@@ -171,7 +176,22 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
new TestData(getClassLoaderResource("/tiff/jpeg-lossless-24bit-rgb.tif"), new Dimension(512, 512)), // Lossless JPEG RGB, 8 bit/sample
|
||||
// Custom PIXTIFF ZIP (Compression: 50013)
|
||||
new TestData(getClassLoaderResource("/tiff/pixtiff/40-8bit-gray-zip.tif"), new Dimension(801, 1313)), // ZIP Gray, 8 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)) // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
|
||||
new TestData(getClassLoaderResource("/tiff/part.tif"), new Dimension(50, 50)), // Gray/BlackIsZero, uncompressed, striped signed int (SampleFormat 2)
|
||||
// Planar YCbCr full chroma
|
||||
new TestData(getClassLoaderResource("/tiff/lab-a-8.tiff"), new Dimension(589, 340)), // Lab + Alpha, uncompressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/lab-a-16.tiff"), new Dimension(589, 340)), // Lab + Alpha, 16 bit uncompressed, striped
|
||||
// Planar YCbCr full chroma
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv444-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv444-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, LZW compressed, striped
|
||||
// Planar YCbCr subsampled
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv422-bt601-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, Rec.601 coefficients, uncompressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv422-bt601-lzw.tif"), new Dimension(256, 64)), // YCbCr, Rec.601 coefficients,LZW compressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv422-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv422-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients,LZW compressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv420-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv420-jpeg-lzw.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients,LZW compressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv410-jpeg-uncompressed.tif"), new Dimension(256, 64)), // YCbCr, JPEG coefficients, uncompressed, striped
|
||||
new TestData(getClassLoaderResource("/tiff/planar-yuv410-jpeg-lzw.tif"), new Dimension(256, 64)) // YCbCr, JPEG coefficients,LZW compressed, striped
|
||||
);
|
||||
}
|
||||
|
||||
@@ -194,6 +214,8 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-08.tif"), new Dimension(73, 43)), // Palette 8 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-palette-16.tif"), new Dimension(73, 43)), // Palette 16 bit/sample
|
||||
// RGB Interleaved (PlanarConfiguration: 1)
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-04.tif"), new Dimension(73, 43)), // RGB 4 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-08.tif"), new Dimension(73, 43)), // RGB 8 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-10.tif"), new Dimension(73, 43)), // RGB 10 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-12.tif"), new Dimension(73, 43)), // RGB 12 bit/sample
|
||||
@@ -221,9 +243,6 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
|
||||
private List<TestData> getUnsupportedTestData() {
|
||||
return Arrays.asList(
|
||||
// RGB Interleaved (PlanarConfiguration: 1)
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-contig-04.tif"), new Dimension(73, 43)), // RGB 4 bit/sample
|
||||
// RGB Planar (PlanarConfiguration: 2)
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-02.tif"), new Dimension(73, 43)), // RGB 2 bit/sample
|
||||
new TestData(getClassLoaderResource("/tiff/depth/flower-rgb-planar-04.tif"), new Dimension(73, 43)) // RGB 4 bit/sample
|
||||
@@ -876,6 +895,32 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
assertSubsampledImageDataEquals("Subsampled image data does not match expected", image, subsampled, param);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadLittleEndian4444ARGB() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
|
||||
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/tiff/little-endian-rgba-4444.tiff"))) {
|
||||
reader.setInput(stream);
|
||||
|
||||
BufferedImage image = null;
|
||||
try {
|
||||
image = reader.read(0);
|
||||
}
|
||||
catch (IOException e) {
|
||||
failBecause("Image could not be read", e);
|
||||
}
|
||||
|
||||
assertNotNull(image);
|
||||
assertEquals(589, image.getWidth());
|
||||
assertEquals(340, image.getHeight());
|
||||
|
||||
assertRGBEquals("Red", 0xffff1111, image.getRGB(124, 42), 4);
|
||||
assertRGBEquals("Green", 0xff66ee11, image.getRGB(476, 100), 4);
|
||||
assertRGBEquals("Yellow", 0xffffff00, image.getRGB(312, 186), 4);
|
||||
assertRGBEquals("Blue", 0xff1155dd, image.getRGB(366, 192), 4);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadUnsupported() throws IOException {
|
||||
ImageReader reader = createReader();
|
||||
@@ -886,7 +931,7 @@ public class TIFFImageReaderTest extends ImageReaderAbstractTest<TIFFImageReader
|
||||
for (int i = 0; i < data.getImageCount(); i++) {
|
||||
try {
|
||||
reader.read(i);
|
||||
fail("Sample should be moved from unsupported to normal test case");
|
||||
fail("Sample should be moved from unsupported to normal test case: " + data);
|
||||
}
|
||||
catch (IIOException e) {
|
||||
assertThat(e.getMessage().toLowerCase(), containsString("unsupported"));
|
||||
|
||||
+125
-5
@@ -36,6 +36,7 @@ import com.twelvemonkeys.imageio.metadata.tiff.Rational;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import com.twelvemonkeys.io.NullOutputStream;
|
||||
@@ -43,7 +44,12 @@ import com.twelvemonkeys.io.NullOutputStream;
|
||||
import org.junit.Test;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
import javax.imageio.*;
|
||||
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.event.IIOWriteProgressListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
@@ -53,10 +59,15 @@ import javax.imageio.stream.FileCacheImageOutputStream;
|
||||
import javax.imageio.stream.FileImageOutputStream;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.RenderedImage;
|
||||
import java.io.*;
|
||||
import java.awt.color.*;
|
||||
import java.awt.image.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
@@ -152,6 +163,29 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
assertEquals(resolutionValue, yResolution.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testByteCountsForUncompressed() throws IOException {
|
||||
// See issue #863
|
||||
BufferedImage image = ImageTypeSpecifiers.createGrayscale(2, DataBuffer.TYPE_BYTE)
|
||||
.createBufferedImage(43, 2); // 43 + 2 = 86 bits/line;
|
||||
|
||||
ImageWriter writer = createWriter();
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
try (ImageOutputStream stream = ImageIO.createImageOutputStream(buffer)) {
|
||||
writer.setOutput(stream);
|
||||
writer.write(image);
|
||||
}
|
||||
|
||||
try (ImageInputStream stream = new ByteArrayImageInputStream(buffer.toByteArray())) {
|
||||
Directory entries = new TIFFReader().read(stream);
|
||||
Entry byteCountsEntry = entries.getEntryById(TIFF.TAG_STRIP_BYTE_COUNTS);
|
||||
assertNotNull(byteCountsEntry);
|
||||
|
||||
assertEquals(22, ((Number) byteCountsEntry.getValue()).intValue()); // 86 bits/line, needs 88 bits * 2 => 22 bytes
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteWithCustomSoftwareNative() throws IOException {
|
||||
String softwareString = "12M TIFF Test 1.0 (build $foo$)";
|
||||
@@ -638,7 +672,12 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
int maxH = Math.min(300, image.getHeight());
|
||||
for (int y = 0; y < maxH; y++) {
|
||||
for (int x = 0; x < image.getWidth(); x++) {
|
||||
assertRGBEquals(String.format("Pixel differ: @%d,%d", x, y), orig.getRGB(x, y), image.getRGB(x, y), 0);
|
||||
try {
|
||||
assertRGBEquals("", orig.getRGB(x, y), image.getRGB(x, y), 0);
|
||||
}
|
||||
catch (AssertionError err) {
|
||||
fail(String.format("Pixel differ: @%d,%d %s", x, y, err.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1266,6 +1305,50 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortOverflowHuge() throws IOException {
|
||||
int width = 34769;
|
||||
int height = 33769;
|
||||
|
||||
// Create a huge image without actually allocating memory...
|
||||
DataBuffer buffer = new NullDataBuffer(DataBuffer.TYPE_USHORT, width * height);
|
||||
WritableRaster raster = Raster.createWritableRaster(new ComponentSampleModel(buffer.getDataType(), width, height, 1, width, new int[] {0}), buffer, null);
|
||||
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, buffer.getDataType());
|
||||
BufferedImage image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
|
||||
|
||||
// Write image without any exception
|
||||
TIFFImageWriter writer = createWriter();
|
||||
try (ImageOutputStream stream = new NullImageOutputStream()) {
|
||||
writer.setOutput(stream);
|
||||
writer.write(image);
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIntOverflowHuge() throws IOException {
|
||||
int width = 34769;
|
||||
int height = 33769;
|
||||
|
||||
// Create a huge image without actually allocating memory...
|
||||
DataBuffer buffer = new NullDataBuffer(DataBuffer.TYPE_INT, width * height);
|
||||
WritableRaster raster = Raster.createWritableRaster(new ComponentSampleModel(buffer.getDataType(), width, height, 1, width, new int[] {0}), buffer, null);
|
||||
ColorModel colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, Transparency.OPAQUE, buffer.getDataType());
|
||||
BufferedImage image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
|
||||
|
||||
// Write image without any exception
|
||||
TIFFImageWriter writer = createWriter();
|
||||
try (ImageOutputStream stream = new NullImageOutputStream()) {
|
||||
writer.setOutput(stream);
|
||||
writer.write(image);
|
||||
}
|
||||
finally {
|
||||
writer.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImageInfo {
|
||||
final int width;
|
||||
final int height;
|
||||
@@ -1278,4 +1361,41 @@ public class TIFFImageWriterTest extends ImageWriterAbstractTest<TIFFImageWriter
|
||||
this.compression = compression;
|
||||
}
|
||||
}
|
||||
|
||||
// Special purpose output stream that acts as a sink
|
||||
private static final class NullImageOutputStream extends ImageOutputStreamImpl {
|
||||
@Override
|
||||
public void write(int b) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Special purpose data buffer that does not require memory, to allow very large images
|
||||
private static final class NullDataBuffer extends DataBuffer {
|
||||
public NullDataBuffer(int type, int size) {
|
||||
super(type, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getElem(int bank, int i) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setElem(int bank, int i, int val) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
|
||||
|
||||
+30
-22
@@ -31,6 +31,28 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.webp;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.ColorConvertOp;
|
||||
import java.awt.image.ColorModel;
|
||||
import java.awt.image.DataBuffer;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.color.ColorProfiles;
|
||||
import com.twelvemonkeys.imageio.color.ColorSpaces;
|
||||
@@ -45,23 +67,6 @@ import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
|
||||
import com.twelvemonkeys.imageio.util.ProgressListenerBase;
|
||||
import com.twelvemonkeys.imageio.util.RasterUtils;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import java.awt.*;
|
||||
import java.awt.color.ICC_ColorSpace;
|
||||
import java.awt.color.ICC_Profile;
|
||||
import java.awt.image.*;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* WebPImageReader
|
||||
*/
|
||||
@@ -72,6 +77,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
private LSBBitReader lsbBitReader;
|
||||
|
||||
// Either VP8_, VP8L or VP8X chunk
|
||||
private long fileSize;
|
||||
private VP8xChunk header;
|
||||
private ICC_Profile iccProfile;
|
||||
private final List<AnimationFrame> frames = new ArrayList<>();
|
||||
@@ -82,6 +88,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
|
||||
@Override
|
||||
protected void resetMembers() {
|
||||
fileSize = -1;
|
||||
header = null;
|
||||
iccProfile = null;
|
||||
lsbBitReader = null;
|
||||
@@ -119,7 +126,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
RIFFChunk frame = frames.isEmpty() ? header : frames.get(frames.size() - 1);
|
||||
imageInput.seek(frame.offset + frame.length);
|
||||
|
||||
while (imageInput.getStreamPosition() < imageInput.length()) {
|
||||
while (imageInput.getStreamPosition() < fileSize) {
|
||||
int nextChunk = imageInput.readInt();
|
||||
long chunkLength = imageInput.readUnsignedInt();
|
||||
long chunkStart = imageInput.getStreamPosition();
|
||||
@@ -184,7 +191,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
throw new IIOException(String.format("Not a WebP file, invalid 'RIFF' magic: '%s'", fourCC(riff)));
|
||||
}
|
||||
|
||||
imageInput.readUnsignedInt(); // Skip file size NOTE: LITTLE endian!
|
||||
fileSize = 8 + imageInput.readUnsignedInt(); // 8 + RIFF container length (LITTLE endian) == file size
|
||||
|
||||
int webp = imageInput.readInt();
|
||||
if (webp != WebP.WEBP_MAGIC) {
|
||||
@@ -282,7 +289,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
|
||||
if (header.containsICCP) {
|
||||
// ICCP chunk must be first chunk, if present
|
||||
while (iccProfile == null && imageInput.getStreamPosition() < imageInput.length()) {
|
||||
while (iccProfile == null && imageInput.getStreamPosition() < fileSize) {
|
||||
int nextChunk = imageInput.readInt();
|
||||
long chunkLength = imageInput.readUnsignedInt();
|
||||
long chunkStart = imageInput.getStreamPosition();
|
||||
@@ -305,6 +312,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
System.out.println("file size: " + fileSize + " (stream length: " + imageInput.length() + ")");
|
||||
System.out.println("header: " + header);
|
||||
}
|
||||
}
|
||||
@@ -422,7 +430,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
imageInput.seek(header.offset + header.length);
|
||||
readVP8Extended(destination, param, imageInput.length());
|
||||
readVP8Extended(destination, param, fileSize);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -591,7 +599,7 @@ final class WebPImageReader extends ImageReaderBase {
|
||||
// TODO: WebP spec says possible EXIF and XMP chunks are always AFTER image data
|
||||
imageInput.seek(header.offset + header.length);
|
||||
|
||||
while (imageInput.getStreamPosition() < imageInput.length()) {
|
||||
while (imageInput.getStreamPosition() < fileSize) {
|
||||
int nextChunk = imageInput.readInt();
|
||||
long chunkLength = imageInput.readUnsignedInt();
|
||||
long chunkStart = imageInput.getStreamPosition();
|
||||
|
||||
+34
-7
@@ -1,20 +1,22 @@
|
||||
package com.twelvemonkeys.imageio.plugins.webp;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import org.junit.Test;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
|
||||
|
||||
/**
|
||||
* WebPImageReaderTest
|
||||
@@ -102,4 +104,29 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadFromUnknownStreamLength() throws IOException {
|
||||
// See #672, image was not decoded and returned all black, when the stream length was unknown (-1).
|
||||
WebPImageReader reader = createReader();
|
||||
|
||||
try (ImageInputStream stream = new MemoryCacheImageInputStream(getClassLoaderResource("/webp/photo-iccp-adobergb.webp").openStream()) {
|
||||
@Override public long length() {
|
||||
return -1;
|
||||
}
|
||||
}) {
|
||||
reader.setInput(stream);
|
||||
|
||||
// We'll read a small portion of the image into a destination type that use sRGB
|
||||
ImageReadParam param = new ImageReadParam();
|
||||
param.setDestinationType(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR));
|
||||
param.setSourceRegion(new Rectangle(10, 10, 20, 20));
|
||||
|
||||
BufferedImage image = reader.read(0, param);
|
||||
assertRGBEquals("RGB values differ, image all black?", 0xFFEC9800, image.getRGB(5, 5), 8);
|
||||
}
|
||||
finally {
|
||||
reader.dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>imageio-xwd</artifactId>
|
||||
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>TwelveMonkeys</name>
|
||||
<description>TwelveMonkeys parent POM</description>
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
<parent>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys</artifactId>
|
||||
<version>3.8.2</version>
|
||||
<version>3.8.4-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
Reference in New Issue
Block a user