Compare commits

...

7 Commits

Author SHA1 Message Date
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
53 changed files with 491 additions and 219 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<groupId>com.twelvemonkeys.bom</groupId> <groupId>com.twelvemonkeys.bom</groupId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>common-image</artifactId> <artifactId>common-image</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>common-io</artifactId> <artifactId>common-io</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>common-lang</artifactId> <artifactId>common-lang</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<groupId>com.twelvemonkeys.common</groupId> <groupId>com.twelvemonkeys.common</groupId>
<artifactId>common</artifactId> <artifactId>common</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<groupId>com.twelvemonkeys.contrib</groupId> <groupId>com.twelvemonkeys.contrib</groupId>
<artifactId>contrib</artifactId> <artifactId>contrib</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-batik</artifactId> <artifactId>imageio-batik</artifactId>
<name>TwelveMonkeys :: ImageIO :: Batik Plugin</name> <name>TwelveMonkeys :: ImageIO :: Batik Plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-bmp</artifactId> <artifactId>imageio-bmp</artifactId>
<name>TwelveMonkeys :: ImageIO :: BMP plugin</name> <name>TwelveMonkeys :: ImageIO :: BMP plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-clippath</artifactId> <artifactId>imageio-clippath</artifactId>
<name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name> <name>TwelveMonkeys :: ImageIO :: Photoshop Path Support</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-core</artifactId> <artifactId>imageio-core</artifactId>
<name>TwelveMonkeys :: ImageIO :: Core</name> <name>TwelveMonkeys :: ImageIO :: Core</name>
@@ -31,6 +31,7 @@
package com.twelvemonkeys.imageio.stream; package com.twelvemonkeys.imageio.stream;
import javax.imageio.stream.ImageInputStreamImpl; import javax.imageio.stream.ImageInputStreamImpl;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
@@ -52,6 +53,10 @@ import static java.lang.Math.max;
* for shorter reads, like single byte or bit reads. * for shorter reads, like single byte or bit reads.
*/ */
final class BufferedChannelImageInputStream extends ImageInputStreamImpl { final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private static final Closeable CLOSEABLE_STUB = new Closeable() {
@Override public void close() {}
};
static final int DEFAULT_BUFFER_SIZE = 8192; static final int DEFAULT_BUFFER_SIZE = 8192;
private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE); private ByteBuffer byteBuffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE);
@@ -63,6 +68,7 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
private final byte[] integralCacheArray = integralCache.array(); private final byte[] integralCacheArray = integralCache.array();
private SeekableByteChannel channel; private SeekableByteChannel channel;
private Closeable closeable;
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code File}. * 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. * @throws SecurityException if a security manager is installed, and it denies read access to the file.
*/ */
public BufferedChannelImageInputStream(final Path file) throws IOException { 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}. * 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. * @param file a {@code RandomAccessFile} to read from.
* @throws IllegalArgumentException if {@code file} is {@code null}. * @throws IllegalArgumentException if {@code file} is {@code null}.
*/ */
public BufferedChannelImageInputStream(final RandomAccessFile file) { public BufferedChannelImageInputStream(final RandomAccessFile file) {
// Assumption: Closing the FileChannel will also close its backing RandomAccessFile // Closing the RAF is inconsistent, but emulates the behavior of javax.imageio.stream.FileImageInputStream
// (it does in the OpenJDK implementation, and it makes sense, although I can't see this is documented behaviour). this(notNull(file, "file").getChannel(), true);
this(notNull(file, "file").getChannel());
} }
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}. * Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code FileInputStream}.
* <p> * <p>
* Closing this stream will close the {@code FileInputStream}. * Closing this stream will <em>not</em> close the {@code FileInputStream}.
* </p> * </p>
* *
* @param inputStream a {@code FileInputStream} to read from. * @param inputStream a {@code FileInputStream} to read from.
* @throws IllegalArgumentException if {@code inputStream} is {@code null}. * @throws IllegalArgumentException if {@code inputStream} is {@code null}.
*/ */
public BufferedChannelImageInputStream(final FileInputStream inputStream) { 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(), false);
this(notNull(inputStream, "inputStream").getChannel());
} }
/** /**
* Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}. * Constructs a {@code BufferedChannelImageInputStream} that will read from a given {@code SeekableByteChannel}.
* <p> * <p>
* Closing this stream will close the {@code SeekableByteChannel}. * Closing this stream will <em>not</em> close the {@code SeekableByteChannel}.
* </p> * </p>
* *
* @param channel a {@code SeekableByteChannel} to read from. * @param channel a {@code SeekableByteChannel} to read from.
* @throws IllegalArgumentException if {@code channel} is {@code null}. * @throws IllegalArgumentException if {@code channel} is {@code null}.
*/ */
public BufferedChannelImageInputStream(final SeekableByteChannel channel) { 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.channel = notNull(channel, "channel");
this.closeable = closeChannelOnClose ? this.channel : CLOSEABLE_STUB;
} }
@SuppressWarnings("BooleanMethodIsAlwaysInverted") @SuppressWarnings("BooleanMethodIsAlwaysInverted")
@@ -246,8 +265,14 @@ final class BufferedChannelImageInputStream extends ImageInputStreamImpl {
buffer = null; buffer = null;
byteBuffer = null; byteBuffer = null;
channel.close();
channel = null; channel = null;
try {
closeable.close();
}
finally {
closeable = null;
}
} }
// Need to override the readShort(), readInt() and readLong() methods, // 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 { public void flushBefore(final long pos) throws IOException {
super.flushBefore(pos); super.flushBefore(pos);
if (channel instanceof MemoryCache) { if (channel instanceof Cache) {
// In case of memory cache, free up memory // 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 // 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); 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 dataOffset;
private final int dataLength; private final int dataLength;
public ByteArrayImageInputStream(final byte[] pData) { public ByteArrayImageInputStream(final byte[] data) {
this(pData, 0, pData != null ? pData.length : -1); this(data, 0, data != null ? data.length : -1);
} }
public ByteArrayImageInputStream(final byte[] pData, int offset, int length) { public ByteArrayImageInputStream(final byte[] data, int offset, int length) {
data = notNull(pData, "data"); this.data = notNull(data, "data");
dataOffset = isBetween(0, pData.length, offset, "offset"); dataOffset = isMax(data.length, offset, "offset");
dataLength = isBetween(0, pData.length - offset, length, "length"); dataLength = isMax(data.length - offset, length, "length");
} }
private static int isBetween(final int low, final int high, final int value, final String name) { private static int isMax(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)); return isTrue(value >= 0 && value <= high, value, String.format("%s out of range [0, %d]: %d", name, high, value));
} }
public int read() throws IOException { public int read() throws IOException {
@@ -72,14 +72,14 @@ public final class ByteArrayImageInputStream extends ImageInputStreamImpl {
return data[((int) streamPos++) + dataOffset] & 0xff; 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) { if (streamPos >= dataLength) {
return -1; return -1;
} }
int length = (int) Math.min(this.dataLength - streamPos, pLength); int length = (int) Math.min(dataLength - streamPos, len);
bitOffset = 0; bitOffset = 0;
System.arraycopy(data, (int) streamPos + dataOffset, pBuffer, pOffset, length); System.arraycopy(data, (int) streamPos + dataOffset, buffer, offset, length);
streamPos += length; streamPos += length;
return 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 // the usual {@link #read read} and {@link #write write} methods. From the
// standpoint of performance it is generally only worth mapping relatively // standpoint of performance it is generally only worth mapping relatively
// large files into memory. // large files into memory.
final class DiskCache implements SeekableByteChannel { final class FileCache implements Cache {
final static int BLOCK_SIZE = 1 << 13; final static int BLOCK_SIZE = 1 << 13;
private final FileChannel cache; private final FileChannel cache;
private final ReadableByteChannel channel; private final ReadableByteChannel channel;
// TODO: Perhaps skip this constructor? // 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 // Stream will be closed with channel, documented behavior
this(Channels.newChannel(notNull(stream, "stream")), cacheDir); 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"); this.channel = notNull(channel, "channel");
isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory"); isTrue(cacheDir == null || cacheDir.isDirectory(), cacheDir, "%s is not a directory");
@@ -65,12 +65,7 @@ final class DiskCache implements SeekableByteChannel {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { cache.close();
cache.close();
}
finally {
channel.close();
}
} }
@Override @Override
@@ -110,5 +105,8 @@ final class DiskCache implements SeekableByteChannel {
public SeekableByteChannel truncate(long size) { public SeekableByteChannel truncate(long size) {
throw new NonWritableChannelException(); throw new NonWritableChannelException();
} }
@Override public void flushBefore(long pos) {
}
} }
@@ -13,7 +13,7 @@ import java.util.List;
import static com.twelvemonkeys.lang.Validate.notNull; import static com.twelvemonkeys.lang.Validate.notNull;
import static java.lang.Math.min; import static java.lang.Math.min;
public final class MemoryCache implements SeekableByteChannel { final class MemoryCache implements Cache {
final static int BLOCK_SIZE = 1 << 13; final static int BLOCK_SIZE = 1 << 13;
@@ -78,12 +78,7 @@ public final class MemoryCache implements SeekableByteChannel {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { cache.clear();
cache.clear();
}
finally {
channel.close();
}
} }
@Override @Override
@@ -135,7 +130,8 @@ public final class MemoryCache implements SeekableByteChannel {
throw new NonWritableChannelException(); throw new NonWritableChannelException();
} }
void flushBefore(long pos) { @Override
public void flushBefore(long pos) {
if (pos < start) { if (pos < start) {
throw new IndexOutOfBoundsException("pos < flushed position"); throw new IndexOutOfBoundsException("pos < flushed position");
} }
@@ -80,7 +80,7 @@ public final class URLImageInputStreamSpi extends ImageInputStreamSpi {
// Otherwise revert to cached // Otherwise revert to cached
InputStream urlStream = url.openStream(); 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); 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$ * @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! // 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 final Random random = new Random(170984354357234566L);
private InputStream randomDataToInputStream(byte[] data) { private InputStream randomDataToInputStream(byte[] data) {
@@ -68,7 +68,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testCreate() throws IOException { 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()); assertEquals("Stream length should be unknown", -1, stream.length());
} }
} }
@@ -76,7 +76,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testCreateNullStream() throws IOException { public void testCreateNullStream() throws IOException {
try { try {
new DiskCache((InputStream) null, null); new FileCache((InputStream) null, null);
fail("Expected IllegalArgumentException"); fail("Expected IllegalArgumentException");
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
@@ -90,7 +90,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testCreateNullChannel() throws IOException { public void testCreateNullChannel() throws IOException {
try { try {
new DiskCache((ReadableByteChannel) null, null); new FileCache((ReadableByteChannel) null, null);
fail("Expected IllegalArgumentException"); fail("Expected IllegalArgumentException");
} }
catch (IllegalArgumentException expected) { catch (IllegalArgumentException expected) {
@@ -106,7 +106,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 1024]; byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data); 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()); assertEquals("Stream length should be unknown", -1, stream.length());
for (byte value : data) { for (byte value : data) {
@@ -122,7 +122,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 1024]; byte[] data = new byte[1024 * 1024];
InputStream input = randomDataToInputStream(data); 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()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[1024]; byte[] result = new byte[1024];
@@ -141,7 +141,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 14]; byte[] data = new byte[1024 * 14];
InputStream input = randomDataToInputStream(data); 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()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[7]; byte[] result = new byte[7];
@@ -159,7 +159,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[1024 * 18]; byte[] data = new byte[1024 * 18];
InputStream input = randomDataToInputStream(data); 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()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] result = new byte[9]; byte[] result = new byte[9];
@@ -180,7 +180,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] data = new byte[256]; byte[] data = new byte[256];
InputStream input = randomDataToInputStream(data); 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()); assertEquals("Stream length should be unknown", -1, stream.length());
byte[] buffer = new byte[data.length * 2]; byte[] buffer = new byte[data.length * 2];
@@ -198,7 +198,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
long value = ByteBuffer.wrap(bytes).getLong(); long value = ByteBuffer.wrap(bytes).getLong();
// Create stream // 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++) { for (int i = 1; i <= 64; i++) {
assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit()); 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(); long value = ByteBuffer.wrap(bytes).getLong();
// Create stream // 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++) { for (int i = 1; i <= 64; i++) {
stream.seek(0); stream.seek(0);
assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i)); 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(); long value = ByteBuffer.wrap(bytes).getLong();
// Create stream // 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++) { for (int i = 1; i <= 60; i++) {
stream.seek(0); stream.seek(0);
stream.setBitOffset(i % 8); stream.setBitOffset(i % 8);
@@ -244,7 +244,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); 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); stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 2; i++) { for (int i = 0; i < bytes.length / 2; i++) {
@@ -282,7 +282,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); 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); stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 4; i++) { for (int i = 0; i < bytes.length / 4; i++) {
@@ -320,7 +320,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
InputStream input = randomDataToInputStream(bytes); InputStream input = randomDataToInputStream(bytes);
ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); 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); stream.setByteOrder(ByteOrder.BIG_ENDIAN);
for (int i = 0; i < bytes.length / 8; i++) { for (int i = 0; i < bytes.length / 8; i++) {
@@ -357,7 +357,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] bytes = new byte[9]; byte[] bytes = new byte[9];
InputStream input = randomDataToInputStream(bytes); 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); stream.seek(1000);
assertEquals(-1, stream.read()); assertEquals(-1, stream.read());
@@ -406,11 +406,11 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
@Test @Test
public void testClose() throws IOException { public void testClose() throws IOException {
// Create wrapper stream // Create wrapper stream
InputStream mock = mock(InputStream.class); Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(new DiskCache(mock, null)); ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close(); stream.close();
verify(mock, only()).close(); verify(cache, only()).close();
} }
@Test @Test
@@ -422,7 +422,7 @@ public class BufferedChannelImageInputStreamDiskCacheTest {
byte[] bytes = new byte[size]; byte[] bytes = new byte[size];
InputStream input = randomDataToInputStream(bytes); 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]; byte[] result = new byte[size];
int head = stream.read(result, 0, 12); // Provoke a buffered read int head = stream.read(result, 0, 12); // Provoke a buffered read
int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read int len = stream.read(result, 12, size - 12); // Rest of buffer + direct read
@@ -406,11 +406,11 @@ public class BufferedChannelImageInputStreamMemoryCacheTest {
@Test @Test
public void testClose() throws IOException { public void testClose() throws IOException {
// Create wrapper stream // Create wrapper stream
InputStream mock = mock(InputStream.class); Cache cache = mock(Cache.class);
ImageInputStream stream = new BufferedChannelImageInputStream(new MemoryCache(mock)); ImageInputStream stream = new BufferedChannelImageInputStream(cache);
stream.close(); stream.close();
verify(mock, only()).close(); verify(cache, only()).close();
} }
@Test @Test
@@ -47,7 +47,7 @@ import java.util.Random;
import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals; import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
/** /**
@@ -403,14 +403,11 @@ public class BufferedChannelImageInputStreamTest {
@Test @Test
public void testCloseChannel() throws IOException { public void testCloseChannel() throws IOException {
// NOTE: As the stream-based constructor is chained to the channel-based one, SeekableByteChannel channel = mock(SeekableByteChannel.class);
// we'll rely on the fact that closing the channel will close the stream. ImageInputStream stream = new BufferedChannelImageInputStream(channel);
SeekableByteChannel mock = mock(SeekableByteChannel.class);
ImageInputStream stream = new BufferedChannelImageInputStream(mock);
stream.close(); stream.close();
verify(mock, only()).close(); verify(channel, never()).close();
} }
@Test @Test
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-hdr</artifactId> <artifactId>imageio-hdr</artifactId>
<name>TwelveMonkeys :: ImageIO :: HDR plugin</name> <name>TwelveMonkeys :: ImageIO :: HDR plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-icns</artifactId> <artifactId>imageio-icns</artifactId>
<name>TwelveMonkeys :: ImageIO :: ICNS plugin</name> <name>TwelveMonkeys :: ImageIO :: ICNS plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-iff</artifactId> <artifactId>imageio-iff</artifactId>
<name>TwelveMonkeys :: ImageIO :: IFF plugin</name> <name>TwelveMonkeys :: ImageIO :: IFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-jpeg-jai-interop</artifactId> <artifactId>imageio-jpeg-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name> <name>TwelveMonkeys :: ImageIO :: JPEG/JAI TIFF Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-jpeg-jep262-interop</artifactId> <artifactId>imageio-jpeg-jep262-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name> <name>TwelveMonkeys :: ImageIO :: JPEG/JEP-262 Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-jpeg</artifactId> <artifactId>imageio-jpeg</artifactId>
<name>TwelveMonkeys :: ImageIO :: JPEG plugin</name> <name>TwelveMonkeys :: ImageIO :: JPEG plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>imageio-metadata</artifactId> <artifactId>imageio-metadata</artifactId>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-pcx</artifactId> <artifactId>imageio-pcx</artifactId>
<name>TwelveMonkeys :: ImageIO :: PCX plugin</name> <name>TwelveMonkeys :: ImageIO :: PCX plugin</name>
@@ -13,7 +13,6 @@ final class PCXMetadata extends StandardImageMetadataSupport {
} }
private static PlanarConfiguration planarConfiguration(PCXHeader header) { private static PlanarConfiguration planarConfiguration(PCXHeader header) {
System.out.println("header = " + header);
return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null; return header.getChannels() > 1 ? PlanarConfiguration.LineInterleaved : null;
} }
@@ -25,6 +24,6 @@ final class PCXMetadata extends StandardImageMetadataSupport {
return "RLE"; return "RLE";
} }
return "Uknown"; return "Unknown";
} }
} }
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-pdf</artifactId> <artifactId>imageio-pdf</artifactId>
<name>TwelveMonkeys :: ImageIO :: PDF plugin</name> <name>TwelveMonkeys :: ImageIO :: PDF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-pict</artifactId> <artifactId>imageio-pict</artifactId>
<name>TwelveMonkeys :: ImageIO :: PICT plugin</name> <name>TwelveMonkeys :: ImageIO :: PICT plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-pnm</artifactId> <artifactId>imageio-pnm</artifactId>
<name>TwelveMonkeys :: ImageIO :: PNM plugin</name> <name>TwelveMonkeys :: ImageIO :: PNM plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-psd</artifactId> <artifactId>imageio-psd</artifactId>
<name>TwelveMonkeys :: ImageIO :: PSD plugin</name> <name>TwelveMonkeys :: ImageIO :: PSD plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-reference</artifactId> <artifactId>imageio-reference</artifactId>
<name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name> <name>TwelveMonkeys :: ImageIO :: JDK Reference Tests</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-sgi</artifactId> <artifactId>imageio-sgi</artifactId>
<name>TwelveMonkeys :: ImageIO :: SGI plugin</name> <name>TwelveMonkeys :: ImageIO :: SGI plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-tga</artifactId> <artifactId>imageio-tga</artifactId>
<name>TwelveMonkeys :: ImageIO :: TGA plugin</name> <name>TwelveMonkeys :: ImageIO :: TGA plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-thumbsdb</artifactId> <artifactId>imageio-thumbsdb</artifactId>
<name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name> <name>TwelveMonkeys :: ImageIO :: Thumbs.db plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-tiff-jai-interop</artifactId> <artifactId>imageio-tiff-jai-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name> <name>TwelveMonkeys :: ImageIO :: TIFF/JAI Metadata Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-tiff-jdk-interop</artifactId> <artifactId>imageio-tiff-jdk-interop</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name> <name>TwelveMonkeys :: ImageIO :: TIFF/JDK JPEG Interop</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-tiff</artifactId> <artifactId>imageio-tiff</artifactId>
<name>TwelveMonkeys :: ImageIO :: TIFF plugin</name> <name>TwelveMonkeys :: ImageIO :: TIFF plugin</name>
+1 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-webp</artifactId> <artifactId>imageio-webp</artifactId>
<name>TwelveMonkeys :: ImageIO :: WebP plugin</name> <name>TwelveMonkeys :: ImageIO :: WebP plugin</name>
@@ -35,6 +35,8 @@ import javax.imageio.stream.ImageInputStream;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import static com.twelvemonkeys.lang.Validate.notNull;
/** /**
* LSBBitReader * LSBBitReader
* *
@@ -45,18 +47,17 @@ public final class LSBBitReader {
// TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)? // TODO: Consider creating an ImageInputStream wrapper with the WebP implementation of readBit(s)?
private final ImageInputStream imageInput; private final ImageInputStream imageInput;
private int bitOffset = 64; int bitOffset = 64;
private long streamPosition = -1; 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). * 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; private long buffer;
public LSBBitReader(ImageInputStream imageInput) { public LSBBitReader(ImageInputStream imageInput) {
this.imageInput = imageInput; this.imageInput = notNull(imageInput);
} }
/** /**
@@ -89,20 +90,16 @@ public final class LSBBitReader {
if (bits > 56) { if (bits > 56) {
throw new IllegalArgumentException("Tried peeking over 56"); throw new IllegalArgumentException("Tried peeking over 56");
} }
return readBits(bits, true); return readBits(bits, true);
} }
//Driver
private long readBits(int bits, boolean peek) throws IOException { private long readBits(int bits, boolean peek) throws IOException {
if (bits <= 56) { if (bits <= 56) {
// Could eliminate if we never read from the underlying InputStream
/* // outside this class after the object is created
Could eliminate if we never read from the underlying InputStream outside this class after the object is if (streamPosition != imageInput.getStreamPosition()) {
created // Need to reset buffer as stream was read in the meantime
*/
long inputStreamPosition = imageInput.getStreamPosition();
if (streamPosition != inputStreamPosition) {
//Need to reset buffer as stream was read in the meantime
resetBuffer(); resetBuffer();
} }
@@ -110,21 +107,23 @@ public final class LSBBitReader {
if (!peek) { if (!peek) {
bitOffset += bits; bitOffset += bits;
refillBuffer();
if (bitOffset >= 8) {
refillBuffer();
}
} }
return ret; return ret;
} }
else { else {
//FIXME Untested // Peek always false in this case
long lower = readBits(56); long lower = readBits(56);
return (readBits(bits - 56) << (56)) | lower; return (readBits(bits - 56) << (56)) | lower;
} }
} }
private void refillBuffer() throws IOException { private void refillBuffer() throws IOException {
// Set to stream position consistent with buffered bytes
//Set to stream position consistent with buffered bytes
imageInput.seek(streamPosition + 8); imageInput.seek(streamPosition + 8);
for (; bitOffset >= 8; bitOffset -= 8) { for (; bitOffset >= 8; bitOffset -= 8) {
try { try {
@@ -138,17 +137,16 @@ public final class LSBBitReader {
return; return;
} }
} }
/*
Reset to guarantee stream position consistent with returned bytes // 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 // Would not need to do this seeking around when the underlying ImageInputStream is never read from outside
this class after the object is created. // this class after the object is created.
*/
imageInput.seek(streamPosition); imageInput.seek(streamPosition);
} }
private void resetBuffer() throws IOException { private void resetBuffer() throws IOException {
long inputStreamPosition = imageInput.getStreamPosition(); long inputStreamPosition = imageInput.getStreamPosition();
try { try {
buffer = imageInput.readLong(); buffer = imageInput.readLong();
bitOffset = 0; bitOffset = 0;
@@ -156,7 +154,7 @@ public final class LSBBitReader {
imageInput.seek(inputStreamPosition); imageInput.seek(inputStreamPosition);
} }
catch (EOFException e) { catch (EOFException e) {
//Retry byte by byte // Retry byte by byte
streamPosition = inputStreamPosition - 8; streamPosition = inputStreamPosition - 8;
bitOffset = 64; bitOffset = 64;
refillBuffer(); 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 { public int readBit() throws IOException {
return (int) readBits(1); return (int) readBits(1);
} }
@@ -96,7 +96,9 @@ final class WebPImageReader extends ImageReaderBase {
public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) { public void setInput(Object input, boolean seekForwardOnly, boolean ignoreMetadata) {
super.setInput(input, seekForwardOnly, ignoreMetadata); super.setInput(input, seekForwardOnly, ignoreMetadata);
lsbBitReader = new LSBBitReader(imageInput); if (imageInput != null) {
lsbBitReader = new LSBBitReader(imageInput);
}
} }
private void readHeader(int imageIndex) throws IOException { private void readHeader(int imageIndex) throws IOException {
@@ -272,19 +274,19 @@ final class WebPImageReader extends ImageReaderBase {
} }
// RsV|I|L|E|X|A|R // RsV|I|L|E|X|A|R
int reserved = (int) imageInput.readBits(2); int reserved = lsbBitReader.readBit();
if (reserved != 0) { if (reserved != 0) {
// Spec says SHOULD be 0 // Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved)); throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
} }
header.containsICCP = imageInput.readBit() == 1; header.containsANIM = lsbBitReader.readBit() == 1; // A -> Anim
header.containsALPH = imageInput.readBit() == 1; // L -> aLpha header.containsXMP_ = lsbBitReader.readBit() == 1;
header.containsEXIF = imageInput.readBit() == 1; header.containsEXIF = lsbBitReader.readBit() == 1;
header.containsXMP_ = imageInput.readBit() == 1; header.containsALPH = lsbBitReader.readBit() == 1; // L -> aLpha
header.containsANIM = imageInput.readBit() == 1; // A -> Anim 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) { if (reserved != 0) {
// Spec says SHOULD be 0 // Spec says SHOULD be 0
throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved)); throw new IIOException(String.format("Unexpected 'VP8X' chunk reserved value, expected 0: %d", reserved));
@@ -509,15 +511,15 @@ final class WebPImageReader extends ImageReaderBase {
} }
private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException { private void readAlpha(BufferedImage destination, ImageReadParam param, final int width, final int height) throws IOException {
int reserved = (int) imageInput.readBits(2); int reserved = (int) lsbBitReader.readBits(2);
if (reserved != 0) { if (reserved != 0) {
// Spec says SHOULD be 0 // Spec says SHOULD be 0
processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved)); processWarningOccurred(String.format("Unexpected 'ALPH' chunk reserved value, expected 0: %d", reserved));
} }
int preProcessing = (int) imageInput.readBits(2); int preProcessing = (int) lsbBitReader.readBits(2);
int filtering = (int) imageInput.readBits(2); int filtering = (int) lsbBitReader.readBits(2);
int compression = (int) imageInput.readBits(2); int compression = (int) lsbBitReader.readBits(2);
if (DEBUG) { if (DEBUG) {
System.out.println("preProcessing: " + preProcessing); System.out.println("preProcessing: " + preProcessing);
@@ -55,7 +55,7 @@ final class ColorIndexingTransform implements Transform {
byte[] rgba = new byte[4]; byte[] rgba = new byte[4];
for (int y = 0; y < height; y++) { 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--) { for (int x = width - 1; x >= 0; x--) {
int componentSize = 8 >> bits; int componentSize = 8 >> bits;
@@ -67,7 +67,7 @@ final class ColorIndexingTransform implements Transform {
int index = sample >> componentOffset & ((1 << componentSize) - 1); 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); System.arraycopy(colorTable, index * 4, rgba, 0, 4);
raster.setDataElements(x, y, rgba); raster.setDataElements(x, y, rgba);
@@ -37,7 +37,7 @@ import java.awt.image.*;
* @author Simon Kammermeier * @author Simon Kammermeier
*/ */
final class HuffmanInfo { final class HuffmanInfo {
public Raster huffmanMetaCodes; //Raster allows intuitive lookup by x and y public Raster huffmanMetaCodes; // Raster allows intuitive lookup by x and y
public int metaCodeBits; public int metaCodeBits;
@@ -83,7 +83,6 @@ final class HuffmanTable {
* @throws IOException when reading produces an exception * @throws IOException when reading produces an exception
*/ */
public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException { public HuffmanTable(LSBBitReader lsbBitReader, int alphabetSize) throws IOException {
boolean simpleLengthCode = lsbBitReader.readBit() == 1; boolean simpleLengthCode = lsbBitReader.readBit() == 1;
if (simpleLengthCode) { if (simpleLengthCode) {
@@ -104,11 +103,9 @@ final class HuffmanTable {
} }
} }
else { else {
/* // code lengths also huffman coded
code lengths also huffman coded // first read the "first stage" code lengths
first read the "first stage" code lengths // In the following this is called the L-Code (for length code)
In the following this is called the L-Code (for length code)
*/
int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4); int numLCodeLengths = (int) (lsbBitReader.readBits(4) + 4);
short[] lCodeLengths = new short[L_CODE_ORDER.length]; short[] lCodeLengths = new short[L_CODE_ORDER.length];
int numPosCodeLens = 0; int numPosCodeLens = 0;
@@ -116,16 +113,15 @@ final class HuffmanTable {
for (int i = 0; i < numLCodeLengths; i++) { for (int i = 0; i < numLCodeLengths; i++) {
short len = (short) lsbBitReader.readBits(3); short len = (short) lsbBitReader.readBits(3);
lCodeLengths[L_CODE_ORDER[i]] = len; lCodeLengths[L_CODE_ORDER[i]] = len;
if (len > 0) { if (len > 0) {
numPosCodeLens++; 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); short[] codeLengths = readCodeLengths(lsbBitReader, lCodeLengths, alphabetSize, numPosCodeLens);
buildFromLengths(codeLengths); buildFromLengths(codeLengths);
} }
} }
@@ -142,25 +138,21 @@ final class HuffmanTable {
buildFromLengths(codeLengths, numPosCodeLens); buildFromLengths(codeLengths, numPosCodeLens);
} }
// Helper methods to allow reusing in different constructors
/*
Helper methods to allow reusing in different constructors
*/
private void buildFromLengths(short[] codeLengths) { private void buildFromLengths(short[] codeLengths) {
int numPosCodeLens = 0; int numPosCodeLens = 0;
for (short codeLength : codeLengths) { for (short codeLength : codeLengths) {
if (codeLength != 0) { if (codeLength != 0) {
numPosCodeLens++; numPosCodeLens++;
} }
} }
buildFromLengths(codeLengths, numPosCodeLens); buildFromLengths(codeLengths, numPosCodeLens);
} }
private void buildFromLengths(short[] codeLengths, int 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[] lengthsAndSymbols = new int[numPosCodeLens];
int index = 0; 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) { 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); 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); Arrays.sort(lengthsAndSymbols);
/* // The next code, in the bit order it would appear on the input stream, i.e. it is reversed.
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.
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.
Example: code 0..010 (length 2) would appear as 0..001.
*/
int code = 0; int code = 0;
//Used for level2 lookup // Used for level2 lookup
int rootEntry = -1; int rootEntry = -1;
int[] currentTable = null; int[] currentTable = null;
for (int i = 0; i < lengthsAndSymbols.length; i++) { for (int i = 0; i < lengthsAndSymbols.length; i++) {
int lengthAndSymbol = lengthsAndSymbols[i]; int lengthAndSymbol = lengthsAndSymbols[i];
int length = lengthAndSymbol >>> 16; int length = lengthAndSymbol >>> 16;
@@ -202,16 +191,15 @@ final class HuffmanTable {
} }
} }
else { else {
//Existing level2 table not fitting // Existing level2 table not fitting
if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) { if ((code & ((1 << LEVEL1_BITS) - 1)) != rootEntry) {
/* // Figure out needed table size.
Figure out needed table size. // Start at current symbol and length.
Start at current symbol and length. // Every symbol uses 1 slot at the current bit length.
Every symbol uses 1 slot at the current bit length. // Going up 1 bit in length multiplies the slots by 2.
Going up 1 bit in length multiplies the slots by 2. // No more open slots indicate the table size to be big enough.
No more open slots indicate the table size to be big enough.
*/
int maxLength = length; int maxLength = length;
for (int j = i, openSlots = 1 << (length - LEVEL1_BITS); for (int j = i, openSlots = 1 << (length - LEVEL1_BITS);
j < lengthsAndSymbols.length && openSlots > 0; j < lengthsAndSymbols.length && openSlots > 0;
j++, openSlots--) { j++, openSlots--) {
@@ -230,11 +218,11 @@ final class HuffmanTable {
rootEntry = code & ((1 << LEVEL1_BITS) - 1); rootEntry = code & ((1 << LEVEL1_BITS) - 1);
level2.add(currentTable); level2.add(currentTable);
//Set root table indirection // Set root table indirection
level1[rootEntry] = (LEVEL1_BITS + level2Size) << 16 | (level2.size() - 1); 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)) { for (int j = (code >>> LEVEL1_BITS); j < currentTable.length; j += 1 << (length - LEVEL1_BITS)) {
currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff); currentTable[j] = (length - LEVEL1_BITS) << 16 | (lengthAndSymbol & 0xffff);
} }
@@ -256,12 +244,12 @@ final class HuffmanTable {
private int nextCode(int code, int length) { private int nextCode(int code, int length) {
int a = (~code) & ((1 << length) - 1); 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) // 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 // I.e. the lowest 0-bit in the value code represents
int step = Integer.highestOneBit(a); 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 // 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 // This corresponds to adding 1 to the value
return (code & (step - 1)) | step; return (code & (step - 1)) | step;
} }
@@ -270,7 +258,7 @@ final class HuffmanTable {
HuffmanTable huffmanTable = new HuffmanTable(aCodeLengths, numPosCodeLens); 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; int codedSymbols;
if (lsbBitReader.readBit() == 1) { if (lsbBitReader.readBit() == 1) {
int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3)); int maxSymbolBitLength = (int) (2 + 2 * lsbBitReader.readBits(3));
@@ -282,13 +270,13 @@ final class HuffmanTable {
short[] codeLengths = new short[alphabetSize]; short[] codeLengths = new short[alphabetSize];
//Default code for repeating // Default code for repeating
short prevLength = 8; short prevLength = 8;
for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) { for (int i = 0; i < alphabetSize && codedSymbols > 0; i++, codedSymbols--) {
short len = huffmanTable.readSymbol(lsbBitReader); short len = huffmanTable.readSymbol(lsbBitReader);
if (len < 16) { //Literal length if (len < 16) { // Literal length
codeLengths[i] = len; codeLengths[i] = len;
if (len != 0) { if (len != 0) {
prevLength = len; prevLength = len;
@@ -300,16 +288,16 @@ final class HuffmanTable {
int repeatOffset; int repeatOffset;
switch (len) { switch (len) {
case 16: //Repeat previous case 16: // Repeat previous
repeatSymbol = prevLength; repeatSymbol = prevLength;
extraBits = 2; extraBits = 2;
repeatOffset = 3; repeatOffset = 3;
break; break;
case 17: //Repeat 0 short case 17: // Repeat 0 short
extraBits = 3; extraBits = 3;
repeatOffset = 3; repeatOffset = 3;
break; break;
case 18: //Repeat 0 long case 18: // Repeat 0 long
extraBits = 7; extraBits = 7;
repeatOffset = 11; repeatOffset = 11;
break; break;
@@ -319,7 +307,6 @@ final class HuffmanTable {
int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset); int repeatCount = (int) (lsbBitReader.readBits(extraBits) + repeatOffset);
if (i + repeatCount > alphabetSize) { if (i + repeatCount > alphabetSize) {
throw new IIOException( throw new IIOException(
String.format( String.format(
@@ -330,11 +317,9 @@ final class HuffmanTable {
Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol); Arrays.fill(codeLengths, i, i + repeatCount, repeatSymbol);
i += repeatCount - 1; i += repeatCount - 1;
} }
} }
return codeLengths; return codeLengths;
} }
@@ -346,21 +331,20 @@ final class HuffmanTable {
* @throws IOException when the reader throws one reading a symbol * @throws IOException when the reader throws one reading a symbol
*/ */
public short readSymbol(LSBBitReader lsbBitReader) throws IOException { public short readSymbol(LSBBitReader lsbBitReader) throws IOException {
int index = (int) lsbBitReader.peekBits(LEVEL1_BITS); int index = (int) lsbBitReader.peekBits(LEVEL1_BITS);
int lengthAndSymbol = level1[index]; int lengthAndSymbol = level1[index];
int length = lengthAndSymbol >>> 16; int length = lengthAndSymbol >>> 16;
if (length > LEVEL1_BITS) { if (length > LEVEL1_BITS) {
//Lvl2 lookup // Lvl2 lookup
lsbBitReader.readBits(LEVEL1_BITS); //Consume bits of first level lsbBitReader.readBits(LEVEL1_BITS); // Consume bits of first level
int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); //Peek remaining required bits int level2Index = (int) lsbBitReader.peekBits(length - LEVEL1_BITS); // Peek remaining required bits
lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index]; lengthAndSymbol = level2.get(lengthAndSymbol & 0xffff)[level2Index];
length = lengthAndSymbol >>> 16; length = lengthAndSymbol >>> 16;
} }
lsbBitReader.readBits(length); //Consume bits lsbBitReader.readBits(length); // Consume bits
return (short) (lengthAndSymbol & 0xffff); return (short) (lengthAndSymbol & 0xffff);
} }
@@ -51,25 +51,23 @@ final class PredictorTransform implements Transform {
@Override @Override
public void applyInverse(WritableRaster raster) { public void applyInverse(WritableRaster raster) {
int width = raster.getWidth(); int width = raster.getWidth();
int height = raster.getHeight(); int height = raster.getHeight();
byte[] rgba = new byte[4]; 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); raster.getDataElements(0, 0, rgba);
rgba[3] += 0xff; rgba[3] += 0xff;
raster.setDataElements(0, 0, rgba); raster.setDataElements(0, 0, rgba);
byte[] predictor = new byte[4]; byte[] predictor = new byte[4];
byte[] predictor2 = new byte[4]; byte[] predictor2 = new byte[4];
byte[] predictor3 = new byte[4]; byte[] predictor3 = new byte[4];
//(x,0) L predict // (x,0) L predict
for (int x = 1; x < width; x++) { for (int x = 1; x < width; x++) {
raster.getDataElements(x, 0, rgba); raster.getDataElements(x, 0, rgba);
raster.getDataElements(x - 1, 0, predictor); raster.getDataElements(x - 1, 0, predictor);
@@ -78,7 +76,7 @@ final class PredictorTransform implements Transform {
raster.setDataElements(x, 0, rgba); raster.setDataElements(x, 0, rgba);
} }
//(0,y) T predict // (0,y) T predict
for (int y = 1; y < height; y++) { for (int y = 1; y < height; y++) {
raster.getDataElements(0, y, rgba); raster.getDataElements(0, y, rgba);
raster.getDataElements(0, y - 1, predictor); raster.getDataElements(0, y - 1, predictor);
@@ -89,16 +87,14 @@ final class PredictorTransform implements Transform {
for (int y = 1; y < height; y++) { for (int y = 1; y < height; y++) {
for (int x = 1; x < width; x++) { for (int x = 1; x < width; x++) {
int transformType = data.getSample(x >> bits, y >> bits, 1); int transformType = data.getSample(x >> bits, y >> bits, 1);
raster.getDataElements(x, y, rgba); 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 trX = x == width - 1 ? 0 : x + 1;
int trY = x == width - 1 ? y : tY; 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> * @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 { interface TransformType {
int PREDICTOR_TRANSFORM = 0; int PREDICTOR_TRANSFORM = 0;
int COLOR_TRANSFORM = 1; 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 -1
View File
@@ -4,7 +4,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio</artifactId> <artifactId>imageio</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<artifactId>imageio-xwd</artifactId> <artifactId>imageio-xwd</artifactId>
<name>TwelveMonkeys :: ImageIO :: XWD plugin</name> <name>TwelveMonkeys :: ImageIO :: XWD plugin</name>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.twelvemonkeys.imageio</groupId> <groupId>com.twelvemonkeys.imageio</groupId>
+2 -2
View File
@@ -4,7 +4,7 @@
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>TwelveMonkeys</name> <name>TwelveMonkeys</name>
<description>TwelveMonkeys parent POM</description> <description>TwelveMonkeys parent POM</description>
@@ -80,7 +80,7 @@
<connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection> <connection>scm:git:https://github.com/haraldk/TwelveMonkeys</connection>
<developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection> <developerConnection>scm:git:ssh://git@github.com/haraldk/TwelveMonkeys</developerConnection>
<url>https://github.com/haraldk/TwelveMonkeys</url> <url>https://github.com/haraldk/TwelveMonkeys</url>
<tag>twelvemonkeys-3.9.0</tag> <tag>twelvemonkeys-3.9.1</tag>
</scm> </scm>
<distributionManagement> <distributionManagement>
+1 -1
View File
@@ -3,7 +3,7 @@
<parent> <parent>
<groupId>com.twelvemonkeys</groupId> <groupId>com.twelvemonkeys</groupId>
<artifactId>twelvemonkeys</artifactId> <artifactId>twelvemonkeys</artifactId>
<version>3.9.0</version> <version>3.9.1</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>