diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStream.java new file mode 100644 index 00000000..0017b7fd --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStream.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2021, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.stream.ImageInputStreamImpl; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static com.twelvemonkeys.lang.Validate.notNull; +import static java.lang.Math.max; + +/** + * A buffered replacement for {@link javax.imageio.stream.FileImageInputStream} + * that provides greatly improved performance for shorter reads, like single + * byte or bit reads. + * As with {@code javax.imageio.stream.FileImageInputStream}, either + * {@link File} or {@link RandomAccessFile} can be used as input. + * + * @see javax.imageio.stream.FileImageInputStream + */ +// TODO: Create a memory-mapped version? +// Or not... From java.nio.channels.FileChannel.map: +// For most operating systems, mapping a file into memory is more +// expensive than reading or writing a few tens of kilobytes of data via +// the usual {@link #read read} and {@link #write write} methods. From the +// standpoint of performance it is generally only worth mapping relatively +// large files into memory. +public final class BufferedFileImageInputStream extends ImageInputStreamImpl { + static final int DEFAULT_BUFFER_SIZE = 8192; + + private byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + private int bufferPos; + private int bufferLimit; + + private final ByteBuffer integralCache = ByteBuffer.allocate(8); + private final byte[] integralCacheArray = integralCache.array(); + + private RandomAccessFile raf; + + /** + * Constructs a BufferedFileImageInputStream that will read from a given File. + * + * @param file a File to read from. + * @throws IllegalArgumentException if file is null. + * @throws FileNotFoundException if file is a directory or cannot be opened for reading + * for any reason. + * @throws IOException if an I/O error occurs. + */ + public BufferedFileImageInputStream(final File file) throws IOException { + this(new RandomAccessFile(notNull(file, "file"), "r")); + } + + /** + * Constructs a BufferedFileImageInputStream that will read from a given RandomAccessFile. + * + * @param raf a RandomAccessFile to read from. + * @throws IllegalArgumentException if raf is null. + */ + public BufferedFileImageInputStream(final RandomAccessFile raf) { + this.raf = notNull(raf, "raf"); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private boolean fillBuffer() throws IOException { + bufferPos = 0; + int length = raf.read(buffer, 0, buffer.length); + bufferLimit = max(length, 0); + + return bufferLimit > 0; + } + + private boolean bufferEmpty() { + return bufferPos >= bufferLimit; + } + + @Override + public void setByteOrder(ByteOrder byteOrder) { + super.setByteOrder(byteOrder); + integralCache.order(byteOrder); + } + + @Override + public int read() throws IOException { + checkClosed(); + + if (bufferEmpty() && !fillBuffer()) { + return -1; + } + + bitOffset = 0; + streamPos++; + + return buffer[bufferPos++] & 0xff; + } + + @Override + public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + checkClosed(); + bitOffset = 0; + + if (bufferEmpty()) { + // Bypass buffer if buffer is empty for reads longer than buffer + if (pLength >= buffer.length) { + return readDirect(pBuffer, pOffset, pLength); + } + else if (!fillBuffer()) { + return -1; + } + } + + return readBuffered(pBuffer, pOffset, pLength); + } + + private int readDirect(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { + // Invalidate the buffer, as its contents is no longer in sync with the stream's position. + bufferLimit = 0; + int read = raf.read(pBuffer, pOffset, pLength); + + if (read > 0) { + streamPos += read; + } + + return read; + } + + private int readBuffered(final byte[] pBuffer, final int pOffset, final int pLength) { + // Read as much as possible from buffer + int length = Math.min(bufferLimit - bufferPos, pLength); + + if (length > 0) { + System.arraycopy(buffer, bufferPos, pBuffer, pOffset, length); + bufferPos += length; + streamPos += length; + } + + return length; + } + + public long length() { + // WTF?! This method is allowed to throw IOException in the interface... + try { + checkClosed(); + return raf.length(); + } + catch (IOException ignore) { + } + + return -1; + } + + public void close() throws IOException { + super.close(); + + raf.close(); + + raf = null; + buffer = null; + } + + // Need to override the readShort(), readInt() and readLong() methods, + // because the implementations in ImageInputStreamImpl expects the + // read(byte[], int, int) to always read the expected number of bytes, + // causing uninitialized values, alignment issues and EOFExceptions at + // random places... + // Notes: + // * readUnsignedXx() is covered by their signed counterparts + // * readChar() is covered by readShort() + // * readFloat() and readDouble() is covered by readInt() and readLong() + // respectively. + // * readLong() may be covered by two readInt()s, we'll override to be safe + + @Override + public short readShort() throws IOException { + readFully(integralCacheArray, 0, 2); + + return integralCache.getShort(0); + } + + @Override + public int readInt() throws IOException { + readFully(integralCacheArray, 0, 4); + + return integralCache.getInt(0); + } + + @Override + public long readLong() throws IOException { + readFully(integralCacheArray, 0, 8); + + return integralCache.getLong(0); + } + + @Override + public void seek(long position) throws IOException { + checkClosed(); + + if (position < flushedPos) { + throw new IndexOutOfBoundsException("position < flushedPos!"); + } + + bitOffset = 0; + + if (streamPos == position) { + return; + } + + // Optimized to not invalidate buffer if new position is within current buffer + long newBufferPos = bufferPos + position - streamPos; + if (newBufferPos >= 0 && newBufferPos <= bufferLimit) { + bufferPos = (int) newBufferPos; + } + else { + // Will invalidate buffer + bufferLimit = 0; + raf.seek(position); + } + + streamPos = position; + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpi.java new file mode 100644 index 00000000..9574ae71 --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpi.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.stream; + +import com.twelvemonkeys.imageio.spi.ProviderInfo; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; +import java.util.Locale; + +/** + * BufferedFileImageInputStreamSpi + * Experimental + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BufferedFileImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ + */ +public class BufferedFileImageInputStreamSpi extends ImageInputStreamSpi { + public BufferedFileImageInputStreamSpi() { + this(new StreamProviderInfo()); + } + + private BufferedFileImageInputStreamSpi(ProviderInfo providerInfo) { + super(providerInfo.getVendorName(), providerInfo.getVersion(), File.class); + } + + @Override + public void onRegistration(final ServiceRegistry registry, final Class category) { + Iterator providers = registry.getServiceProviders(ImageInputStreamSpi.class, new FileInputFilter(), true); + + while (providers.hasNext()) { + ImageInputStreamSpi provider = providers.next(); + if (provider != this) { + registry.setOrdering(ImageInputStreamSpi.class, this, provider); + } + } + } + + public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) throws IOException { + if (input instanceof File) { + File file = (File) input; + return new BufferedFileImageInputStream(file); + } + else { + throw new IllegalArgumentException("Expected input of type URL: " + input); + } + } + + @Override + public boolean canUseCacheFile() { + return false; + } + + public String getDescription(final Locale pLocale) { + return "Service provider that instantiates an ImageInputStream from a File"; + } + + private static class FileInputFilter implements ServiceRegistry.Filter { + @Override + public boolean filter(final Object provider) { + return ((ImageInputStreamSpi) provider).getInputClass() == File.class; + } + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java index dca1a5ca..9b320929 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java @@ -43,15 +43,18 @@ import static com.twelvemonkeys.lang.Validate.notNull; * A buffered {@code ImageInputStream}. * Experimental - seems to be effective for {@link javax.imageio.stream.FileImageInputStream} * and {@link javax.imageio.stream.FileCacheImageInputStream} when doing a lot of single-byte reads - * (or short byte-array reads) on OS X at least. + * (or short byte-array reads). * Code that uses the {@code readFully} methods are not affected by the issue. + *

+ * NOTE: Invoking {@code close()} will NOT close the wrapped stream. * * @author Harald Kuhr * @author last modified by $Author: haraldk$ * @version $Id: BufferedFileImageInputStream.java,v 1.0 May 15, 2008 4:36:49 PM haraldk Exp$ + * + * @deprecated Use {@link BufferedFileImageInputStream} instead. */ -// TODO: Create a provider for this (wrapping the FileIIS and FileCacheIIS classes), and disable the Sun built-in spis? -// TODO: Test on other platforms, might be just an OS X issue +@Deprecated public final class BufferedImageInputStream extends ImageInputStreamImpl implements ImageInputStream { static final int DEFAULT_BUFFER_SIZE = 8192; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpi.java new file mode 100644 index 00000000..bb5e3fef --- /dev/null +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpi.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2021, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.stream; + +import com.twelvemonkeys.imageio.spi.ProviderInfo; + +import javax.imageio.spi.ImageInputStreamSpi; +import javax.imageio.spi.ServiceRegistry; +import javax.imageio.stream.ImageInputStream; +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Iterator; +import java.util.Locale; + +/** + * BufferedRAFImageInputStreamSpi + * Experimental + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BufferedRAFImageInputStreamSpi.java,v 1.0 May 15, 2008 2:14:59 PM haraldk Exp$ + */ +public class BufferedRAFImageInputStreamSpi extends ImageInputStreamSpi { + public BufferedRAFImageInputStreamSpi() { + this(new StreamProviderInfo()); + } + + private BufferedRAFImageInputStreamSpi(ProviderInfo providerInfo) { + super(providerInfo.getVendorName(), providerInfo.getVersion(), RandomAccessFile.class); + } + + @Override + public void onRegistration(final ServiceRegistry registry, final Class category) { + Iterator providers = registry.getServiceProviders(ImageInputStreamSpi.class, new RAFInputFilter(), true); + + while (providers.hasNext()) { + ImageInputStreamSpi provider = providers.next(); + if (provider != this) { + registry.setOrdering(ImageInputStreamSpi.class, this, provider); + } + } + } + + public ImageInputStream createInputStreamInstance(final Object input, final boolean pUseCache, final File pCacheDir) { + if (input instanceof RandomAccessFile) { + RandomAccessFile file = (RandomAccessFile) input; + return new BufferedFileImageInputStream(file); + } + else { + throw new IllegalArgumentException("Expected input of type URL: " + input); + } + } + + @Override + public boolean canUseCacheFile() { + return false; + } + + public String getDescription(final Locale pLocale) { + return "Service provider that instantiates an ImageInputStream from a RandomAccessFile"; + } + + private static class RAFInputFilter implements ServiceRegistry.Filter { + @Override + public boolean filter(final Object provider) { + return ((ImageInputStreamSpi) provider).getInputClass() == RandomAccessFile.class; + } + } +} diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java index e0426b0a..b56204c7 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/URLImageInputStreamSpi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, Harald Kuhr + * Copyright (c) 2021, Harald Kuhr * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +34,6 @@ import com.twelvemonkeys.imageio.spi.ProviderInfo; import javax.imageio.spi.ImageInputStreamSpi; import javax.imageio.stream.FileCacheImageInputStream; -import javax.imageio.stream.FileImageInputStream; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.MemoryCacheImageInputStream; import java.io.File; @@ -72,7 +71,7 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi { // Special case for file protocol, a lot faster than FileCacheImageInputStream if ("file".equals(url.getProtocol())) { try { - return new BufferedImageInputStream(new FileImageInputStream(new File(url.toURI()))); + return new BufferedFileImageInputStream(new File(url.toURI())); } catch (URISyntaxException ignore) { // This should never happen, but if it does, we'll fall back to using the stream @@ -81,29 +80,29 @@ public class URLImageInputStreamSpi extends ImageInputStreamSpi { } // Otherwise revert to cached - final InputStream stream = url.openStream(); + final InputStream urlStream = url.openStream(); if (pUseCache) { - return new BufferedImageInputStream(new FileCacheImageInputStream(stream, pCacheDir) { + return new FileCacheImageInputStream(urlStream, pCacheDir) { @Override public void close() throws IOException { try { super.close(); } finally { - stream.close(); // NOTE: If this line throws IOE, it will shadow the original.. + urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original.. } } - }); + }; } else { - return new MemoryCacheImageInputStream(stream) { + return new MemoryCacheImageInputStream(urlStream) { @Override public void close() throws IOException { try { super.close(); } finally { - stream.close(); // NOTE: If this line throws IOE, it will shadow the original.. + urlStream.close(); // NOTE: If this line throws IOE, it will shadow the original.. } } }; diff --git a/imageio/imageio-core/src/main/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi b/imageio/imageio-core/src/main/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi new file mode 100644 index 00000000..c31ffbaf --- /dev/null +++ b/imageio/imageio-core/src/main/resources/META-INF/services/javax.imageio.spi.ImageInputStreamSpi @@ -0,0 +1,2 @@ +com.twelvemonkeys.imageio.stream.BufferedFileImageInputStreamSpi +com.twelvemonkeys.imageio.stream.BufferedRAFImageInputStreamSpi diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpiTest.java new file mode 100644 index 00000000..5622fd2f --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamSpiTest.java @@ -0,0 +1,17 @@ +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.spi.ImageInputStreamSpi; +import java.io.File; +import java.io.IOException; + +public class BufferedFileImageInputStreamSpiTest extends ImageInputStreamSpiTest { + @Override + protected ImageInputStreamSpi createProvider() { + return new BufferedFileImageInputStreamSpi(); + } + + @Override + protected File createInput() throws IOException { + return File.createTempFile("test-", ".tst"); + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamTest.java new file mode 100755 index 00000000..822db90e --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedFileImageInputStreamTest.java @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2020, Harald Kuhr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.twelvemonkeys.imageio.stream; + +import org.junit.Test; +import org.junit.function.ThrowingRunnable; + +import javax.imageio.stream.ImageInputStream; +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.file.Files; +import java.util.Random; + +import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rangeEquals; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * BufferedFileImageInputStreamTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: BufferedFileImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ + */ +public class BufferedFileImageInputStreamTest { + private final Random random = new Random(170984354357234566L); + + private File randomDataToFile(byte[] data) throws IOException { + random.nextBytes(data); + + File file = File.createTempFile("read", ".tmp"); + Files.write(file.toPath(), data); + return file; + } + + @Test + public void testCreate() throws IOException { + BufferedFileImageInputStream stream = new BufferedFileImageInputStream(File.createTempFile("empty", ".tmp")); + assertEquals("Data length should be same as stream length", 0, stream.length()); + } + + @Test + public void testCreateNullFile() throws IOException { + try { + new BufferedFileImageInputStream((File) null); + fail("Expected IllegalArgumentException"); + } + catch (IllegalArgumentException expected) { + assertNotNull("Null exception message", expected.getMessage()); + String message = expected.getMessage().toLowerCase(); + assertTrue("Exception message does not contain parameter name", message.contains("file")); + assertTrue("Exception message does not contain null", message.contains("null")); + } + } + + @Test + public void testCreateNullRAF() { + try { + new BufferedFileImageInputStream((RandomAccessFile) null); + fail("Expected IllegalArgumentException"); + } + catch (IllegalArgumentException expected) { + assertNotNull("Null exception message", expected.getMessage()); + String message = expected.getMessage().toLowerCase(); + assertTrue("Exception message does not contain parameter name", message.contains("raf")); + assertTrue("Exception message does not contain null", message.contains("null")); + } + } + + @Test + public void testRead() throws IOException { + byte[] data = new byte[1024 * 1024]; + File file = randomDataToFile(data); + + BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file); + + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + for (byte value : data) { + assertEquals("Wrong data read", value & 0xff, stream.read()); + } + } + + @Test + public void testReadArray() throws IOException { + byte[] data = new byte[1024 * 1024]; + File file = randomDataToFile(data); + + BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file); + + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] result = new byte[1024]; + + for (int i = 0; i < data.length / result.length; i++) { + stream.readFully(result); + assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length)); + } + } + + @Test + public void testReadSkip() throws IOException { + byte[] data = new byte[1024 * 14]; + File file = randomDataToFile(data); + + BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file); + + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] result = new byte[7]; + + for (int i = 0; i < data.length / result.length; i += 2) { + stream.readFully(result); + stream.skipBytes(result.length); + assertTrue("Wrong data read: " + i, rangeEquals(data, i * result.length, result, 0, result.length)); + } + } + + @Test + public void testReadSeek() throws IOException { + byte[] data = new byte[1024 * 18]; + File file = randomDataToFile(data); + + BufferedFileImageInputStream stream = new BufferedFileImageInputStream(file); + + assertEquals("File length should be same as stream length", file.length(), stream.length()); + + byte[] result = new byte[9]; + + for (int i = 0; i < data.length / result.length; i++) { + // Read backwards + long newPos = stream.length() - result.length - i * result.length; + stream.seek(newPos); + assertEquals("Wrong stream position", newPos, stream.getStreamPosition()); + stream.readFully(result); + assertTrue("Wrong data read: " + i, rangeEquals(data, (int) newPos, result, 0, result.length)); + } + } + + @Test + public void testReadBitRandom() throws IOException { + byte[] bytes = new byte[8]; + File file = randomDataToFile(bytes); + long value = ByteBuffer.wrap(bytes).getLong(); + + // Create stream + ImageInputStream stream = new BufferedFileImageInputStream(file); + + for (int i = 1; i <= 64; i++) { + assertEquals(String.format("bit %d differ", i), (value << (i - 1L)) >>> 63L, stream.readBit()); + } + } + + @Test + public void testReadBitsRandom() throws IOException { + byte[] bytes = new byte[8]; + File file = randomDataToFile(bytes); + long value = ByteBuffer.wrap(bytes).getLong(); + + // Create stream + ImageInputStream stream = new BufferedFileImageInputStream(file); + + for (int i = 1; i <= 64; i++) { + stream.seek(0); + assertEquals(String.format("bit %d differ", i), value >>> (64L - i), stream.readBits(i)); + assertEquals(i % 8, stream.getBitOffset()); + } + } + + @Test + public void testReadBitsRandomOffset() throws IOException { + byte[] bytes = new byte[8]; + File file = randomDataToFile(bytes); + long value = ByteBuffer.wrap(bytes).getLong(); + + // Create stream + ImageInputStream stream = new BufferedFileImageInputStream(file); + + for (int i = 1; i <= 60; i++) { + stream.seek(0); + stream.setBitOffset(i % 8); + assertEquals(String.format("bit %d differ", i), (value << (i % 8)) >>> (64L - i), stream.readBits(i)); + assertEquals(i * 2 % 8, stream.getBitOffset()); + } + } + + @Test + public void testReadShort() throws IOException { + byte[] bytes = new byte[8743]; // Slightly more than one buffer size + File file = randomDataToFile(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + final ImageInputStream stream = new BufferedFileImageInputStream(file); + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + for (int i = 0; i < bytes.length / 2; i++) { + assertEquals(buffer.getShort(), stream.readShort()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readShort(); + } + }); + + stream.seek(0); + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < bytes.length / 2; i++) { + assertEquals(buffer.getShort(), stream.readShort()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readShort(); + } + }); + } + + @Test + public void testReadInt() throws IOException { + byte[] bytes = new byte[8743]; // Slightly more than one buffer size + File file = randomDataToFile(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + final ImageInputStream stream = new BufferedFileImageInputStream(file); + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + for (int i = 0; i < bytes.length / 4; i++) { + assertEquals(buffer.getInt(), stream.readInt()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readInt(); + } + }); + + stream.seek(0); + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < bytes.length / 4; i++) { + assertEquals(buffer.getInt(), stream.readInt()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readInt(); + } + }); + } + + @Test + public void testReadLong() throws IOException { + byte[] bytes = new byte[8743]; // Slightly more than one buffer size + File file = randomDataToFile(bytes); + ByteBuffer buffer = ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN); + final ImageInputStream stream = new BufferedFileImageInputStream(file); + stream.setByteOrder(ByteOrder.BIG_ENDIAN); + + for (int i = 0; i < bytes.length / 8; i++) { + assertEquals(buffer.getLong(), stream.readLong()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readLong(); + } + }); + + stream.seek(0); + stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); + buffer.position(0); + buffer.order(ByteOrder.LITTLE_ENDIAN); + + for (int i = 0; i < bytes.length / 8; i++) { + assertEquals(buffer.getLong(), stream.readLong()); + } + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readLong(); + } + }); + } + + @Test + public void testSeekPastEOF() throws IOException { + byte[] bytes = new byte[9]; + File file = randomDataToFile(bytes); + + final ImageInputStream stream = new BufferedFileImageInputStream(file); + stream.seek(1000); + + assertEquals(-1, stream.read()); + assertEquals(-1, stream.read(new byte[1], 0, 1)); + + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readFully(new byte[1]); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readByte(); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readShort(); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readInt(); + } + }); + assertThrows(EOFException.class, new ThrowingRunnable() { + @Override + public void run() throws Throwable { + stream.readLong(); + } + }); + + stream.seek(0); + for (byte value : bytes) { + assertEquals(value, stream.readByte()); + } + + assertEquals(-1, stream.read()); + } + + @Test + public void testClose() throws IOException { + // Create wrapper stream + RandomAccessFile mock = mock(RandomAccessFile.class); + ImageInputStream stream = new BufferedFileImageInputStream(mock); + + stream.close(); + verify(mock, only()).close(); + } +} diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpiTest.java new file mode 100644 index 00000000..8441bcb8 --- /dev/null +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedRAFImageInputStreamSpiTest.java @@ -0,0 +1,18 @@ +package com.twelvemonkeys.imageio.stream; + +import javax.imageio.spi.ImageInputStreamSpi; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; + +public class BufferedRAFImageInputStreamSpiTest extends ImageInputStreamSpiTest { + @Override + protected ImageInputStreamSpi createProvider() { + return new BufferedRAFImageInputStreamSpi(); + } + + @Override + protected RandomAccessFile createInput() throws IOException { + return new RandomAccessFile(File.createTempFile("test-", ".tst"), "r"); + } +} \ No newline at end of file diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java index 35a3d677..5d770994 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTest.java @@ -39,11 +39,11 @@ import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTest.rang import static org.junit.Assert.*; /** - * ByteArrayImageInputStreamTestCase + * ByteArrayImageInputStreamTest * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: ByteArrayImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ + * @version $Id: ByteArrayImageInputStreamTest.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ */ public class ByteArrayImageInputStreamTest { private final Random random = new Random(1709843507234566L); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java index df95fee9..cbc69648 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ImageInputStreamSpiTest.java @@ -18,7 +18,7 @@ abstract class ImageInputStreamSpiTest { protected abstract ImageInputStreamSpi createProvider(); - protected abstract T createInput(); + protected abstract T createInput() throws IOException; @Test public void testInputClass() { diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java index cc2f1439..d3c8f6ef 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java @@ -30,6 +30,17 @@ package com.twelvemonkeys.imageio.plugins.iff; +import com.twelvemonkeys.image.ResampleOp; +import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; +import com.twelvemonkeys.io.enc.DecoderStream; +import com.twelvemonkeys.io.enc.PackBitsDecoder; + +import javax.imageio.*; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.*; @@ -41,23 +52,6 @@ import java.util.Arrays; import java.util.Iterator; import java.util.List; -import javax.imageio.IIOException; -import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.spi.ImageReaderSpi; -import javax.imageio.stream.ImageInputStream; - -import com.twelvemonkeys.image.ResampleOp; -import com.twelvemonkeys.imageio.ImageReaderBase; -import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; -import com.twelvemonkeys.imageio.util.IIOUtil; -import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; -import com.twelvemonkeys.io.enc.DecoderStream; -import com.twelvemonkeys.io.enc.PackBitsDecoder; - /** * Reader for Commodore Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) and PBM * format (Packed BitMap). @@ -824,8 +818,7 @@ public final class IFFImageReader extends ImageReaderBase { continue; } - try { - ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(file)); + try (ImageInputStream input = ImageIO.createImageInputStream(file)) { boolean canRead = reader.getOriginatingProvider().canDecodeInput(input); if (canRead) { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java index fc89031f..84da94f2 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReader.java @@ -30,37 +30,6 @@ package com.twelvemonkeys.imageio.plugins.jpeg; -import java.awt.*; -import java.awt.color.ColorSpace; -import java.awt.color.ICC_ColorSpace; -import java.awt.color.ICC_Profile; -import java.awt.image.*; -import java.io.DataInput; -import java.io.DataInputStream; -import java.io.EOFException; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.SequenceInputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; - -import javax.imageio.IIOException; -import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageReader; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.event.IIOReadUpdateListener; -import javax.imageio.event.IIOReadWarningListener; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; -import javax.imageio.metadata.IIOMetadataNode; -import javax.imageio.spi.ImageReaderSpi; -import javax.imageio.stream.ImageInputStream; - import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.color.YCbCrConverter; @@ -72,7 +41,6 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; import com.twelvemonkeys.imageio.metadata.tiff.TIFF; import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader; -import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.imageio.util.ImageTypeSpecifiers; @@ -80,6 +48,23 @@ import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.xml.XMLSerializer; +import javax.imageio.*; +import javax.imageio.event.IIOReadUpdateListener; +import javax.imageio.event.IIOReadWarningListener; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import javax.imageio.spi.ImageReaderSpi; +import javax.imageio.stream.ImageInputStream; +import java.awt.*; +import java.awt.color.ColorSpace; +import java.awt.color.ICC_ColorSpace; +import java.awt.color.ICC_Profile; +import java.awt.image.*; +import java.io.*; +import java.util.List; +import java.util.*; + /** * A JPEG {@code ImageReader} implementation based on the JRE {@code JPEGImageReader}, * that adds support and properly handles cases where the JRE version throws exceptions. @@ -140,7 +125,7 @@ public final class JPEGImageReader extends ImageReaderBase { private List segments; private int currentStreamIndex = 0; - private List streamOffsets = new ArrayList<>(); + private final List streamOffsets = new ArrayList<>(); protected JPEGImageReader(final ImageReaderSpi provider, final ImageReader delegate) { super(provider); @@ -197,10 +182,10 @@ public final class JPEGImageReader extends ImageReaderBase { return true; } } - catch (IIOException ignore) { + catch (IIOException e) { // May happen if no SOF is found, in case we'll just fall through if (DEBUG) { - ignore.printStackTrace(); + e.printStackTrace(); } } @@ -747,26 +732,26 @@ public final class JPEGImageReader extends ImageReaderBase { long lastKnownSOIOffset = streamOffsets.get(streamOffsets.size() - 1); imageInput.seek(lastKnownSOIOffset); - try (ImageInputStream stream = new BufferedImageInputStream(imageInput)) { // Extreme (10s -> 50ms) speedup if imageInput is FileIIS + try { for (int i = streamOffsets.size() - 1; i < imageIndex; i++) { long start = 0; if (DEBUG) { start = System.currentTimeMillis(); - System.out.println(String.format("Start seeking for image index %d", i + 1)); + System.out.printf("Start seeking for image index %d%n", i + 1); } // Need to skip over segments, as they may contain JPEG markers (eg. JFXX or EXIF thumbnail) - JPEGSegmentUtil.readSegments(stream, Collections.>emptyMap()); + JPEGSegmentUtil.readSegments(imageInput, Collections.>emptyMap()); // Now, search for EOI and following SOI... int marker; - while ((marker = stream.read()) != -1) { - if (marker == 0xFF && (0xFF00 | stream.readUnsignedByte()) == JPEG.EOI) { + while ((marker = imageInput.read()) != -1) { + if (marker == 0xFF && (0xFF00 | imageInput.readUnsignedByte()) == JPEG.EOI) { // Found EOI, now the SOI should be nearby... - while ((marker = stream.read()) != -1) { - if (marker == 0xFF && (0xFF00 | stream.readUnsignedByte()) == JPEG.SOI) { - long nextSOIOffset = stream.getStreamPosition() - 2; + while ((marker = imageInput.read()) != -1) { + if (marker == 0xFF && (0xFF00 | imageInput.readUnsignedByte()) == JPEG.SOI) { + long nextSOIOffset = imageInput.getStreamPosition() - 2; imageInput.seek(nextSOIOffset); streamOffsets.add(nextSOIOffset); @@ -780,10 +765,9 @@ public final class JPEGImageReader extends ImageReaderBase { } if (DEBUG) { - System.out.println(String.format("Seek in %d ms", System.currentTimeMillis() - start)); + System.out.printf("Seek in %d ms%n", System.currentTimeMillis() - start); } } - } catch (EOFException eof) { IndexOutOfBoundsException ioobe = new IndexOutOfBoundsException("Image index " + imageIndex + " not found in stream"); @@ -843,9 +827,9 @@ public final class JPEGImageReader extends ImageReaderBase { return JPEGSegmentUtil.readSegments(imageInput, JPEGSegmentUtil.ALL_SEGMENTS); } - catch (IIOException | IllegalArgumentException ignore) { + catch (IIOException | IllegalArgumentException e) { if (DEBUG) { - ignore.printStackTrace(); + e.printStackTrace(); } } finally { @@ -1392,7 +1376,7 @@ public final class JPEGImageReader extends ImageReaderBase { final String arg = args[argIdx]; if (arg.charAt(0) == '-') { - if (arg.equals("-s") || arg.equals("--subsample") && args.length > argIdx) { + if (arg.equals("-s") || arg.equals("--subsample") && args.length > argIdx + 1) { String[] sub = args[++argIdx].split(","); try { @@ -1411,7 +1395,7 @@ public final class JPEGImageReader extends ImageReaderBase { System.err.println("Bad sub sampling (x,y): '" + args[argIdx] + "'"); } } - else if (arg.equals("-r") || arg.equals("--roi") && args.length > argIdx) { + else if (arg.equals("-r") || arg.equals("--roi") && args.length > argIdx + 1) { String[] region = args[++argIdx].split(","); try { diff --git a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java index 07d7d7dd..fdac72db 100644 --- a/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java +++ b/imageio/imageio-jpeg/src/main/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGLosslessDecoderWrapper.java @@ -31,8 +31,6 @@ package com.twelvemonkeys.imageio.plugins.jpeg; -import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; - import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -77,7 +75,7 @@ final class JPEGLosslessDecoderWrapper { * @throws IOException is thrown if the decoder failed or a conversion is not supported */ BufferedImage readImage(final List segments, final ImageInputStream input) throws IOException { - JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, createBufferedInput(input), listenerDelegate); + JPEGLosslessDecoder decoder = new JPEGLosslessDecoder(segments, input, listenerDelegate); // TODO: Allow 10/12/14 bit (using a ComponentColorModel with correct bits, as in TIFF) // TODO: Rewrite this to pass a pre-allocated buffer of correct type (byte/short)/correct bands @@ -111,10 +109,6 @@ final class JPEGLosslessDecoderWrapper { throw new IIOException("JPEG Lossless with " + decoder.getPrecision() + " bit precision and " + decoder.getNumComponents() + " component(s) not supported"); } - private ImageInputStream createBufferedInput(final ImageInputStream input) throws IOException { - return input instanceof BufferedImageInputStream ? input : new BufferedImageInputStream(input); - } - Raster readRaster(final List segments, final ImageInputStream input) throws IOException { // TODO: Can perhaps be implemented faster return readImage(segments, input).getRaster(); diff --git a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java index b5a95d7d..64b0d300 100644 --- a/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java +++ b/imageio/imageio-jpeg/src/test/java/com/twelvemonkeys/imageio/plugins/jpeg/JPEGImageReaderTest.java @@ -1415,7 +1415,7 @@ public class JPEGImageReaderTest extends ImageReaderAbstractTest