Compare commits

...

21 Commits

Author SHA1 Message Date
Harald Kuhr dbb7c07695 [maven-release-plugin] prepare release twelvemonkeys-3.9.4 2022-11-21 18:51:26 +01:00
Harald Kuhr 6840f31fa3 #712 Core: Fix possible OOM situation in new stream implementation
(cherry picked from commit 8f5c1b409f)
2022-11-21 18:49:06 +01:00
Harald Kuhr debf7d0207 #714 PNM: Add support for writing TYPE_INT_* images + implementation of WriterSpi.canEncode
(cherry picked from commit 26981513d8)
2022-11-21 18:49:06 +01:00
Harald Kuhr 0538db7103 #713 PSD: Broken uncompressed reading from stream w/unknown length
(cherry picked from commit da800be8c8)
2022-11-21 18:44:44 +01:00
snyk-bot 1e981242ad fix: imageio/imageio-batik/pom.xml to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JAVA-ORGAPACHEXMLGRAPHICS-3063442
- https://snyk.io/vuln/SNYK-JAVA-ORGAPACHEXMLGRAPHICS-3063691

(cherry picked from commit 304d050bc3)
2022-11-21 18:43:40 +01:00
Harald Kuhr 135a631bcc #708 PSD: No longer emit warning for '8B64' (64 bit/long) resources.
(cherry picked from commit 0443172666)
2022-11-21 18:43:40 +01:00
Harald Kuhr f2624d5193 [maven-release-plugin] prepare for next development iteration 2022-10-20 16:22:15 +02:00
Harald Kuhr ada3a84bec [maven-release-plugin] prepare release twelvemonkeys-3.9.3 2022-10-20 16:22:10 +02:00
Harald Kuhr 7e7aaa293e #707 WebP: Fix Alpha support the correct way...
(cherry picked from commit cee2663f06)
2022-10-20 16:10:05 +02:00
Harald Kuhr 5d623cce9f #707 WebP: Fix Alpha support
(cherry picked from commit 8f44cfc43c)
2022-10-20 16:00:45 +02:00
Harald Kuhr 055838aaaf [maven-release-plugin] prepare for next development iteration 2022-10-20 14:08:11 +02:00
Harald Kuhr a8327c3c67 [maven-release-plugin] prepare release twelvemonkeys-3.9.2 2022-10-20 14:08:06 +02:00
Harald Kuhr 36c91f67e4 #704 Fix LSBBitReader to avoid back/forth seeking that invalidates buffer
(cherry picked from commit 8a240aac68)
2022-10-20 14:06:19 +02:00
Harald Kuhr 4cc53d822f [maven-release-plugin] prepare for next development iteration 2022-10-19 20:56:12 +02:00
Harald Kuhr 9875de0383 [maven-release-plugin] prepare release twelvemonkeys-3.9.1 2022-10-19 20:56:08 +02:00
Harald Kuhr 6ed858a4ca #704 Tiny performance improvement + code clean-up
(cherry picked from commit 61424f33b6)
2022-10-19 20:46:45 +02:00
Harald Kuhr 38192ae835 Code clean-up.
(cherry picked from commit c7b9b1fadd)
2022-10-19 20:46:45 +02:00
Harald Kuhr b5e8853e6b Set versions for 3.9 bugfix branch. 2022-10-18 20:49:43 +02:00
Harald Kuhr a98224e652 #705 No longer closes streams we didn't open
(cherry picked from commit ab08ec1e0d)
2022-10-18 20:34:27 +02:00
Harald Kuhr 73a58266be ...and removed System.out.. Ouch...
(cherry picked from commit cbe78dc67f)
2022-10-18 20:34:26 +02:00
Harald Kuhr edd523534c Fixed typo...
(cherry picked from commit c9e11f171f)
2022-10-18 20:34:26 +02:00
70 changed files with 1057 additions and 310 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>common-image</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>common-io</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>common-lang</artifactId>
<packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId>
+2 -2
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
@@ -17,7 +17,7 @@
<properties>
<project.jpms.module.name>com.twelvemonkeys.imageio.batik</project.jpms.module.name>
<batik.version>1.15</batik.version>
<batik.version>1.16</batik.version>
</properties>
<build>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.stream;
import javax.imageio.stream.ImageInputStreamImpl;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -52,6 +53,10 @@ import static java.lang.Math.max;
* for shorter reads, like single byte or bit reads.
*/
final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private static final Closeable CLOSEABLE_STUB = new Closeable() {
@Override public void close() {}
};
static final int DEFAULT_BUFFER_SIZE = 8192;
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
@@ -63,6 +68,7 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private final byte[] integralCacheArray = integralCache.array();
private SeekableByteChannel channel;
private Closeable closeable;
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}.
@@ -86,49 +92,62 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
* @throws SecurityException if a security manager is installed, and it denies read access to the file.
*/
public BufferedChannelImageInputStream(final Path file) throws IOException {
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ));
this(FileChannel.open(notNull(file, "file"), StandardOpenOption.READ), true);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code RandomAccessFile}.
* <p>
* Closing this stream will close the {@code RandomAccessFile}.
* </p>
*
* @param file a {@code RandomAccessFile} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}.
*/
public BufferedChannelImageInputStream(final RandomAccessFile file) {
// Assumption: Closing the FileChannel will also close its backing RandomAccessFile
// (it does in the OpenJDK implementation, and it makes sense, although I can't see this is documented behaviour).
this(notNull(file, "file").getChannel());
// Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
this(notNull(file, "file").getChannel(), true);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
* <p>
* Closing this stream will close the {@code FileInputStream}.
* Closing this stream will <em>not</em> close the {@code FileInputStream}.
* </p>
*
* @param inputStream a {@code FileInputStream} to read from.
* @throws IllegalArgumentException if {@code inputStream} is {@code null}.
*/
public BufferedChannelImageInputStream(final FileInputStream inputStream) {
// Assumption: Closing the FileChannel will also close its backing FileInputStream (it does in the OpenJDK implementation, although I can't see this is documented).
this(notNull(inputStream, "inputStream").getChannel());
this(notNull(inputStream, "inputStream").getChannel(), false);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
* <p>
* Closing this stream will close the {@code SeekableByteChannel}.
* Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
* </p>
*
* @param channel a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}.
*/
public BufferedChannelImageInputStream(final SeekableByteChannel channel) {
this(notNull(channel, "channel"), false);
}
/**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code Cache}.
* <p>
* Closing this stream will close the {@code Cache}.
* </p>
*
* @param cache a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}.
*/
BufferedChannelImageInputStream(final Cache cache) {
this(notNull(cache, "cache"), true);
}
private BufferedChannelImageInputStream(final SeekableByteChannel channel, boolean closeChannelOnClose) {
this.channel = notNull(channel, "channel");
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
}
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
@@ -246,8 +265,14 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
buffer = null;
byteBuffer = null;
channel.close();
channel = null;
try {
closeable.close();
}
finally {
closeable = null;
}
}
// Need to override the readShort(), readInt() and readLong() methods,
@@ -315,9 +340,9 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
public void flushBefore(final long pos) throws IOException {
super.flushBefore(pos);
if (channel instanceof MemoryCache) {
if (channel instanceof Cache) {
// In case of memory cache, free up memory
((MemoryCache) channel).flushBefore(pos);
((Cache) channel).flushBefore(pos);
}
}
}
@@ -53,7 +53,7 @@ public final class BufferedInputStreamImageInputStreamSpi extends ImageInputStre
}
// Otherwise, create a cache for backwards seeking
return new BufferedChannelImageInputStream(useCacheFile ? new DiskCache(channel, cacheDir): new MemoryCache(channel));
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(channel, cacheDir) : new MemoryCache(channel));
}
throw new IllegalArgumentException("Expected input of type InputStream: " + input);
@@ -48,18 +48,18 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
private final int dataOffset;
private final int dataLength;
public ByteArrayImageInputStream(final byte[] pData) {
this(pData, 0, pData != null ? pData.length : -1);
public ByteArrayImageInputStream(final byte[] data) {
this(data, 0, data != null ? data.length : -1);
}
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) {
data = notNull(pData, "data");
dataOffset = isBetween(0, pData.length, offset, "offset");
dataLength = isBetween(0, pData.length - offset, length, "length");
public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
this.data = notNull(data, "data");
dataOffset = isMax(data.length, offset, "offset");
dataLength = isMax(data.length - offset, length, "length");
}
private static int isBetween(final int low, final int high, final int value, final String name) {
return isTrue(value >= low && value <= high, value, String.format("%s out of range [%d, %d]: %d", name, low, high, value));
private static int isMax(final int high, final int value, final String name) {
return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
}
public int read() throws IOException {
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
return data[((int) streamPos++) + dataOffset] & 0xff;
}
public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException {
public int read(byte[] buffer, int offset, int len) throws IOException {
if (streamPos >= dataLength) {
return -1;
}
int length = (int) Math.min(this.dataLength - streamPos, pLength);
int length = (int) Math.min(dataLength - streamPos, len);
bitOffset = 0;
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length);
System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
streamPos += length;
return length;
@@ -0,0 +1,7 @@
package com.twelvemonkeys.imageio.stream;
import java.nio.channels.SeekableByteChannel;
interface Cache extends SeekableByteChannel {
void flushBefore(long pos);
}
@@ -26,19 +26,19 @@ import static java.nio.file.StandardOpenOption.WRITE;
// the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively
// large files into memory.
final class DiskCache implements SeekableByteChannel {
final class FileCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private final FileChannel cache;
private final ReadableByteChannel channel;
// TODO: Perhaps skip this constructor?
DiskCache(InputStream stream, File cacheDir) throws IOException {
FileCache(InputStream stream, File cacheDir) throws IOException {
// Stream will be closed with channel, documented behavior
this(Channels.newChannel(notNull(stream, "stream")), cacheDir);
}
public DiskCache(ReadableByteChannel channel, File cacheDir) throws IOException {
public FileCache(ReadableByteChannel channel, File cacheDir) throws IOException {
this.channel = notNull(channel, "channel");
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
@@ -65,12 +65,7 @@ final class DiskCache implements SeekableByteChannel {
@Override
public void close() throws IOException {
try {
cache.close();
}
finally {
channel.close();
}
cache.close();
}
@Override
@@ -110,5 +105,8 @@ final class DiskCache implements SeekableByteChannel {
public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException();
}
@Override public void flushBefore(long pos) {
}
}
@@ -13,12 +13,16 @@ import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.min;
public final class MemoryCache implements SeekableByteChannel {
final class MemoryCache implements Cache {
final static int BLOCK_SIZE = 1 << 13;
private static final byte[] NULL_BLOCK = new byte[0];
private final List<byte[]> cache = new ArrayList<>();
private final ReadableByteChannel channel;
private int maxBlock = Integer.MAX_VALUE;
private long length;
private long position;
private long start;
@@ -34,12 +38,14 @@ public final class MemoryCache implements SeekableByteChannel {
byte[] fetchBlock() throws IOException {
long currPos = position;
long index = currPos / BLOCK_SIZE;
if (index >= Integer.MAX_VALUE) {
throw new IOException("Memory cache max size exceeded");
}
if (index > maxBlock) {
return NULL_BLOCK;
}
while (index >= cache.size()) {
byte[] block;
@@ -51,7 +57,14 @@ public final class MemoryCache implements SeekableByteChannel {
}
cache.add(block);
length += readBlock(block);
int bytesRead = readBlock(block);
length += bytesRead;
if (bytesRead < BLOCK_SIZE) {
// Last block, EOF found
maxBlock = (int) index;
return block;
}
}
return cache.get((int) index);
@@ -63,7 +76,7 @@ public final class MemoryCache implements SeekableByteChannel {
while (wrapped.hasRemaining()) {
int count = channel.read(wrapped);
if (count == -1) {
// Last block
// Last block, EOF found
break;
}
}
@@ -78,23 +91,18 @@ public final class MemoryCache implements SeekableByteChannel {
@Override
public void close() throws IOException {
try {
cache.clear();
}
finally {
channel.close();
}
cache.clear();
}
@Override
public int read(ByteBuffer dest) throws IOException {
byte[] buffer = fetchBlock();
int bufferPos = (int) (position % BLOCK_SIZE);
if (position >= length) {
return -1;
}
int bufferPos = (int) (position % BLOCK_SIZE);
int len = min(dest.remaining(), (int) min(BLOCK_SIZE - bufferPos, length - position));
dest.put(buffer, bufferPos, len);
@@ -135,7 +143,8 @@ public final class MemoryCache implements SeekableByteChannel {
throw new NonWritableChannelException();
}
void flushBefore(long pos) {
@Override
public void flushBefore(long pos) {
if (pos < start) {
throw new IndexOutOfBoundsException("pos < flushed position");
}
@@ -53,20 +53,20 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
/**
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
*
* @param pStream the underlying stream
* @param pLength the maximum length to read from the stream.
* Note that {@code pStream} may contain less than this maximum number of bytes.
* @param stream the underlying stream
* @param length the maximum length to read from the stream.
* Note that {@code stream} may contain less than this maximum number of bytes.
*
* @throws IOException if {@code pStream}'s position can't be determined.
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
* @throws IOException if {@code stream}'s position can't be determined.
* @throws IllegalArgumentException if {@code stream == null} or {@code length < 0}
*/
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
Validate.notNull(pStream, "stream");
Validate.isTrue(pLength >= 0, pLength, "length < 0: %d");
public SubImageInputStream(final ImageInputStream stream, final long length) throws IOException {
Validate.notNull(stream, "stream");
Validate.isTrue(length >= 0, length, "length < 0: %d");
stream = pStream;
startPos = pStream.getStreamPosition();
length = pLength;
this.stream = stream;
this.startPos = stream.getStreamPosition();
this.length = length;
}
public int read() throws IOException {
@@ -84,14 +84,14 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
}
}
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
public int read(final byte[] bytes, final int off, final int len) throws IOException {
if (streamPos >= length) { // Local EOF
return -1;
}
// Safe cast, as pLength can never cause int overflow
int length = (int) Math.min(pLength, this.length - streamPos);
int count = stream.read(pBytes, pOffset, length);
// Safe cast, as len can never cause int overflow
int length = (int) Math.min(len, this.length - streamPos);
int count = stream.read(bytes, off, length);
if (count >= 0) {
streamPos += count;
@@ -113,18 +113,18 @@ public final class SubImageInputStream extends ImageInputStreamImpl {
}
@Override
public void seek(final long pPosition) throws IOException {
if (pPosition < getFlushedPosition()) {
public void seek(final long position) throws IOException {
if (position < getFlushedPosition()) {
throw new IndexOutOfBoundsException("pos < flushedPosition");
}
stream.seek(startPos + pPosition);
streamPos = pPosition;
stream.seek(startPos + position);
streamPos = position;
}
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
@SuppressWarnings("MethodDoesntCallSuperMethod")
@Override
protected void finalize() throws Throwable {
protected void finalize() {
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
}
}
@@ -80,7 +80,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
// Otherwise revert to cached
InputStream urlStream = url.openStream();
return new BufferedChannelImageInputStream(useCacheFile ? new DiskCache(urlStream, cacheDir) : new MemoryCache(urlStream));
return new BufferedChannelImageInputStream(useCacheFile ? new FileCache(urlStream, cacheDir) : new MemoryCache(urlStream));
}
throw new IllegalArgumentException("Expected input of type URL: " + input);
@@ -57,7 +57,7 @@ import static org.mockito.Mockito.verify;
* @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$
*/
// TODO: Remove this test, and instead test the disk cache directly!
public class BufferedChannelImageInputStreamDiskCacheTest {
public class BufferedChannelImageInputStreamFileCacheTest {
private final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) {
@@ -68,7 +68,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test
public void testCreate() throws IOException {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(new ByteArrayInputStream(new byte[0]), null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(new ByteArrayInputStream(new byte[0]), null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
}
}
@@ -76,7 +76,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test
public void testCreateNullStream() throws IOException {
try {
new DiskCache((InputStream) null, null);
new FileCache((InputStream) null, null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
@@ -90,7 +90,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test
public void testCreateNullChannel() throws IOException {
try {
new DiskCache((ReadableByteChannel) null, null);
new FileCache((ReadableByteChannel) null, null);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException expected) {
@@ -106,7 +106,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) {
@@ -122,7 +122,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024];
@@ -141,7 +141,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7];
@@ -159,7 +159,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9];
@@ -180,7 +180,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2];
@@ -198,7 +198,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit());
}
@@ -212,7 +212,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 64; i++) {
stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i));
@@ -228,7 +228,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong();
// Create stream
try (ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
for (int i = 1; i <= 60; i++) {
stream.seek(0);
stream.setBitOffset(i % 8);
@@ -244,7 +244,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) {
@@ -282,7 +282,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) {
@@ -320,7 +320,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) {
@@ -357,7 +357,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
stream.seek(1000);
assertEquals(-1, stream.read());
@@ -406,11 +406,11 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test
public void testClose() throws IOException {
// Create wrapper stream
InputStream mock = mock(InputStream.class);
ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(mock, null));
Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close();
verify(mock, only()).close();
verify(cache, only()).close();
}
@Test
@@ -422,7 +422,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes);
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(input, null))) {
try (BufferedChannelImageInputStream stream = new BufferedChannelImageInputStream(new FileCache(input, null))) {
byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
@@ -402,15 +402,33 @@ public class BufferedChannelImageInputStreamMemoryCacheTest {
assertEquals(-1, stream.read());
}
}
@Test
public void testSeekWayPastEOFShouldNotThrowOOME() throws IOException {
byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes);
try (final ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(input))) {
stream.seek(Integer.MAX_VALUE * 4L * 512L); // ~4 TB
assertEquals(-1, stream.read()); // No OOME should happen...
stream.seek(0);
for (byte value : bytes) {
assertEquals(value, stream.readByte());
}
assertEquals(-1, stream.read());
}
}
@Test
public void testClose() throws IOException {
// Create wrapper stream
InputStream mock = mock(InputStream.class);
ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(mock));
Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close();
verify(mock, only()).close();
verify(cache, only()).close();
}
@Test
@@ -47,7 +47,7 @@ import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
/**
@@ -403,14 +403,11 @@ public class BufferedChannelImageInputStreamTest {
@Test
public void testCloseChannel() throws IOException {
// NOTE: As the stream-based constructor is chained to the channel-based one,
// we'll rely on the fact that closing the channel will close the stream.
SeekableByteChannel mock = mock(SeekableByteChannel.class);
ImageInputStream stream = new BufferedChannelImageInputStream(mock);
SeekableByteChannel channel = mock(SeekableByteChannel.class);
ImageInputStream stream = new BufferedChannelImageInputStream(channel);
stream.close();
verify(mock, only()).close();
verify(channel, never()).close();
}
@Test
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
@@ -13,7 +13,6 @@ final class PCXMetadata extends StandardImageMetadataSupport {
}
private static PlanarConfiguration planarConfiguration(PCXHeader header) {
System.out.println("header = " + header);
return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null;
}
@@ -25,6 +24,6 @@ final class PCXMetadata extends StandardImageMetadataSupport {
return "RLE";
}
return "Uknown";
return "Unknown";
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
@@ -30,8 +30,6 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.ImageWriterBase;
import org.w3c.dom.NodeList;
import javax.imageio.IIOImage;
@@ -40,7 +38,7 @@ import javax.imageio.metadata.IIOMetadataFormatImpl;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.DataBuffer;
import java.awt.image.*;
import java.io.IOException;
import java.util.Locale;
@@ -53,11 +51,7 @@ abstract class HeaderWriter {
this.imageOutput = imageOutput;
}
public static void write(final IIOImage image, final ImageWriterBase writer, final ImageOutputStream imageOutput) throws IOException {
createHeaderWriter(writer.getFormatName(), imageOutput).writeHeader(image, writer.getOriginatingProvider());
}
private static HeaderWriter createHeaderWriter(final String formatName, final ImageOutputStream imageOutput) {
static HeaderWriter createHeaderWriter(final String formatName, final ImageOutputStream imageOutput) {
if (formatName.equals("pam")) {
return new PAMHeaderWriter(imageOutput);
}
@@ -83,25 +77,36 @@ abstract class HeaderWriter {
return image.hasRaster() ? image.getRaster().getNumBands() : image.getRenderedImage().getSampleModel().getNumBands();
}
protected final SampleModel getSampleModel(final IIOImage image) {
return image.hasRaster() ? image.getRaster().getSampleModel() : image.getRenderedImage().getSampleModel();
}
protected int getMaxVal(final IIOImage image) {
int transferType = getTransferType(image);
if (transferType == DataBuffer.TYPE_BYTE) {
return PNM.MAX_VAL_8BIT;
}
else if (transferType == DataBuffer.TYPE_USHORT) {
return PNM.MAX_VAL_16BIT;
}
// else if (transferType == DataBuffer.TYPE_INT) {
// TODO: Support TYPE_INT through conversion, if number of channels is 3 or 4 (TYPE_INT_RGB, TYPE_INT_ARGB)
// }
else {
throw new IllegalArgumentException("Unsupported data type: " + transferType);
switch (transferType) {
case DataBuffer.TYPE_BYTE:
return PNM.MAX_VAL_8BIT;
case DataBuffer.TYPE_USHORT:
return PNM.MAX_VAL_16BIT;
case DataBuffer.TYPE_INT:
// We support TYPE_INT through conversion, if number of channels is 3 or 4 (TYPE_INT_RGB, TYPE_INT_ARGB)
SampleModel sampleModel = getSampleModel(image);
int numBands = sampleModel.getNumBands();
if (sampleModel instanceof SinglePixelPackedSampleModel && (numBands == 3 || numBands == 4)) {
return PNM.MAX_VAL_8BIT;
}
// ...else fall through
default:
throw new IllegalArgumentException("Unsupported data type: " + transferType);
}
}
protected final int getTransferType(final IIOImage image) {
return image.hasRaster() ? image.getRaster().getTransferType() : image.getRenderedImage().getSampleModel().getTransferType();
return image.hasRaster() ? image.getRaster().getTransferType() : image.getRenderedImage().getSampleModel().getTransferType();
}
protected final void writeComments(final IIOMetadata metadata, final ImageWriterSpi provider) throws IOException {
@@ -120,5 +125,4 @@ abstract class HeaderWriter {
}
}
}
}
@@ -30,6 +30,8 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOImage;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
@@ -44,6 +46,8 @@ final class PAMHeaderWriter extends HeaderWriter {
@Override
public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException {
TupleType tupleType = tupleType(image);
// Write PAM magic
imageOutput.writeShort(PNM.PAM);
imageOutput.write('\n');
@@ -52,12 +56,19 @@ final class PAMHeaderWriter extends HeaderWriter {
// Write width/height and number of channels
imageOutput.write(String.format("WIDTH %s\nHEIGHT %s\n", getWidth(image), getHeight(image)).getBytes(UTF_8));
imageOutput.write(String.format("DEPTH %s\n", getNumBands(image)).getBytes(UTF_8));
// TODO: maxSample (8 or16 bit)
imageOutput.write(String.format("MAXVAL %s\n", getMaxVal(image)).getBytes(UTF_8));
// TODO: Determine tuple type based on input color model and image data
TupleType tupleType = getNumBands(image) > 3 ? TupleType.RGB_ALPHA : TupleType.RGB;
imageOutput.write(String.format("TUPLTYPE %s\nENDHDR\n", tupleType).getBytes(UTF_8));
}
private static TupleType tupleType(IIOImage image) {
TupleType tupleType = image.hasRaster()
? TupleType.forPAM(image.getRaster())
: TupleType.forPAM(ImageTypeSpecifiers.createFromRenderedImage(image.getRenderedImage()));
if (tupleType == null) {
throw new IllegalArgumentException("Unknown TupleType for " + (image.hasRaster() ? image.getRaster() : image.getRenderedImage()));
}
return tupleType;
}
}
@@ -45,12 +45,11 @@ public final class PAMImageWriterSpi extends ImageWriterSpiBase {
super(new PAMProviderInfo());
}
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
// TODO: FixMe
return true;
public boolean canEncodeImage(final ImageTypeSpecifier type) {
return TupleType.forPAM(type) != null;
}
public ImageWriter createWriterInstance(final Object pExtension) {
public ImageWriter createWriterInstance(final Object extension) {
return new PNMImageWriter(this);
}
@@ -69,7 +69,7 @@ final class PFMHeaderParser extends HeaderParser {
public PNMHeader parse() throws IOException {
int width = 0;
int height = 0;
float maxSample = tupleType == TupleType.BLACKANDWHITE_WHITE_IS_ZERO ? 1 : 0; // PBM has no maxSample line
float maxSample = 0;
List<String> comments = new ArrayList<>();
@@ -77,7 +77,7 @@ final class PFMHeaderParser extends HeaderParser {
String line = input.readLine();
if (line == null) {
throw new IIOException("Unexpeced end of stream");
throw new IIOException("Unexpected end of stream");
}
int commentStart = line.indexOf('#');
@@ -30,6 +30,8 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers;
import javax.imageio.IIOImage;
import javax.imageio.spi.ImageWriterSpi;
import javax.imageio.stream.ImageOutputStream;
@@ -45,8 +47,7 @@ final class PNMHeaderWriter extends HeaderWriter {
@Override
public void writeHeader(final IIOImage image, final ImageWriterSpi provider) throws IOException {
// Write P4/P5/P6 magic (Support only RAW formats for now; if we are to support PLAIN formats, pass parameter)
// TODO: Determine PBM, PBM or PPM based on input color model and image data?
short type = PNM.PPM;
short type = type(image);
imageOutput.writeShort(type);
imageOutput.write('\n');
@@ -61,4 +62,35 @@ final class PNMHeaderWriter extends HeaderWriter {
imageOutput.write(String.format("%s\n", getMaxVal(image)).getBytes(UTF_8));
}
}
private short type(IIOImage image) {
TupleType type = tupleType(image);
if (type != null) {
switch (type) {
case BLACKANDWHITE_WHITE_IS_ZERO:
return PNM.PBM;
case GRAYSCALE:
return PNM.PGM;
case RGB:
return PNM.PPM;
default:
// fall through...
}
}
throw new IllegalArgumentException("Unsupported tupleType: " + type);
}
private static TupleType tupleType(IIOImage image) {
TupleType tupleType = image.hasRaster()
? TupleType.forPNM(image.getRaster())
: TupleType.forPNM(ImageTypeSpecifiers.createFromRenderedImage(image.getRenderedImage()));
if (tupleType == null) {
throw new IllegalArgumentException("Unknown TupleType for " + (image.hasRaster() ? image.getRaster() : image.getRenderedImage()));
}
return tupleType;
}
}
@@ -31,14 +31,17 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.ImageWriterBase;
import com.twelvemonkeys.imageio.util.RasterUtils;
import javax.imageio.*;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import javax.imageio.stream.ImageOutputStream;
import java.awt.image.*;
import java.io.File;
import java.io.IOException;
@@ -69,7 +72,7 @@ public final class PNMImageWriter extends ImageWriterBase {
// TODO: Issue warning if streamMetadata is non-null?
// TODO: Issue warning if IIOImage contains thumbnails or other data we can't store?
HeaderWriter.write(image, this, imageOutput);
writeHeader(image, this, imageOutput);
// TODO: Sub region
// TODO: Subsampling
@@ -80,16 +83,20 @@ public final class PNMImageWriter extends ImageWriterBase {
processImageComplete();
}
private void writeHeader(final IIOImage image, final ImageWriterBase writer, final ImageOutputStream imageOutput) throws IOException {
HeaderWriter.createHeaderWriter(writer.getFormatName(), imageOutput).writeHeader(image, writer.getOriginatingProvider());
}
private void writeImageData(final IIOImage image) throws IOException {
// - dump data as is (or convert, if TYPE_INT_xxx)
// - dump data as is (or convert, if TYPE_INT_xxx
// Enforce RGB/CMYK order for such data!
// TODO: Loop over x/y tiles, using 0,0 is only valid for BufferedImage
// TODO: PNM/PAM does not support tiling, we must iterate all tiles along the x-axis for each row we write
Raster tile = image.hasRaster() ? image.getRaster() : image.getRenderedImage().getTile(0, 0);
tile = tile.getTransferType() == DataBuffer.TYPE_INT ? RasterUtils.asByteRaster(tile) : tile;
SampleModel sampleModel = tile.getSampleModel();
DataBuffer dataBuffer = tile.getDataBuffer();
int tileWidth = tile.getWidth();
@@ -48,12 +48,11 @@ public final class PNMImageWriterSpi extends ImageWriterSpiBase {
super(new PNMProviderInfo());
}
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
// TODO: FixMe: Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
return true;
public boolean canEncodeImage(final ImageTypeSpecifier type) {
return TupleType.forPNM(type) != null;
}
public ImageWriter createWriterInstance(final Object pExtension) {
public ImageWriter createWriterInstance(final Object extension) {
return new PNMImageWriter(this);
}
@@ -30,7 +30,10 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import javax.imageio.ImageTypeSpecifier;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
enum TupleType {
// Official:
@@ -80,4 +83,150 @@ enum TupleType {
public boolean isValidMaxSample(int maxSample) {
return maxSample >= minMaxSample && maxSample <= maxMaxSample;
}
static TupleType forPNM(Raster raster) {
return filterPNM(forPAM(raster));
}
static TupleType forPNM(ImageTypeSpecifier type) {
return filterPNM(forPAM(type));
}
private static TupleType filterPNM(TupleType tupleType) {
if (tupleType == null) {
return null;
}
switch (tupleType) {
case BLACKANDWHITE:
return BLACKANDWHITE_WHITE_IS_ZERO;
case GRAYSCALE:
case RGB:
return tupleType;
default:
return null;
}
}
static TupleType forPAM(Raster raster) {
SampleModel sampleModel = raster.getSampleModel();
switch (sampleModel.getTransferType()) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_INT:
// B/W, Gray or RGB
int bands = sampleModel.getNumBands();
if (bands == 1 && sampleModel.getSampleSize(0) == 1) {
return TupleType.BLACKANDWHITE;
}
else if (bands == 2 && sampleModel.getSampleSize(0) == 1 && sampleModel.getSampleSize(1) == 1) {
return TupleType.BLACKANDWHITE_ALPHA;
}
// We can only write 8 or 16 bits/band
if (!(sampleModel.getSampleSize(0) == 8 || sampleModel.getSampleSize(0) == 16)) {
return null;
}
for (int i = 1; i < bands; i++) {
if (sampleModel.getSampleSize(0) != sampleModel.getSampleSize(i)) {
return null;
}
}
if (bands == 1) {
return TupleType.GRAYSCALE;
}
else if (bands == 2) {
return TupleType.GRAYSCALE_ALPHA;
}
else if (bands == 3) {
return TupleType.RGB;
}
else if (bands == 4) {
return TupleType.RGB_ALPHA;
}
// ...else fall through...
}
return null;
}
static TupleType forPAM(ImageTypeSpecifier type) {
// Support only 1 bit b/w, 8-16 bit gray and 8-16 bit/sample RGB
switch (type.getBufferedImageType()) {
// 1 bit b/w or b/w + a
case BufferedImage.TYPE_BYTE_BINARY:
switch (type.getNumBands()) {
case 1:
return type.getBitsPerBand(0) == 1 ? TupleType.BLACKANDWHITE : null;
case 2:
return type.getBitsPerBand(0) == 2 || type.getBitsPerBand(0) == 1 && type.getBitsPerBand(1) == 1 ? TupleType.BLACKANDWHITE_ALPHA : null;
default:
return null;
}
// Gray
case BufferedImage.TYPE_BYTE_GRAY:
case BufferedImage.TYPE_USHORT_GRAY:
return TupleType.GRAYSCALE;
// RGB
case BufferedImage.TYPE_3BYTE_BGR:
case BufferedImage.TYPE_INT_RGB:
case BufferedImage.TYPE_INT_BGR:
return TupleType.RGB;
// RGBA
case BufferedImage.TYPE_4BYTE_ABGR:
case BufferedImage.TYPE_4BYTE_ABGR_PRE:
case BufferedImage.TYPE_INT_ARGB:
case BufferedImage.TYPE_INT_ARGB_PRE:
return TupleType.RGB_ALPHA;
default:
// BYTE, USHORT or INT (packed)
switch (type.getSampleModel().getTransferType()) {
case DataBuffer.TYPE_BYTE:
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_INT:
// Gray or RGB
ColorModel colorModel = type.getColorModel();
if (!(colorModel instanceof IndexColorModel)) {
ColorSpace cs = colorModel.getColorSpace();
// We can only write 8 or 16 bits/band
int bands = type.getNumBands();
if (!(type.getBitsPerBand(0) == 8 || type.getBitsPerBand(0) == 16)) {
return null;
}
for (int i = 1; i < bands; i++) {
if (type.getBitsPerBand(0) != type.getBitsPerBand(i)) {
return null;
}
}
if (cs.getType() == ColorSpace.TYPE_GRAY && bands == 1) {
return TupleType.GRAYSCALE;
}
else if (cs.getType() == ColorSpace.TYPE_GRAY && colorModel.hasAlpha() && bands == 2) {
return TupleType.GRAYSCALE_ALPHA;
}
else if (cs.getType() == ColorSpace.TYPE_RGB && bands == 3) {
return TupleType.RGB;
}
else if (cs.getType() == ColorSpace.TYPE_RGB && colorModel.hasAlpha() && bands == 4) {
return TupleType.RGB_ALPHA;
}
else if (cs.getType() == ColorSpace.TYPE_CMYK && bands == 4) {
return TupleType.CMYK;
}
else if (cs.getType() == ColorSpace.TYPE_CMYK && colorModel.hasAlpha() && bands == 5) {
return TupleType.CMYK_ALPHA;
}
// ...else fall through...
}
}
}
return null;
}
}
@@ -30,15 +30,23 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* PAMImageWriterSpiTest.
@@ -65,4 +73,85 @@ public class PAMImageWriterSpiTest {
public void getOutputTypes() {
assertNotNull(spi.getOutputTypes());
}
@Test
public void canEncodeImageBinary() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY)));
}
@Test
public void canNotEncodeImageIndexed() {
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_INDEXED)));
}
@Test
public void canEncodeImageGray() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_USHORT_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY)));
}
@Test
public void canEncodeImageGrayAlpha() {
ComponentColorModel grayAlphaByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaByte, grayAlphaByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel grayAlphaUShort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaUShort, grayAlphaUShort.createCompatibleSampleModel(1, 1))));
}
@Test
public void canEncodeImageRGB() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)));
}
@Test
public void canEncodeImageARGB() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)));
}
@Test
public void canEncodeImageCMYK() {
ComponentColorModel cmykByte = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykByte, cmykByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel cmykUShort = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykUShort, cmykUShort.createCompatibleSampleModel(1, 1))));
ComponentColorModel cmykAlphaByte = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykAlphaByte, cmykAlphaByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel cmykAlphaUShort = new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
assertTrue(spi.canEncodeImage(new ImageTypeSpecifier(cmykAlphaUShort, cmykAlphaUShort.createCompatibleSampleModel(1, 1))));
}
@Test
public void canNotEncodeImageNonGrayOrRGB() {
ComponentColorModel xyzByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzByte, xyzByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel xyzUshort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzUshort, xyzUshort.createCompatibleSampleModel(1, 1))));
}
}
@@ -0,0 +1,37 @@
package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.color.ColorSpaces;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import java.util.Arrays;
import java.util.List;
public class PAMImageWriterTest extends ImageWriterAbstractTest<PNMImageWriter> {
// NOTE: It's the same writer, however, the different SPI configures PAM mode, and enables extra formats
@Override
protected ImageWriterSpi createProvider() {
return new PAMImageWriterSpi();
}
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_USHORT_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR),
new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_BGR),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_ARGB_PRE),
new BufferedImage(new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT), Raster.createInterleavedRaster(DataBuffer.TYPE_USHORT, 10, 10, 2, null), false, null),
new BufferedImage(new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE), Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, 10, 10, 4, null), false, null),
new BufferedImage(new ComponentColorModel(ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT), Raster.createInterleavedRaster(DataBuffer.TYPE_USHORT, 10, 10, 5, null), false, null)
);
}
}
@@ -32,13 +32,19 @@ package com.twelvemonkeys.imageio.plugins.pnm;
import org.junit.Test;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.ImageWriter;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.*;
import java.awt.color.*;
import java.awt.image.*;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassExists;
import static com.twelvemonkeys.imageio.spi.ReaderWriterProviderInfoTest.assertClassesExist;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* PNMImageWriterSpiTest.
@@ -65,4 +71,70 @@ public class PNMImageWriterSpiTest {
public void getOutputTypes() {
assertNotNull(spi.getOutputTypes());
}
@Test
public void canEncodeImageBinary() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_BINARY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_BINARY)));
}
@Test
public void canNotEncodeImageIndexed() {
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_INDEXED)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_INDEXED)));
}
@Test
public void canEncodeImageGray() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_BYTE_GRAY)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_USHORT_GRAY)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_USHORT_GRAY)));
}
@Test
public void canNotEncodeImageGrayAlpha() {
ComponentColorModel grayAlphaByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaByte, grayAlphaByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel grayAlphaUShort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), true, false, Transparency.TRANSLUCENT, DataBuffer.TYPE_USHORT);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(grayAlphaUShort, grayAlphaUShort.createCompatibleSampleModel(1, 1))));
}
@Test
public void canEncodeImageRGB() {
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_RGB)));
assertTrue(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_BGR)));
assertTrue(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_BGR)));
}
@Test
public void canNotEncodeImageARGB() {
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)));
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR_PRE)));
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB)));
assertFalse(spi.canEncodeImage(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE)));
assertFalse(spi.canEncodeImage(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_INT_ARGB_PRE)));
}
@Test
public void canNotEncodeImageNonGrayOrRGB() {
ComponentColorModel xyzByte = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzByte, xyzByte.createCompatibleSampleModel(1, 1))));
ComponentColorModel xyzUshort = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_CIEXYZ), false, false, Transparency.OPAQUE, DataBuffer.TYPE_USHORT);
assertFalse(spi.canEncodeImage(new ImageTypeSpecifier(xyzUshort, xyzUshort.createCompatibleSampleModel(1, 1))));
}
}
@@ -3,8 +3,7 @@ package com.twelvemonkeys.imageio.plugins.pnm;
import com.twelvemonkeys.imageio.util.ImageWriterAbstractTest;
import javax.imageio.spi.ImageWriterSpi;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.awt.image.*;
import java.util.Arrays;
import java.util.List;
@@ -17,10 +16,12 @@ public class PNMImageWriterTest extends ImageWriterAbstractTest<PNMImageWriter>
@Override
protected List<? extends RenderedImage> getTestData() {
return Arrays.asList(
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_BINARY),
new BufferedImage(100, 100, BufferedImage.TYPE_3BYTE_BGR),
new BufferedImage(100, 100, BufferedImage.TYPE_BYTE_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_USHORT_GRAY),
new BufferedImage(100, 100, BufferedImage.TYPE_4BYTE_ABGR)
new BufferedImage(100, 100, BufferedImage.TYPE_INT_BGR),
new BufferedImage(10, 10, BufferedImage.TYPE_INT_BGR)
);
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
@@ -944,7 +944,7 @@ public final class PSDImageReader extends ImageReaderBase {
if (metadata.layerInfo == null) {
while (imageInput.getStreamPosition() + 12 < metadata.layerAndMaskInfoStart + layerAndMaskInfoLength) {
int resSig = imageInput.readInt();
if (resSig != PSD.RESOURCE_TYPE) {
if (resSig != PSD.RESOURCE_TYPE && resSig != PSD.RESOURCE_TYPE_LONG) {
processWarningOccurred(String.format("Bad resource alignment, expected: '8BIM' was '%s'", PSDUtil.intToStr(resSig)));
break;
}
@@ -103,7 +103,8 @@ final class PSDUtil {
final int[] byteCounts, long compressedLength) throws IOException {
switch (compression) {
case PSD.COMPRESSION_NONE:
return new SubImageInputStream(stream, stream.length());
long streamLength = stream.length();
return new SubImageInputStream(stream, streamLength < 0 ? Long.MAX_VALUE : streamLength);
case PSD.COMPRESSION_RLE:
return new DirectImageInputStream(new SequenceInputStream(new LazyPackBitsStreamEnumeration(byteCounts, stream)));
@@ -31,10 +31,12 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.DirectImageInputStream;
import org.junit.Test;
import javax.imageio.stream.ImageInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import static com.twelvemonkeys.imageio.plugins.psd.PSDUtil.createDecompressorStream;
@@ -65,6 +67,29 @@ public class PSDUtilDecompressorStreamTest {
}
}
@Test
public void testUncompressedUnknownLength() 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 DirectImageInputStream(new ByteArrayInputStream(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
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
@@ -35,6 +35,8 @@ import javax.imageio.stream.ImageInputStream;
import java.io.EOFException;
import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* LSBBitReader
*
@@ -49,14 +51,13 @@ public final class LSBBitReader {
private long streamPosition = -1;
/**
* Pre buffers up to the next 8 Bytes in input.
* Pre-buffers up to the next 8 Bytes in input.
* Contains valid bits in bits 63 to {@code bitOffset} (inclusive).
* Should always be refilled to have at least 56 valid bits (if possible)
*/
private long buffer;
public LSBBitReader(ImageInputStream imageInput) {
this.imageInput = imageInput;
this.imageInput = notNull(imageInput);
}
/**
@@ -89,20 +90,16 @@ public final class LSBBitReader {
if (bits > 56) {
throw new IllegalArgumentException("Tried peeking over 56");
}
return readBits(bits, true);
}
//Driver
private long readBits(int bits, boolean peek) throws IOException {
if (bits <= 56) {
/*
Could eliminate if we never read from the underlying InputStream outside this class after the object is
created
*/
long inputStreamPosition = imageInput.getStreamPosition();
if (streamPosition != inputStreamPosition) {
//Need to reset buffer as stream was read in the meantime
// Could eliminate if we never read from the underlying InputStream
// outside this class after the object is created
if (streamPosition != imageInput.getStreamPosition()) {
// Need to reset buffer as stream was read in the meantime
resetBuffer();
}
@@ -110,45 +107,46 @@ public final class LSBBitReader {
if (!peek) {
bitOffset += bits;
refillBuffer();
if (bitOffset >= 8) {
refillBuffer();
}
}
return ret;
}
else {
//FIXME Untested
// Peek always false in this case
long lower = readBits(56);
return (readBits(bits - 56) << (56)) | lower;
}
}
private void refillBuffer() throws IOException {
// Set to stream position consistent with buffered bytes
imageInput.readLong(); // Don't replace with skipBytes(8) or seek(+8), this will invalidate stream buffer... TODO: Fix streams to cope...
//Set to stream position consistent with buffered bytes
imageInput.seek(streamPosition + 8);
for (; bitOffset >= 8; bitOffset -= 8) {
try {
byte b = imageInput.readByte();
buffer >>>= 8;
buffer = ((long) b << 56) | buffer >>> 8;
streamPosition++;
buffer |= ((long) b << 56);
}
catch (EOFException e) {
imageInput.seek(streamPosition);
return;
}
}
/*
Reset to guarantee stream position consistent with returned bytes
Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
this class after the object is created.
*/
// Reset to guarantee stream position consistent with returned bytes
// Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
// this class after the object is created.
imageInput.seek(streamPosition);
}
private void resetBuffer() throws IOException {
long inputStreamPosition = imageInput.getStreamPosition();
try {
buffer = imageInput.readLong();
bitOffset = 0;
@@ -156,7 +154,7 @@ public final class LSBBitReader {
imageInput.seek(inputStreamPosition);
}
catch (EOFException e) {
//Retry byte by byte
// Retry byte by byte
streamPosition = inputStreamPosition - 8;
bitOffset = 64;
refillBuffer();
@@ -164,7 +162,7 @@ public final class LSBBitReader {
}
//Left for backwards compatibility / Compatibility with ImageInputStream interface
// Left for backwards compatibility / Compatibility with ImageInputStream interface
public int readBit() throws IOException {
return (int) readBits(1);
}
@@ -96,7 +96,9 @@ final class WebPImageReader extends ImageReaderBase {
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata);
lsbBitReader = new LSBBitReader(imageInput);
if (imageInput != null) {
lsbBitReader = new LSBBitReader(imageInput);
}
}
private void readHeader(int imageIndex) throws IOException {
@@ -272,19 +274,19 @@ final class WebPImageReader extends ImageReaderBase {
}
// RsV|I|L|E|X|A|R
int reserved = (int) imageInput.readBits(2);
int reserved = lsbBitReader.readBit();
if (reserved != 0) {
// Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
}
header.containsICCP = imageInput.readBit() == 1;
header.containsALPH = imageInput.readBit() == 1; // L -> aLpha
header.containsEXIF = imageInput.readBit() == 1;
header.containsXMP_ = imageInput.readBit() == 1;
header.containsANIM = imageInput.readBit() == 1; // A -> Anim
header.containsANIM = lsbBitReader.readBit() == 1; // A -> Anim
header.containsXMP_ = lsbBitReader.readBit() == 1;
header.containsEXIF = lsbBitReader.readBit() == 1;
header.containsALPH = lsbBitReader.readBit() == 1; // L -> aLpha
header.containsICCP = lsbBitReader.readBit() == 1;
reserved = (int) imageInput.readBits(25); // 1 + 24 bits reserved
reserved = (int) lsbBitReader.readBits(26); // 2 + 24 bits reserved
if (reserved != 0) {
// Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
@@ -509,16 +511,16 @@ final class WebPImageReader extends ImageReaderBase {
}
private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException {
int reserved = (int) imageInput.readBits(2);
int compression = (int) lsbBitReader.readBits(2);
int filtering = (int) lsbBitReader.readBits(2);
int preProcessing = (int) lsbBitReader.readBits(2);
int reserved = (int) lsbBitReader.readBits(2);
if (reserved != 0) {
// Spec says SHOULD be 0
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
}
int preProcessing = (int) imageInput.readBits(2);
int filtering = (int) imageInput.readBits(2);
int compression = (int) imageInput.readBits(2);
if (DEBUG) {
System.out.println("preProcessing: " + preProcessing);
System.out.println("filtering: " + filtering);
@@ -48,16 +48,14 @@ final class ColorIndexingTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) {
//Reversed so no used elements are overridden (in case of packing)
// Reversed so no used elements are overridden (in case of packing)
for (int x = width - 1; x >= 0; x--) {
int componentSize = 8 >> bits;
int packed = 1 << bits;
int xC = x / packed;
@@ -67,10 +65,9 @@ final class ColorIndexingTransform implements Transform {
int index = sample >> componentOffset & ((1 << componentSize) - 1);
//Arraycopy for 4 elements might not be beneficial
// Arraycopy for 4 elements might not be beneficial
System.arraycopy(colorTable, index * 4, rgba, 0, 4);
raster.setDataElements(x, y, rgba);
}
}
}
@@ -37,11 +37,11 @@ import java.awt.image.*;
* @author Simon Kammermeier
*/
final class HuffmanInfo {
public Raster huffmanMetaCodes; //Raster allows intuitive lookup by x and y
public final Raster huffmanMetaCodes; // Raster allows intuitive lookup by x and y
public int metaCodeBits;
public final int metaCodeBits;
public HuffmanCodeGroup[] huffmanGroups;
public final HuffmanCodeGroup[] huffmanGroups;
public HuffmanInfo(Raster huffmanMetaCodes, int metaCodeBits, HuffmanCodeGroup[] huffmanGroups) {
this.huffmanMetaCodes = huffmanMetaCodes;
@@ -83,7 +83,6 @@ final class HuffmanTable {
* @throws IOException when reading produces an exception
*/
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
boolean simpleLengthCode = lsbBitReader.readBit() == 1;
if (simpleLengthCode) {
@@ -104,11 +103,9 @@ final class HuffmanTable {
}
}
else {
/*
code lengths also huffman coded
first read the "first stage" code lengths
In the following this is called the L-Code (for length code)
*/
// code lengths also huffman coded
// first read the "first stage" code lengths
// In the following this is called the L-Code (for length code)
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
short[] lCodeLengths = new short[L_CODE_ORDER.length];
int numPosCodeLens = 0;
@@ -116,16 +113,15 @@ final class HuffmanTable {
for (int i = 0; i < numLCodeLengths; i++) {
short len = (short) lsbBitReader.readBits(3);
lCodeLengths[L_CODE_ORDER[i]] = len;
if (len > 0) {
numPosCodeLens++;
}
}
//Use L-Code to read the actual code lengths
// Use L-Code to read the actual code lengths
short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens);
buildFromLengths(codeLengths);
}
}
@@ -142,25 +138,21 @@ final class HuffmanTable {
buildFromLengths(codeLengths, numPosCodeLens);
}
/*
Helper methods to allow reusing in different constructors
*/
// Helper methods to allow reusing in different constructors
private void buildFromLengths(short[] codeLengths) {
int numPosCodeLens = 0;
for (short codeLength : codeLengths) {
if (codeLength != 0) {
numPosCodeLens++;
}
}
buildFromLengths(codeLengths, numPosCodeLens);
}
private void buildFromLengths(short[] codeLengths, int numPosCodeLens) {
//Pack code length and corresponding symbols as described above
// Pack code length and corresponding symbols as described above
int[] lengthsAndSymbols = new int[numPosCodeLens];
int index = 0;
@@ -170,28 +162,25 @@ final class HuffmanTable {
}
}
//Special case: Only 1 code value
// Special case: Only 1 code value
if (numPosCodeLens == 1) {
//Length is 0 so mask to clear length bits
// Length is 0 so mask to clear length bits
Arrays.fill(level1, lengthsAndSymbols[0] & 0xffff);
}
//Due to the layout of the elements this effectively first sorts by length and then symbol.
// Due to the layout of the elements this effectively first sorts by length and then symbol.
Arrays.sort(lengthsAndSymbols);
/*
The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
Only the lowest bits (corresponding to the bit length of the code) are considered.
Example: code 0..010 (length 2) would appear as 0..001.
*/
// The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
// Only the lowest bits (corresponding to the bit length of the code) are considered.
// Example: code 0..010 (length 2) would appear as 0..001.
int code = 0;
//Used for level2 lookup
// Used for level2 lookup
int rootEntry = -1;
int[] currentTable = null;
for (int i = 0; i < lengthsAndSymbols.length; i++) {
int lengthAndSymbol = lengthsAndSymbols[i];
int length = lengthAndSymbol >>> 16;
@@ -202,16 +191,15 @@ final class HuffmanTable {
}
}
else {
//Existing level2 table not fitting
// Existing level2 table not fitting
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
/*
Figure out needed table size.
Start at current symbol and length.
Every symbol uses 1 slot at the current bit length.
Going up 1 bit in length multiplies the slots by 2.
No more open slots indicate the table size to be big enough.
*/
// Figure out needed table size.
// Start at current symbol and length.
// Every symbol uses 1 slot at the current bit length.
// Going up 1 bit in length multiplies the slots by 2.
// No more open slots indicate the table size to be big enough.
int maxLength = length;
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
j < lengthsAndSymbols.length && openSlots > 0;
j++, openSlots--) {
@@ -230,11 +218,11 @@ final class HuffmanTable {
rootEntry = code & ((1 << LEVEL1_BITS) - 1);
level2.add(currentTable);
//Set root table indirection
// Set root table indirection
level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1);
}
//Add to existing (or newly generated) 2nd level table
// Add to existing (or newly generated) 2nd level table
for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) {
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
}
@@ -256,12 +244,12 @@ final class HuffmanTable {
private int nextCode(int code, int length) {
int a = (~code) & ((1 << length) - 1);
//This will result in the highest 0-bit in the lower length bits of code set (by construction of a)
//I.e. the lowest 0-bit in the value code represents
// This will result in the highest 0-bit in the lower length bits of code set (by construction of a)
// I.e. the lowest 0-bit in the value code represents
int step = Integer.highestOneBit(a);
//In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit
//This corresponds to adding 1 to the value
// In the represented value this clears the consecutive 1-bits starting at bit 0 and then sets the lowest 0 bit
// This corresponds to adding 1 to the value
return (code & (step - 1)) | step;
}
@@ -270,7 +258,7 @@ final class HuffmanTable {
HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens);
//Not sure where this comes from. Just adapted from the libwebp implementation
// Not sure where this comes from. Just adapted from the libwebp implementation
int codedSymbols;
if (lsbBitReader.readBit() == 1) {
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
@@ -282,13 +270,13 @@ final class HuffmanTable {
short[] codeLengths = new short[alphabetSize];
//Default code for repeating
// Default code for repeating
short prevLength = 8;
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
short len = huffmanTable.readSymbol(lsbBitReader);
if (len < 16) { //Literal length
if (len < 16) { // Literal length
codeLengths[i] = len;
if (len != 0) {
prevLength = len;
@@ -300,16 +288,16 @@ final class HuffmanTable {
int repeatOffset;
switch (len) {
case 16: //Repeat previous
case 16: // Repeat previous
repeatSymbol = prevLength;
extraBits = 2;
repeatOffset = 3;
break;
case 17: //Repeat 0 short
case 17: // Repeat 0 short
extraBits = 3;
repeatOffset = 3;
break;
case 18: //Repeat 0 long
case 18: // Repeat 0 long
extraBits = 7;
repeatOffset = 11;
break;
@@ -319,7 +307,6 @@ final class HuffmanTable {
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
if (i + repeatCount > alphabetSize) {
throw new IIOException(
String.format(
@@ -330,11 +317,9 @@ final class HuffmanTable {
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
i += repeatCount - 1;
}
}
return codeLengths;
}
@@ -346,21 +331,20 @@ final class HuffmanTable {
* @throws IOException when the reader throws one reading a symbol
*/
public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
int lengthAndSymbol = level1[index];
int length = lengthAndSymbol >>> 16;
if (length > LEVEL1_BITS) {
//Lvl2 lookup
lsbBitReader.readBits(LEVEL1_BITS); //Consume bits of first level
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); //Peek remaining required bits
// Lvl2 lookup
lsbBitReader.readBits(LEVEL1_BITS); // Consume bits of first level
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); // Peek remaining required bits
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
length = lengthAndSymbol >>> 16;
}
lsbBitReader.readBits(length); //Consume bits
lsbBitReader.readBits(length); // Consume bits
return (short) (lengthAndSymbol & 0xffff);
}
@@ -51,25 +51,23 @@ final class PredictorTransform implements Transform {
@Override
public void applyInverse(WritableRaster raster) {
int width = raster.getWidth();
int height = raster.getHeight();
byte[] rgba = new byte[4];
//Handle top and left border separately
// Handle top and left border separately
//(0,0) Black (0x000000ff) predict
// (0,0) Black (0x000000ff) predict
raster.getDataElements(0, 0, rgba);
rgba[3] += 0xff;
raster.setDataElements(0, 0, rgba);
byte[] predictor = new byte[4];
byte[] predictor2 = new byte[4];
byte[] predictor3 = new byte[4];
//(x,0) L predict
// (x,0) L predict
for (int x = 1; x < width; x++) {
raster.getDataElements(x, 0, rgba);
raster.getDataElements(x - 1, 0, predictor);
@@ -78,7 +76,7 @@ final class PredictorTransform implements Transform {
raster.setDataElements(x, 0, rgba);
}
//(0,y) T predict
// (0,y) T predict
for (int y = 1; y < height; y++) {
raster.getDataElements(0, y, rgba);
raster.getDataElements(0, y - 1, predictor);
@@ -89,16 +87,14 @@ final class PredictorTransform implements Transform {
for (int y = 1; y < height; y++) {
for (int x = 1; x < width; x++) {
int transformType = data.getSample(x >> bits, y >> bits, 1);
raster.getDataElements(x, y, rgba);
int lX = x - 1; //x for left
int lX = x - 1; // x for left
int tY = y - 1; // y for top
int tY = y - 1; //y for top
//top right is not (x+1, tY) if last pixel in line instead (0, y)
// top right is not (x+1, tY) if last pixel in line instead (0, y)
int trX = x == width - 1 ? 0 : x + 1;
int trY = x == width - 1 ? y : tY;
@@ -36,7 +36,7 @@ package com.twelvemonkeys.imageio.plugins.webp.lossless;
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
*/
// Hmm.. Why doesn't SUBTRACT_GREEN follow the convention?
// Hmm... Why doesn't SUBTRACT_GREEN follow the convention?
interface TransformType {
int PREDICTOR_TRANSFORM = 0;
int COLOR_TRANSFORM = 1;
@@ -0,0 +1,270 @@
package com.twelvemonkeys.imageio.plugins.webp;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import org.junit.Test;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteOrder;
import static org.junit.Assert.assertEquals;
/**
* LSBBitReaderTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: LSBBitReaderTest.java,v 1.0 16/10/2022 haraldk Exp$
*/
public class LSBBitReaderTest {
@Test
public void testReadBit() throws IOException {
final LSBBitReader bitReader = createBitReader(new byte[] {
0b00010010, 0b00100001, 0b00001000, 0b00000100,
/*TODO: Remove these, should not be needed... */ 0, 0, 0, 0
});
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(1, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
assertEquals(0, bitReader.readBit());
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
@Test
public void testReadBits() throws IOException {
final LSBBitReader bitReader = createBitReader(new byte[] {
0b00100101, 0b01000010, 0b00010000, 0b00001000,
0b00001000, 0b00010000, 0b01000000, 0b00000000,
0b00000010, 0b00100000, 0b00000000, 0b00000100,
0b00000000, 0b00000001, (byte) 0b10000000,
});
assertEquals(1, bitReader.readBits(1));
assertEquals(2, bitReader.readBits(2));
assertEquals(4, bitReader.readBits(3));
assertEquals(8, bitReader.readBits(4));
assertEquals(16, bitReader.readBits(5));
assertEquals(32, bitReader.readBits(6));
assertEquals(64, bitReader.readBits(7));
assertEquals(128, bitReader.readBits(8));
assertEquals(256, bitReader.readBits(9));
assertEquals(512, bitReader.readBits(10));
assertEquals(1024, bitReader.readBits(11));
assertEquals(2048, bitReader.readBits(12));
assertEquals(4096, bitReader.readBits(13));
assertEquals(8192, bitReader.readBits(14));
assertEquals(16384, bitReader.readBits(15));
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
@Test
public void testPeekBits() throws IOException {
final LSBBitReader bitReader = createBitReader(new byte[] {
0b00100101, 0b01000010, 0b00010000, 0b00001000,
0b00001000, 0b00010000, 0b01000000, 0b00000000,
0b00000010, 0b00100000, 0b00000000, 0b00000100,
0b00000000, 0b00000001, (byte) 0b10000000
});
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.readBits(1));
assertEquals(2, bitReader.peekBits(2));
assertEquals(2, bitReader.readBits(2));
assertEquals(4, bitReader.readBits(3));
assertEquals(8, bitReader.peekBits(4));
assertEquals(8, bitReader.readBits(4));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.readBits(5));
assertEquals(32, bitReader.peekBits(6));
assertEquals(32, bitReader.readBits(6));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.readBits(7));
assertEquals(128, bitReader.peekBits(8));
assertEquals(128, bitReader.readBits(8));
assertEquals(256, bitReader.readBits(9));
assertEquals(512, bitReader.peekBits(10));
assertEquals(512, bitReader.readBits(10));
assertEquals(1024, bitReader.peekBits(11));
assertEquals(1024, bitReader.readBits(11));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.readBits(12));
assertEquals(4096, bitReader.peekBits(13));
assertEquals(4096, bitReader.readBits(13));
assertEquals(8192, bitReader.readBits(14));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.readBits(15));
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
@Test
public void testReadBetweenBits() throws IOException {
ImageInputStream stream = createStream(new byte[] {
0b00100101, 0b01000010, 0b00010000, 0b00001000,
0b00001000, 0b00010000, 0b01000000, 0b00000000,
0b00000010, 0b00100000, 0b00000000, 0b00000100,
0b00000000, 0b00000001, (byte) 0b10000000
});
final LSBBitReader bitReader = new LSBBitReader(stream);
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.peekBits(1));
assertEquals(1, bitReader.readBits(1));
assertEquals(2, bitReader.peekBits(2));
assertEquals(2, bitReader.readBits(2));
assertEquals(4, bitReader.readBits(3));
// We've read 6 bits, but still on the 1st byte
assertEquals(0b00100101, stream.readByte());
// Start reading from the second byte (10 == 2)
assertEquals(2, bitReader.readBits(2));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.readBits(5));
// We've now read 7 bits, but still on the second byte
assertEquals(1, stream.getStreamPosition());
assertEquals(0b01000010, stream.readByte());
assertEquals(2, stream.getStreamPosition());
assertEquals(16, bitReader.peekBits(11));
assertEquals(0b00010000, stream.readByte());
assertEquals(3, stream.getStreamPosition());
stream.seek(2);
assertEquals(2, stream.getStreamPosition());
// Start reading from the third byte (10000 == 16)
assertEquals(16, bitReader.peekBits(5));
assertEquals(16, bitReader.readBits(5));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.peekBits(7));
assertEquals(64, bitReader.readBits(7));
assertEquals(128, bitReader.peekBits(8));
assertEquals(128, bitReader.readBits(8));
assertEquals(256, bitReader.readBits(9));
assertEquals(512, bitReader.peekBits(10));
assertEquals(512, bitReader.readBits(10));
assertEquals(1024, bitReader.peekBits(11));
assertEquals(1024, bitReader.readBits(11));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.peekBits(12));
assertEquals(2048, bitReader.readBits(12));
assertEquals(4096, bitReader.peekBits(13));
assertEquals(4096, bitReader.readBits(13));
assertEquals(8192, bitReader.readBits(14));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.peekBits(15));
assertEquals(16384, bitReader.readBits(15));
// assertThrows(EOFException.class, new ThrowingRunnable() {
// @Override
// public void run() throws Throwable {
// bitReader.readBits(1);
// }
// });
}
private static LSBBitReader createBitReader(final byte[] data) {
ImageInputStream stream = createStream(data);
return new LSBBitReader(stream);
}
private static ImageInputStream createStream(byte[] data) {
ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data);
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
return stream;
}
}
@@ -1,11 +1,8 @@
package com.twelvemonkeys.imageio.plugins.webp;
import static java.util.Arrays.asList;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
@@ -13,10 +10,13 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.awt.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import com.twelvemonkeys.imageio.util.ImageReaderAbstractTest;
import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
/**
* WebPImageReaderTest
@@ -141,4 +141,25 @@ public class WebPImageReaderTest extends ImageReaderAbstractTest<WebPImageReader
reader.dispose();
}
}
@Test
public void testReadAlphaTransparent() throws IOException {
WebPImageReader reader = createReader();
try (ImageInputStream stream = ImageIO.createImageInputStream(getClassLoaderResource("/webp/1_webp_a.webp"))) {
reader.setInput(stream);
BufferedImage image = reader.read(0);
assertEquals(Transparency.TRANSLUCENT, image.getTransparency());
assertRGBEquals("Expected transparent corner (0, 0)", 0x00000000, image.getRGB(0, 0) & 0xFF000000, 8);
assertRGBEquals("Expected opaque center (200, 150)", 0xff9a4e01, image.getRGB(200, 150), 8);
assertRGBEquals("Expected transparent corner (399, 0)", 0x00000000, image.getRGB(399, 0) & 0xFF000000, 8);
assertRGBEquals("Expected transparent corner (0, 300)", 0x00000000, image.getRGB(0, 300) & 0xFF000000, 8);
assertRGBEquals("Expected transparent corner (399, 300)", 0x00000000, image.getRGB(399, 300) & 0xFF000000, 8);
}
finally {
reader.dispose();
}
}
}
+1 -1
View File
@@ -4,7 +4,7 @@
<parent>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -4,7 +4,7 @@
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
<packaging>pom</packaging>
<name>TwelveMonkeys</name>
<description>TwelveMonkeys parent POM</description>
@@ -80,7 +80,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.9.0</tag>
<tag>twelvemonkeys-3.9.4</tag>
</scm>
<distributionManagement>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent>
<groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version>
<version>3.9.4</version>
</parent>
<modelVersion>4.0.0</modelVersion>