diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java index ec0bd874..8a4ea85d 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/svg/SVGImageReader.java @@ -98,8 +98,8 @@ public class SVGImageReader extends ImageReaderBase { public void setInput(Object pInput, boolean pSeekForwardOnly, boolean pIgnoreMetadata) { super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata); - if (mImageInput != null) { - TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(mImageInput)); + if (imageInput != null) { + TranscoderInput input = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput)); mRasterizer.setInput(input); } } diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java index caab3b4c..3eebd02d 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageReader.java @@ -137,25 +137,25 @@ public class TIFFImageReader extends ImageReaderBase { private synchronized void init() { if (mDecoder == null) { - if (mImageInput == null) { + if (imageInput == null) { throw new IllegalStateException("input == null"); } mDecoder = new TIFFImageDecoder(new SeekableStream() { public int read() throws IOException { - return mImageInput.read(); + return imageInput.read(); } public int read(final byte[] pBytes, final int pStart, final int pLength) throws IOException { - return mImageInput.read(pBytes, pStart, pLength); + return imageInput.read(pBytes, pStart, pLength); } public long getFilePointer() throws IOException { - return mImageInput.getStreamPosition(); + return imageInput.getStreamPosition(); } public void seek(final long pPos) throws IOException { - mImageInput.seek(pPos); + imageInput.seek(pPos); } }, null); } diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java index 212dc2f4..ccf33c48 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/tiff/TIFFImageWriter.java @@ -124,7 +124,7 @@ public class TIFFImageWriter extends ImageWriterBase { processImageStarted(0); mEncoder.encode(image); - mImageOutput.flush(); + imageOutput.flush(); processImageComplete(); } @@ -136,10 +136,10 @@ public class TIFFImageWriter extends ImageWriterBase { private synchronized void init() { if (mEncoder == null) { - if (mImageOutput == null) { + if (imageOutput == null) { throw new IllegalStateException("output == null"); } - mEncoder = new TIFFImageEncoder(IIOUtil.createStreamAdapter(mImageOutput), null); + mEncoder = new TIFFImageEncoder(IIOUtil.createStreamAdapter(imageOutput), null); } } } diff --git a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java index 9f78ed5c..96b4c6f8 100755 --- a/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java +++ b/imageio/imageio-batik/src/main/java/com/twelvemonkeys/imageio/plugins/wmf/WMFImageReader.java @@ -88,7 +88,7 @@ public class WMFImageReader extends ImageReaderBase { private synchronized void init() throws IOException { // Need the extra test, to avoid throwing an IOException from the Transcoder - if (mImageInput == null) { + if (imageInput == null) { throw new IllegalStateException("input == null"); } @@ -98,7 +98,7 @@ public class WMFImageReader extends ImageReaderBase { ByteArrayOutputStream output = new ByteArrayOutputStream(); Writer writer = new OutputStreamWriter(output, "UTF8"); try { - TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(mImageInput)); + TranscoderInput in = new TranscoderInput(IIOUtil.createStreamAdapter(imageInput)); TranscoderOutput out = new TranscoderOutput(writer); // TODO: Transcodinghints? diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java index 4646bf48..4b1bed97 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageReaderBase.java @@ -37,9 +37,7 @@ import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.io.File; @@ -61,7 +59,7 @@ public abstract class ImageReaderBase extends ImageReader { * For convenience. Only set if the input is an {@code ImageInputStream}. * @see #setInput(Object, boolean, boolean) */ - protected ImageInputStream mImageInput; + protected ImageInputStream imageInput; /** * Constructs an {@code ImageReader} and sets its @@ -102,7 +100,7 @@ public abstract class ImageReaderBase extends ImageReader { resetMembers(); super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata); if (pInput instanceof ImageInputStream) { - mImageInput = (ImageInputStream) pInput; + imageInput = (ImageInputStream) pInput; } } @@ -229,7 +227,7 @@ public abstract class ImageReaderBase extends ImageReader { if (dest != null) { boolean found = false; - // NOTE: This is bad, as it relies on implementation details of super method... + // NOTE: This is bad, as it relies on implementation details of "super" method... // We know that the iterator has not been touched if explicit destination.. while (pTypes.hasNext()) { ImageTypeSpecifier specifier = pTypes.next(); @@ -318,7 +316,27 @@ public abstract class ImageReaderBase extends ImageReader { SwingUtilities.invokeAndWait(new Runnable() { public void run() { JFrame frame = new JFrame(pTitle); - frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + frame.getRootPane().getActionMap().put("window-close", new AbstractAction() { + public void actionPerformed(ActionEvent e) { + Window window = SwingUtilities.getWindowAncestor((Component) e.getSource()); + window.setVisible(false); + window.dispose(); + } + }); + frame.getRootPane().getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), "window-close"); + + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + Window[] windows = Window.getWindows(); + if (windows == null || windows.length == 0) { + System.exit(0); + } + } + }); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.setLocationByPlatform(true); JPanel pane = new JPanel(new BorderLayout()); JScrollPane scroll = new JScrollPane(new ImageLabel(pImage)); @@ -338,6 +356,10 @@ public abstract class ImageReaderBase extends ImageReader { } } + protected static boolean hasExplicitDestination(final ImageReadParam pParam) { + return (pParam != null && (pParam.getDestination() != null || pParam.getDestinationType() != null || pParam.getDestinationOffset() != null)); + } + private static class ImageLabel extends JLabel { Paint mBackground; diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java index 8c60c2d2..5f18b336 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/ImageWriterBase.java @@ -52,7 +52,7 @@ public abstract class ImageWriterBase extends ImageWriter { * For convenience. Only set if the output is an {@code ImageInputStream}. * @see #setOutput(Object) */ - protected ImageOutputStream mImageOutput; + protected ImageOutputStream imageOutput; /** * Constructs an {@code ImageWriter} and sets its @@ -80,7 +80,7 @@ public abstract class ImageWriterBase extends ImageWriter { super.setOutput(pOutput); if (pOutput instanceof ImageOutputStream) { - mImageOutput = (ImageOutputStream) pOutput; + imageOutput = (ImageOutputStream) pOutput; } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java index f9deb312..f78bbbe5 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/spi/ProviderInfo.java @@ -16,9 +16,9 @@ public class ProviderInfo { // TODO: Consider reading the META-INF/MANIFEST.MF from the class path using java.util.jar.Manifest. // Use the manifest that is located in the same class path folder as the package. - private final String mTitle; - private final String mVendorName; - private final String mVersion; + private final String title; + private final String vendorName; + private final String version; /** * Creates a provider information instance based on the given package. @@ -32,13 +32,13 @@ public class ProviderInfo { Validate.notNull(pPackage, "package"); String title = pPackage.getImplementationTitle(); - mTitle = title != null ? title : pPackage.getName(); + this.title = title != null ? title : pPackage.getName(); String vendor = pPackage.getImplementationVendor(); - mVendorName = vendor != null ? vendor : fakeVendor(pPackage); + vendorName = vendor != null ? vendor : fakeVendor(pPackage); String version = pPackage.getImplementationVersion(); - mVersion = version != null ? version : fakeVersion(pPackage); + this.version = version != null ? version : fakeVersion(pPackage); } private static String fakeVendor(final Package pPackage) { @@ -60,7 +60,7 @@ public class ProviderInfo { * @return the implementation title */ final String getImplementationTitle() { - return mTitle; + return title; } /** @@ -72,7 +72,7 @@ public class ProviderInfo { * @return the vendor name. */ public final String getVendorName() { - return mVendorName; + return vendorName; } /** @@ -83,11 +83,11 @@ public class ProviderInfo { * @return the vendor name. */ public final String getVersion() { - return mVersion; + return version; } @Override public String toString() { - return mTitle + ", " + mVersion + " by " + mVendorName; + return title + ", " + version + " by " + vendorName; } } 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 9c75baa2..221a22ba 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 @@ -23,12 +23,12 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme static final int DEFAULT_BUFFER_SIZE = 8192; - private ImageInputStream mStream; + private ImageInputStream stream; - private byte[] mBuffer; - private long mBufferStart = 0; - private int mBufferPos = 0; - private int mBufferLength = 0; + private byte[] buffer; + private long bufferStart = 0; + private int bufferPos = 0; + private int bufferLength = 0; public BufferedImageInputStream(final ImageInputStream pStream) throws IOException { this(pStream, DEFAULT_BUFFER_SIZE); @@ -37,19 +37,19 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme private BufferedImageInputStream(final ImageInputStream pStream, final int pBufferSize) throws IOException { Validate.notNull(pStream, "stream"); - mStream = pStream; + stream = pStream; streamPos = pStream.getStreamPosition(); - mBuffer = new byte[pBufferSize]; + buffer = new byte[pBufferSize]; } private void fillBuffer() throws IOException { - mBufferStart = streamPos; - mBufferLength = mStream.read(mBuffer, 0, mBuffer.length); - mBufferPos = 0; + bufferStart = streamPos; + bufferLength = stream.read(buffer, 0, buffer.length); + bufferPos = 0; } private boolean isBufferValid() throws IOException { - return mBufferPos < mBufferLength && mBufferStart == mStream.getStreamPosition() - mBufferLength; + return bufferPos < bufferLength && bufferStart == stream.getStreamPosition() - bufferLength; } @Override @@ -58,14 +58,14 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme fillBuffer(); } - if (mBufferLength <= 0) { + if (bufferLength <= 0) { return -1; } bitOffset = 0; streamPos++; - return mBuffer[mBufferPos++] & 0xff; + return buffer[bufferPos++] & 0xff; } @Override @@ -74,7 +74,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme if (!isBufferValid()) { // Bypass cache if cache is empty for reads longer than buffer - if (pLength >= mBuffer.length) { + if (pLength >= buffer.length) { return readDirect(pBuffer, pOffset, pLength); } else { @@ -87,30 +87,30 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme private int readDirect(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException { // TODO: Figure out why reading more than the buffer length causes alignment issues... - int read = mStream.read(pBuffer, pOffset, Math.min(mBuffer.length, pLength)); + int read = stream.read(pBuffer, pOffset, Math.min(buffer.length, pLength)); if (read > 0) { streamPos += read; } - mBufferStart = mStream.getStreamPosition(); - mBufferLength = 0; + bufferStart = stream.getStreamPosition(); + bufferLength = 0; return read; } private int readBuffered(final byte[] pBuffer, final int pOffset, final int pLength) { - if (mBufferLength <= 0) { + if (bufferLength <= 0) { return -1; } // Read as much as possible from buffer - int length = Math.min(mBufferLength - mBufferPos, pLength); + int length = Math.min(bufferLength - bufferPos, pLength); if (length > 0) { - System.arraycopy(mBuffer, mBufferPos, pBuffer, pOffset, length); - mBufferPos += length; + System.arraycopy(buffer, bufferPos, pBuffer, pOffset, length); + bufferPos += length; } streamPos += length; @@ -121,42 +121,42 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme @Override public void seek(long pPosition) throws IOException { // TODO: Could probably be optimized to not invalidate buffer if new position is within current buffer - mStream.seek(pPosition); - mBufferLength = 0; // Will invalidate buffer - streamPos = mStream.getStreamPosition(); + stream.seek(pPosition); + bufferLength = 0; // Will invalidate buffer + streamPos = stream.getStreamPosition(); } @Override public void flushBefore(long pos) throws IOException { - mStream.flushBefore(pos); + stream.flushBefore(pos); } @Override public long getFlushedPosition() { - return mStream.getFlushedPosition(); + return stream.getFlushedPosition(); } @Override public boolean isCached() { - return mStream.isCached(); + return stream.isCached(); } @Override public boolean isCachedMemory() { - return mStream.isCachedMemory(); + return stream.isCachedMemory(); } @Override public boolean isCachedFile() { - return mStream.isCachedFile(); + return stream.isCachedFile(); } @Override public void close() throws IOException { - if (mStream != null) { - //mStream.close(); - mStream = null; - mBuffer = null; + if (stream != null) { + //stream.close(); + stream = null; + buffer = null; } super.close(); } @@ -170,7 +170,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme public long length() { // WTF?! This method is allowed to throw IOException in the interface... try { - return mStream.length(); + return stream.length(); } catch (IOException ignore) { } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java index 1c2119df..b4feb8bf 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStream.java @@ -13,35 +13,39 @@ import java.io.IOException; * @version $Id: ByteArrayImageInputStream.java,v 1.0 May 15, 2008 2:12:12 PM haraldk Exp$ */ public final class ByteArrayImageInputStream extends ImageInputStreamImpl { - private final byte[] mData; + private final byte[] data; public ByteArrayImageInputStream(final byte[] pData) { Validate.notNull(pData, "data"); - mData = pData; + data = pData; } public int read() throws IOException { - if (streamPos >= mData.length) { + if (streamPos >= data.length) { return -1; } + bitOffset = 0; - return mData[((int) streamPos++)] & 0xff; + + return data[((int) streamPos++)] & 0xff; } public int read(byte[] pBuffer, int pOffset, int pLength) throws IOException { - if (streamPos >= mData.length) { + if (streamPos >= data.length) { return -1; } - int length = (int) Math.min(mData.length - streamPos, pLength); + + int length = (int) Math.min(data.length - streamPos, pLength); bitOffset = 0; - System.arraycopy(mData, (int) streamPos, pBuffer, pOffset, length); + System.arraycopy(data, (int) streamPos, pBuffer, pOffset, length); streamPos += length; + return length; } @Override public long length() { - return mData.length; + return data.length; } @Override diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java index 2c4ce03c..46600aa3 100644 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java @@ -16,9 +16,9 @@ import java.io.IOException; public final class SubImageInputStream extends ImageInputStreamImpl { // NOTE: This class is based on com.sun.imageio.plugins.common.SubImageInputStream, but fixes some of its bugs. - private final ImageInputStream mStream; - private final long mStartPos; - private final long mLength; + private final ImageInputStream stream; + private final long startPos; + private final long length; /** * Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream. @@ -32,21 +32,19 @@ public final class SubImageInputStream extends ImageInputStreamImpl { */ public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException { Validate.notNull(pStream, "stream"); - if (pLength < 0) { - throw new IllegalArgumentException("length < 0"); - } + Validate.isTrue(pLength >= 0, pLength, "length < 0: %d"); - mStream = pStream; - mStartPos = pStream.getStreamPosition(); - mLength = pLength; + stream = pStream; + startPos = pStream.getStreamPosition(); + length = pLength; } public int read() throws IOException { - if (streamPos >= mLength) { // Local EOF + if (streamPos >= length) { // Local EOF return -1; } else { - int read = mStream.read(); + int read = stream.read(); if (read >= 0) { streamPos++; @@ -57,13 +55,13 @@ public final class SubImageInputStream extends ImageInputStreamImpl { } public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (streamPos >= mLength) { // Local EOF + if (streamPos >= length) { // Local EOF return -1; } // Safe cast, as pLength can never cause int overflow - int length = (int) Math.min(pLength, mLength - streamPos); - int count = mStream.read(pBytes, pOffset, length); + int length = (int) Math.min(pLength, this.length - streamPos); + int count = stream.read(pBytes, pOffset, length); if (count >= 0) { streamPos += count; @@ -75,8 +73,8 @@ public final class SubImageInputStream extends ImageInputStreamImpl { @Override public long length() { try { - long length = mStream.length(); - return length < 0 ? -1 : Math.min(length - mStartPos, mLength); + long length = stream.length(); + return length < 0 ? -1 : Math.min(length - startPos, this.length); } catch (IOException ignore) { } @@ -90,7 +88,7 @@ public final class SubImageInputStream extends ImageInputStreamImpl { throw new IndexOutOfBoundsException("pos < flushedPosition"); } - mStream.seek(mStartPos + pPosition); + stream.seek(startPos + pPosition); streamPos = pPosition; } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java index 1941efff..be74f179 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOInputStreamAdapter.java @@ -43,10 +43,10 @@ import java.io.InputStream; * @version $Id: IIOInputStreamAdapter.java,v 1.0 Sep 26, 2007 11:35:59 AM haraldk Exp$ */ class IIOInputStreamAdapter extends InputStream { - private ImageInputStream mInput; - private final boolean mHasLength; - private long mLeft; - private long mMarkPosition; + private ImageInputStream input; + private final boolean hasLength; + private long left; + private long markPosition; // TODO: Enforce stream boundaries! // TODO: Stream start position.... @@ -82,9 +82,9 @@ class IIOInputStreamAdapter extends InputStream { throw new IllegalArgumentException("length < 0"); } - mInput = pInput; - mHasLength = pHasLength; - mLeft = pLength; + input = pInput; + hasLength = pHasLength; + left = pLength; } @@ -93,17 +93,17 @@ class IIOInputStreamAdapter extends InputStream { * This implementation does not close the underlying stream. */ public void close() throws IOException { - if (mHasLength) { - mInput.seek(mInput.getStreamPosition() + mLeft); + if (hasLength) { + input.seek(input.getStreamPosition() + left); } - mLeft = 0; - mInput = null; + left = 0; + input = null; } public int available() throws IOException { - if (mHasLength) { - return mLeft > 0 ? (int) Math.min(Integer.MAX_VALUE, mLeft) : 0; + if (hasLength) { + return left > 0 ? (int) Math.min(Integer.MAX_VALUE, left) : 0; } return 0; // We don't really know, so we say 0 to be safe. } @@ -115,7 +115,7 @@ class IIOInputStreamAdapter extends InputStream { public void mark(int pReadLimit) { try { - mMarkPosition = mInput.getStreamPosition(); + markPosition = input.getStreamPosition(); } catch (IOException e) { // Let's hope this never happens, because it's not possible to reset then... @@ -124,17 +124,17 @@ class IIOInputStreamAdapter extends InputStream { } public void reset() throws IOException { - long diff = mInput.getStreamPosition() - mMarkPosition; - mInput.seek(mMarkPosition); - mLeft += diff; + long diff = input.getStreamPosition() - markPosition; + input.seek(markPosition); + left += diff; } public int read() throws IOException { - if (mHasLength && mLeft-- <= 0) { - mLeft = 0; + if (hasLength && left-- <= 0) { + left = 0; return -1; } - return mInput.read(); + return input.read(); } public final int read(byte[] pBytes) throws IOException { @@ -142,13 +142,13 @@ class IIOInputStreamAdapter extends InputStream { } public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - if (mHasLength && mLeft <= 0) { + if (hasLength && left <= 0) { return -1; } - int read = mInput.read(pBytes, pOffset, (int) findMaxLen(pLength)); - if (mHasLength) { - mLeft = read < 0 ? 0 : mLeft - read; + int read = input.read(pBytes, pOffset, (int) findMaxLen(pLength)); + if (hasLength) { + left = read < 0 ? 0 : left - read; } return read; } @@ -161,8 +161,8 @@ class IIOInputStreamAdapter extends InputStream { * @return the maximum number of bytes to read */ private long findMaxLen(long pLength) { - if (mHasLength && mLeft < pLength) { - return Math.max(mLeft, 0); + if (hasLength && left < pLength) { + return Math.max(left, 0); } else { return Math.max(pLength, 0); @@ -170,8 +170,8 @@ class IIOInputStreamAdapter extends InputStream { } public long skip(long pLength) throws IOException { - long skipped = mInput.skipBytes(findMaxLen(pLength)); // Skips 0 or more, never -1 - mLeft -= skipped; + long skipped = input.skipBytes(findMaxLen(pLength)); // Skips 0 or more, never -1 + left -= skipped; return skipped; } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java index 9927b983..38a3031f 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/IIOOutputStreamAdapter.java @@ -40,34 +40,34 @@ import java.io.OutputStream; * @version $Id: IIOOutputStreamAdapter.java,v 1.0 Sep 26, 2007 11:50:38 AM haraldk Exp$ */ class IIOOutputStreamAdapter extends OutputStream { - private ImageOutputStream mOutput; + private ImageOutputStream output; public IIOOutputStreamAdapter(final ImageOutputStream pOutput) { - mOutput = pOutput; + output = pOutput; } @Override public void write(final byte[] pBytes) throws IOException { - mOutput.write(pBytes); + output.write(pBytes); } @Override public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { - mOutput.write(pBytes, pOffset, pLength); + output.write(pBytes, pOffset, pLength); } @Override public void write(final int pByte) throws IOException { - mOutput.write(pByte); + output.write(pByte); } @Override public void flush() throws IOException { - mOutput.flush(); + output.flush(); } @Override public void close() throws IOException { - mOutput = null; + output = null; } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java index bdad8e28..e94b61b0 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/ReaderFileSuffixFilter.java @@ -47,15 +47,15 @@ import java.util.Map; * @version $Id: ReaderFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$ */ public final class ReaderFileSuffixFilter extends FileFilter implements java.io.FileFilter { - private final String mDescription; - private final Map mKnownSuffixes = new HashMap(32); + private final String description; + private final Map knownSuffixes = new HashMap(32); public ReaderFileSuffixFilter() { this("Images (all supported input formats)"); } public ReaderFileSuffixFilter(String pDescription) { - mDescription = pDescription; + description = pDescription; } public boolean accept(File pFile) { @@ -71,19 +71,20 @@ public final class ReaderFileSuffixFilter extends FileFilter implements java.io. } private boolean hasReaderForSuffix(String pSuffix) { - if (mKnownSuffixes.get(pSuffix) == Boolean.TRUE) { + if (knownSuffixes.get(pSuffix) == Boolean.TRUE) { return true; } try { // Cahce lookup Iterator iterator = ImageIO.getImageReadersBySuffix(pSuffix); + if (iterator.hasNext()) { - mKnownSuffixes.put(pSuffix, Boolean.TRUE); + knownSuffixes.put(pSuffix, Boolean.TRUE); return true; } else { - mKnownSuffixes.put(pSuffix, Boolean.FALSE); + knownSuffixes.put(pSuffix, Boolean.FALSE); return false; } } @@ -93,6 +94,6 @@ public final class ReaderFileSuffixFilter extends FileFilter implements java.io. } public String getDescription() { - return mDescription; + return description; } } diff --git a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java index 935faebd..20a43ae4 100755 --- a/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java +++ b/imageio/imageio-core/src/main/java/com/twelvemonkeys/imageio/util/WriterFileSuffixFilter.java @@ -47,15 +47,15 @@ import java.util.Map; * @version $Id: WriterFileSuffixFilter.java,v 1.0 11.okt.2006 20:05:36 haku Exp$ */ public final class WriterFileSuffixFilter extends FileFilter implements java.io.FileFilter { - private final String mDescription; - private MapmKnownSuffixes = new HashMap(32); + private final String description; + private Map knownSuffixes = new HashMap(32); public WriterFileSuffixFilter() { this("Images (all supported output formats)"); } public WriterFileSuffixFilter(String pDescription) { - mDescription = pDescription; + description = pDescription; } public boolean accept(File pFile) { @@ -66,24 +66,25 @@ public final class WriterFileSuffixFilter extends FileFilter implements java.io. // Test if we have an ImageWriter for this suffix String suffix = FileUtil.getExtension(pFile); - return !StringUtil.isEmpty(suffix) && hasWriterForSuffix(suffix); + return !StringUtil.isEmpty(suffix) && hasWriterForSuffix(suffix); } private boolean hasWriterForSuffix(String pSuffix) { - if (mKnownSuffixes.get(pSuffix) == Boolean.TRUE) { + if (knownSuffixes.get(pSuffix) == Boolean.TRUE) { return true; } try { // Cahce lookup Iterator iterator = ImageIO.getImageWritersBySuffix(pSuffix); + if (iterator.hasNext()) { - mKnownSuffixes.put(pSuffix, Boolean.TRUE); + knownSuffixes.put(pSuffix, Boolean.TRUE); return true; } else { - mKnownSuffixes.put(pSuffix, Boolean.FALSE); + knownSuffixes.put(pSuffix, Boolean.FALSE); return false; } } @@ -93,6 +94,6 @@ public final class WriterFileSuffixFilter extends FileFilter implements java.io. } public String getDescription() { - return mDescription; + return description; } } diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTestCase.java index 8c04d347..90e4b12b 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStreamTestCase.java @@ -18,7 +18,7 @@ import java.util.Random; * @version $Id: BufferedImageInputStreamTestCase.java,v 1.0 Jun 30, 2008 3:07:42 PM haraldk Exp$ */ public class BufferedImageInputStreamTestCase extends TestCase { - protected final Random mRandom = new Random(); + protected final Random random = new Random(); public void testCreate() throws IOException { new BufferedImageInputStream(new ByteArrayImageInputStream(new byte[0])); @@ -58,7 +58,7 @@ public class BufferedImageInputStreamTestCase extends TestCase { // Fill bytes byte[] bytes = new byte[size * 2]; - mRandom.nextBytes(bytes); + random.nextBytes(bytes); // Create wrapper stream BufferedImageInputStream stream = new BufferedImageInputStream(new ByteArrayImageInputStream(bytes)); @@ -79,7 +79,7 @@ public class BufferedImageInputStreamTestCase extends TestCase { public void testBufferPositionCorrect() throws IOException { // Fill bytes byte[] bytes = new byte[1024]; - mRandom.nextBytes(bytes); + random.nextBytes(bytes); ByteArrayImageInputStream input = new ByteArrayImageInputStream(bytes); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTestCase.java index 372fe6ed..2405ede3 100755 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/ByteArrayImageInputStreamTestCase.java @@ -1,11 +1,12 @@ package com.twelvemonkeys.imageio.stream; -import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTestCase.rangeEquals; import junit.framework.TestCase; import java.io.IOException; import java.util.Random; +import static com.twelvemonkeys.imageio.stream.BufferedImageInputStreamTestCase.rangeEquals; + /** * ByteArrayImageInputStreamTestCase * @@ -14,7 +15,7 @@ import java.util.Random; * @version $Id: ByteArrayImageInputStreamTestCase.java,v 1.0 Apr 21, 2009 10:58:48 AM haraldk Exp$ */ public class ByteArrayImageInputStreamTestCase extends TestCase { - protected final Random mRandom = new Random(); + protected final Random random = new Random(); public void testCreate() { ByteArrayImageInputStream stream = new ByteArrayImageInputStream(new byte[0]); @@ -36,7 +37,7 @@ public class ByteArrayImageInputStreamTestCase extends TestCase { public void testRead() throws IOException { byte[] data = new byte[1024 * 1024]; - mRandom.nextBytes(data); + random.nextBytes(data); ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data); @@ -49,7 +50,7 @@ public class ByteArrayImageInputStreamTestCase extends TestCase { public void testReadArray() throws IOException { byte[] data = new byte[1024 * 1024]; - mRandom.nextBytes(data); + random.nextBytes(data); ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data); @@ -65,7 +66,7 @@ public class ByteArrayImageInputStreamTestCase extends TestCase { public void testReadSkip() throws IOException { byte[] data = new byte[1024 * 14]; - mRandom.nextBytes(data); + random.nextBytes(data); ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data); @@ -82,7 +83,7 @@ public class ByteArrayImageInputStreamTestCase extends TestCase { public void testReadSeek() throws IOException { byte[] data = new byte[1024 * 18]; - mRandom.nextBytes(data); + random.nextBytes(data); ByteArrayImageInputStream stream = new ByteArrayImageInputStream(data); diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java index 665582d8..674afa18 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java @@ -19,12 +19,12 @@ import java.util.Random; */ public class SubImageInputStreamTestCase extends TestCase { // TODO: Extract super test case for all stream tests - private final Random mRandom = new Random(837468l); + private final Random random = new Random(837468l); private ImageInputStream createStream(final int pSize) { byte[] bytes = new byte[pSize]; - mRandom.nextBytes(bytes); + random.nextBytes(bytes); return new MemoryCacheImageInputStream(new ByteArrayInputStream(bytes)) { @Override diff --git a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java index 601ff974..bbf7bcf3 100644 --- a/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java +++ b/imageio/imageio-core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java @@ -116,6 +116,10 @@ public abstract class ImageReaderAbstractTestCase extends protected abstract List getMIMETypes(); + protected boolean allowsNullRawImageType() { + return false; + } + protected void assertProviderInstalledForName(final String pFormat, final Class pReaderClass) { assertProviderInstalled0(pFormat.toUpperCase(), pReaderClass, ImageIO.getImageReadersByFormatName(pFormat.toUpperCase())); assertProviderInstalled0(pFormat.toLowerCase(), pReaderClass, ImageIO.getImageReadersByFormatName(pFormat.toLowerCase())); @@ -1111,30 +1115,34 @@ public abstract class ImageReaderAbstractTestCase extends public void testGetTypeSpecifiers() throws IOException { final ImageReader reader = createReader(); - TestData data = getTestData().get(0); - reader.setInput(data.getInputStream()); + for (TestData data : getTestData()) { + reader.setInput(data.getInputStream()); - ImageTypeSpecifier rawType = reader.getRawImageType(0); - assertNotNull(rawType); - - Iterator types = reader.getImageTypes(0); - - assertNotNull(types); - assertTrue(types.hasNext()); - - // TODO: This might fail even though the specifiers are obviously equal, if the - // color spaces they use are not the SAME instance, as ColorSpace uses identity equals - // and Interleaved ImageTypeSpecifiers are only equal if color spaces are equal... - boolean rawFound = false; - while (types.hasNext()) { - ImageTypeSpecifier type = types.next(); - if (type.equals(rawType)) { - rawFound = true; - break; + ImageTypeSpecifier rawType = reader.getRawImageType(0); + if (rawType == null && allowsNullRawImageType()) { + continue; } - } + assertNotNull(rawType); - assertTrue("ImageTypeSepcifier from getRawImageType should be in the iterator from getImageTypes", rawFound); + Iterator types = reader.getImageTypes(0); + + assertNotNull(types); + assertTrue(types.hasNext()); + + // TODO: This might fail even though the specifiers are obviously equal, if the + // color spaces they use are not the SAME instance, as ColorSpace uses identity equals + // and Interleaved ImageTypeSpecifiers are only equal if color spaces are equal... + boolean rawFound = false; + while (types.hasNext()) { + ImageTypeSpecifier type = types.next(); + if (type.equals(rawType)) { + rawFound = true; + break; + } + } + + assertTrue("ImageTypeSepcifier from getRawImageType should be in the iterator from getImageTypes", rawFound); + } } public void testSetDestination() throws IOException { @@ -1164,12 +1172,18 @@ public abstract class ImageReaderAbstractTestCase extends ImageReadParam param = reader.getDefaultReadParam(); ImageTypeSpecifier type = reader.getRawImageType(0); - BufferedImage destination = type.createBufferedImage(reader.getWidth(0), reader.getHeight(0)); - param.setDestination(destination); - BufferedImage result = reader.read(0, param); + if (type != null) { + BufferedImage destination = type.createBufferedImage(reader.getWidth(0), reader.getHeight(0)); + param.setDestination(destination); - assertSame(destination, result); + BufferedImage result = reader.read(0, param); + + assertSame(destination, result); + } + else { + System.err.println("WARNING: Test skipped due to reader.getRawImageType(0) returning null"); + } } public void testSetDestinationIllegal() throws IOException { @@ -1258,7 +1272,7 @@ public abstract class ImageReaderAbstractTestCase extends boolean removed = illegalTypes.remove(valid); // TODO: 4BYTE_ABGR (6) and 4BYTE_ABGR_PRE (7) is essentially the same type... - // !#$#§%$! ImageTypeSpecifier.equals is not well-defined + // !#$#�%$! ImageTypeSpecifier.equals is not well-defined if (!removed) { for (Iterator iterator = illegalTypes.iterator(); iterator.hasNext();) { ImageTypeSpecifier illegalType = iterator.next(); diff --git a/imageio/imageio-core/todo.txt b/imageio/imageio-core/todo.txt index c2644570..4e48a7fa 100755 --- a/imageio/imageio-core/todo.txt +++ b/imageio/imageio-core/todo.txt @@ -1,7 +1,7 @@ - Rename to imageio-common? - Separate modules for more for more plugins - - The BMP reader spports some special formats not supported by Sun reader - - PNM package is pretty complete, but useless, as it's provided by Sun? Licencse? + - The BMP reader supports some special formats not supported by Sun reader + - PNM package is pretty complete, but useless, as it's provided by Sun? License? - WBMP? - XBM? DONE: diff --git a/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/BitmapIndexed.java b/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/BitmapIndexed.java index bd73dd75..2b0e88c5 100755 --- a/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/BitmapIndexed.java +++ b/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/BitmapIndexed.java @@ -78,7 +78,7 @@ class BitmapIndexed extends BitmapDescriptor { WritableRaster raster = image.getRaster(); - // Make pixels transparant according to mask + // Make pixels transparent according to mask final int trans = icm.getTransparentPixel(); for (int y = 0; y < getHeight(); y++) { for (int x = 0; x < getWidth(); x++) { diff --git a/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java b/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java index 8bf47ebd..bd62a431 100644 --- a/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java +++ b/imageio/imageio-ico/src/main/java/com/twelvemonkeys/imageio/plugins/ico/ICOImageReader.java @@ -132,8 +132,8 @@ public class ICOImageReader extends ImageReaderBase { case 8: // TODO: This is slightly QnD... int offset = entry.getOffset() + header.getSize(); - if (offset != mImageInput.getStreamPosition()) { - mImageInput.seek(offset); + if (offset != imageInput.getStreamPosition()) { + imageInput.seek(offset); } BitmapIndexed indexed = new BitmapIndexed(entry, header); readColorMap(indexed); @@ -220,21 +220,17 @@ public class ICOImageReader extends ImageReaderBase { return destination; } - private boolean hasExplicitDestination(final ImageReadParam pParam) { - return (pParam != null && (pParam.getDestination() != null || pParam.getDestinationType() != null || pParam.getDestinationOffset() != null)); - } - private boolean isPNG(final DirectoryEntry pEntry) throws IOException { long magic; - mImageInput.seek(pEntry.getOffset()); - mImageInput.setByteOrder(ByteOrder.BIG_ENDIAN); + imageInput.seek(pEntry.getOffset()); + imageInput.setByteOrder(ByteOrder.BIG_ENDIAN); try { - magic = mImageInput.readLong(); + magic = imageInput.readLong(); } finally { - mImageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); } return magic == DIB.PNG_MAGIC; @@ -252,8 +248,8 @@ public class ICOImageReader extends ImageReaderBase { private ImageReader initPNGReader(final DirectoryEntry pEntry) throws IOException { ImageReader pngReader = getPNGReader(); - mImageInput.seek(pEntry.getOffset()); - InputStream inputStream = IIOUtil.createStreamAdapter(mImageInput, pEntry.getSize()); + imageInput.seek(pEntry.getOffset()); + InputStream inputStream = IIOUtil.createStreamAdapter(imageInput, pEntry.getSize()); ImageInputStream stream = ImageIO.createImageInputStream(inputStream); // NOTE: Will throw IOException on later reads if input is not PNG @@ -283,8 +279,8 @@ public class ICOImageReader extends ImageReaderBase { private DIBHeader getHeader(final DirectoryEntry pEntry) throws IOException { if (!mHeaders.containsKey(pEntry)) { - mImageInput.seek(pEntry.getOffset()); - DIBHeader header = DIBHeader.read(mImageInput); + imageInput.seek(pEntry.getOffset()); + DIBHeader header = DIBHeader.read(imageInput); mHeaders.put(pEntry, header); } @@ -299,8 +295,8 @@ public class ICOImageReader extends ImageReaderBase { DIBHeader header = getHeader(pEntry); int offset = pEntry.getOffset() + header.getSize(); - if (offset != mImageInput.getStreamPosition()) { - mImageInput.seek(offset); + if (offset != imageInput.getStreamPosition()) { + imageInput.seek(offset); } // TODO: Support this, it's already in the BMP reader, spec allows RLE4 and RLE8 @@ -368,7 +364,7 @@ public class ICOImageReader extends ImageReaderBase { for (int i = 0; i < colorCount; i++) { // aRGB (a is "Reserved") - pBitmap.mColors[i] = (mImageInput.readInt() & 0xffffff) | 0xff000000; + pBitmap.mColors[i] = (imageInput.readInt() & 0xffffff) | 0xff000000; } } @@ -377,7 +373,7 @@ public class ICOImageReader extends ImageReaderBase { byte[] row = new byte[width]; for (int y = 0; y < pBitmap.getHeight(); y++) { - mImageInput.readFully(row, 0, width); + imageInput.readFully(row, 0, width); int rowPos = 0; int xOrVal = 0x80; int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth(); @@ -411,7 +407,7 @@ public class ICOImageReader extends ImageReaderBase { byte[] row = new byte[width]; for (int y = 0; y < pBitmap.getHeight(); y++) { - mImageInput.readFully(row, 0, width); + imageInput.readFully(row, 0, width); int rowPos = 0; boolean high4 = true; int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth(); @@ -446,7 +442,7 @@ public class ICOImageReader extends ImageReaderBase { byte[] row = new byte[width]; for (int y = 0; y < pBitmap.getHeight(); y++) { - mImageInput.readFully(row, 0, width); + imageInput.readFully(row, 0, width); int rowPos = 0; int pos = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth(); @@ -488,12 +484,12 @@ public class ICOImageReader extends ImageReaderBase { for (int y = 0; y < pBitmap.getHeight(); y++) { int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth(); - mImageInput.readFully(pixels, offset, pBitmap.getWidth()); + imageInput.readFully(pixels, offset, pBitmap.getWidth()); // Skip to 32 bit boundary if (pBitmap.getWidth() % 2 != 0) { - mImageInput.readShort(); + imageInput.readShort(); } if (abortRequested()) { @@ -524,7 +520,7 @@ public class ICOImageReader extends ImageReaderBase { for (int y = 0; y < pBitmap.getHeight(); y++) { int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth(); - mImageInput.readFully(pixels, offset, pBitmap.getWidth() * 3); + imageInput.readFully(pixels, offset, pBitmap.getWidth() * 3); // TODO: Possibly read padding byte here! @@ -550,7 +546,7 @@ public class ICOImageReader extends ImageReaderBase { for (int y = 0; y < pBitmap.getHeight(); y++) { int offset = (pBitmap.getHeight() - y - 1) * pBitmap.getWidth(); - mImageInput.readFully(pixels, offset, pBitmap.getWidth()); + imageInput.readFully(pixels, offset, pBitmap.getWidth()); if (abortRequested()) { processReadAborted(); @@ -571,17 +567,17 @@ public class ICOImageReader extends ImageReaderBase { } private void readFileHeader() throws IOException { - mImageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); // Read file header - mImageInput.readUnsignedShort(); // Reserved + imageInput.readUnsignedShort(); // Reserved // Should be same as type as the provider - int type = mImageInput.readUnsignedShort(); - int imageCount = mImageInput.readUnsignedShort(); + int type = imageInput.readUnsignedShort(); + int imageCount = imageInput.readUnsignedShort(); // Read directory - mDirectory = Directory.read(type, imageCount, mImageInput); + mDirectory = Directory.read(type, imageCount, imageInput); } final DirectoryEntry getEntry(final int pImageIndex) throws IOException { 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 dd06892f..7a65350f 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 @@ -135,13 +135,13 @@ public class IFFImageReader extends ImageReaderBase { } private void readMeta() throws IOException { - if (mImageInput.readInt() != IFF.CHUNK_FORM) { + if (imageInput.readInt() != IFF.CHUNK_FORM) { throw new IIOException("Unknown file format for IFFImageReader"); } - int remaining = mImageInput.readInt() - 4; // We'll read 4 more in a sec + int remaining = imageInput.readInt() - 4; // We'll read 4 more in a sec - mFormType = mImageInput.readInt(); + mFormType = imageInput.readInt(); if (mFormType != IFF.TYPE_ILBM && mFormType != IFF.TYPE_PBM) { throw new IIOException("Only IFF (FORM) type ILBM and PBM supported: " + IFFUtil.toChunkStr(mFormType)); } @@ -152,8 +152,8 @@ public class IFFImageReader extends ImageReaderBase { mViewPort = null; while (remaining > 0) { - int chunkId = mImageInput.readInt(); - int length = mImageInput.readInt(); + int chunkId = imageInput.readInt(); + int length = imageInput.readInt(); remaining -= 8; remaining -= length % 2 == 0 ? length : length + 1; @@ -168,7 +168,7 @@ public class IFFImageReader extends ImageReaderBase { } mHeader = new BMHDChunk(length); - mHeader.readChunk(mImageInput); + mHeader.readChunk(imageInput); //System.out.println(mHeader); break; @@ -177,7 +177,7 @@ public class IFFImageReader extends ImageReaderBase { throw new IIOException("Multiple CMAP chunks not allowed"); } mColorMap = new CMAPChunk(length, mHeader, mViewPort); - mColorMap.readChunk(mImageInput); + mColorMap.readChunk(imageInput); //System.out.println(mColorMap); break; @@ -186,7 +186,7 @@ public class IFFImageReader extends ImageReaderBase { throw new IIOException("Multiple GRAB chunks not allowed"); } mGrab = new GRABChunk(length); - mGrab.readChunk(mImageInput); + mGrab.readChunk(imageInput); //System.out.println(mGrab); break; @@ -195,7 +195,7 @@ public class IFFImageReader extends ImageReaderBase { throw new IIOException("Multiple CAMG chunks not allowed"); } mViewPort = new CAMGChunk(length); - mViewPort.readChunk(mImageInput); + mViewPort.readChunk(imageInput); //System.out.println(mViewPort); break; @@ -205,7 +205,7 @@ public class IFFImageReader extends ImageReaderBase { } mBody = new BODYChunk(length); - mBodyStart = mImageInput.getStreamPosition(); + mBodyStart = imageInput.getStreamPosition(); // NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method @@ -215,7 +215,7 @@ public class IFFImageReader extends ImageReaderBase { // TODO: We probably want to store anno chunks as Metadata // ANNO, DEST, SPRT and more IFFChunk generic = new GenericChunk(chunkId, length); - generic.readChunk(mImageInput); + generic.readChunk(imageInput); //System.out.println(generic); break; @@ -323,16 +323,16 @@ public class IFFImageReader extends ImageReaderBase { } private void readBody(final ImageReadParam pParam) throws IOException { - mImageInput.seek(mBodyStart); + imageInput.seek(mBodyStart); mByteRunStream = null; // NOTE: mColorMap may be null for 8 bit (gray), 24 bit or 32 bit only if (mColorMap != null) { IndexColorModel cm = mColorMap.getIndexColorModel(); - readIndexed(pParam, mImageInput, cm); + readIndexed(pParam, imageInput, cm); } else { - readTrueColor(pParam, mImageInput); + readTrueColor(pParam, imageInput); } } diff --git a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java index 62e9bb08..e1a3d845 100755 --- a/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java +++ b/imageio/imageio-iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java @@ -99,22 +99,22 @@ public class IFFImageWriter extends ImageWriterBase { } private void writeBody(ByteArrayOutputStream pImageData) throws IOException { - mImageOutput.writeInt(IFF.CHUNK_BODY); - mImageOutput.writeInt(pImageData.size()); + imageOutput.writeInt(IFF.CHUNK_BODY); + imageOutput.writeInt(pImageData.size()); // NOTE: This is much faster than mOutput.write(pImageData.toByteArray()) // as the data array is not duplicated - pImageData.writeTo(IIOUtil.createStreamAdapter(mImageOutput)); + pImageData.writeTo(IIOUtil.createStreamAdapter(imageOutput)); if (pImageData.size() % 2 == 0) { - mImageOutput.writeByte(0); // PAD + imageOutput.writeByte(0); // PAD } // NOTE: Most progress is done in packImageData, however, as we need to // buffer, to write correct size, we defer the last 10 percent until now. processImageProgress(100f); - mImageOutput.flush(); + imageOutput.flush(); } private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException { @@ -213,16 +213,16 @@ public class IFFImageWriter extends ImageWriterBase { size += 8 + cmap.mChunkLength; } - mImageOutput.writeInt(IFF.CHUNK_FORM); - mImageOutput.writeInt(size); + imageOutput.writeInt(IFF.CHUNK_FORM); + imageOutput.writeInt(size); - mImageOutput.writeInt(IFF.TYPE_ILBM); + imageOutput.writeInt(IFF.TYPE_ILBM); - anno.writeChunk(mImageOutput); - header.writeChunk(mImageOutput); + anno.writeChunk(imageOutput); + header.writeChunk(imageOutput); if (cmap != null) { //System.out.println("CMAP written"); - cmap.writeChunk(mImageOutput); + cmap.writeChunk(imageOutput); } } diff --git a/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickReader.java b/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickReader.java index 20c96a58..7d51b6c1 100755 --- a/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickReader.java +++ b/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickReader.java @@ -286,11 +286,11 @@ abstract class JMagickReader extends ImageReaderBase { // TODO: If ImageInputStream is already file-backed, maybe we can peek into that file? // At the moment, the cache/file is not accessible, but we could create our own // FileImageInputStream provider that gives us this access. - if (!mUseTempFile && mImageInput.length() >= 0 && mImageInput.length() <= Integer.MAX_VALUE) { + if (!mUseTempFile && imageInput.length() >= 0 && imageInput.length() <= Integer.MAX_VALUE) { // This works for most file formats, as long as ImageMagick // uses the file magic to decide file format - byte[] bytes = new byte[(int) mImageInput.length()]; - mImageInput.readFully(bytes); + byte[] bytes = new byte[(int) imageInput.length()]; + imageInput.readFully(bytes); // Unfortunately, this is a waste of space & time... ImageInfo info = new ImageInfo(); @@ -309,7 +309,7 @@ abstract class JMagickReader extends ImageReaderBase { byte[] buffer = new byte[FileUtil.BUF_SIZE]; int count; - while ((count = mImageInput.read(buffer)) != -1) { + while ((count = imageInput.read(buffer)) != -1) { out.write(buffer, 0, count); } diff --git a/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickWriter.java b/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickWriter.java index 8106bbee..9a7dfc4b 100755 --- a/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickWriter.java +++ b/imageio/imageio-jmagick/src/main/java/com/twelvemonkeys/imageio/plugins/jmagick/JMagickWriter.java @@ -113,8 +113,8 @@ abstract class JMagickWriter extends ImageWriterBase { processImageProgress(67); // Write blob to output - mImageOutput.write(bytes); - mImageOutput.flush(); + imageOutput.write(bytes); + imageOutput.flush(); processImageProgress(100); } catch (MagickException e) { 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 1b24e819..69ec6f9b 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 @@ -506,12 +506,12 @@ public class JPEGImageReader extends ImageReaderBase { } private void readSegments() throws IOException { - long pos = mImageInput.getStreamPosition(); + long pos = imageInput.getStreamPosition(); try { - mImageInput.seek(0); // TODO: Seek to wanted image + imageInput.seek(0); // TODO: Seek to wanted image - segments = JPEGSegmentUtil.readSegments(mImageInput, SEGMENT_IDENTIFIERS); + segments = JPEGSegmentUtil.readSegments(imageInput, SEGMENT_IDENTIFIERS); } catch (IOException ignore) { } @@ -519,7 +519,7 @@ public class JPEGImageReader extends ImageReaderBase { foo.printStackTrace(); } finally { - mImageInput.seek(pos); + imageInput.seek(pos); } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java index 75b8b456..cbd850f6 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java @@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata; import com.twelvemonkeys.lang.Validate; import java.lang.reflect.Array; +import java.util.Arrays; /** * AbstractEntry @@ -69,6 +70,45 @@ public abstract class AbstractEntry implements Entry { } public String getValueAsString() { + if (valueCount() > 1) { + if (valueCount() < 16) { + Class type = mValue.getClass().getComponentType(); + + if (type.isPrimitive()) { + if (type.equals(boolean.class)) { + return Arrays.toString((boolean[]) mValue); + } + else if (type.equals(byte.class)) { + return Arrays.toString((byte[]) mValue); + } + else if (type.equals(char.class)) { + return new String((char[]) mValue); + } + else if (type.equals(double.class)) { + return Arrays.toString((double[]) mValue); + } + else if (type.equals(float.class)) { + return Arrays.toString((float[]) mValue); + } + else if (type.equals(int.class)) { + return Arrays.toString((int[]) mValue); + } + else if (type.equals(long.class)) { + return Arrays.toString((long[]) mValue); + } + else if (type.equals(short.class)) { + return Arrays.toString((short[]) mValue); + } + // Fall through should never happen + } + else { + return Arrays.toString((Object[]) mValue); + } + } + + return String.valueOf(mValue) + " (" + valueCount() + ")"; + } + return String.valueOf(mValue); } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index fef71f37..39c23ba9 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -43,16 +43,35 @@ final class EXIFEntry extends AbstractEntry { EXIFEntry(final int pIdentifier, final Object pValue, final short pType) { super(pIdentifier, pValue); - if (pType < 1 || pType > TIFF.TYPE_NAMES.length) { - throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType)); - } +// if (pType < 1 || pType > TIFF.TYPE_NAMES.length) { +// throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType)); +// } mType = pType; } + public short getType() { + return mType; + } + @Override public String getFieldName() { switch ((Integer) getIdentifier()) { + case TIFF.TAG_EXIF_IFD: + return "EXIF"; + case TIFF.TAG_XMP: + return "XMP"; + case TIFF.TAG_IPTC: + return "IPTC"; + case TIFF.TAG_PHOTOSHOP: + return "Adobe"; + case TIFF.TAG_ICC_PROFILE: + return "ICC Profile"; + + case TIFF.TAG_IMAGE_WIDTH: + return "ImageWidth"; + case TIFF.TAG_IMAGE_HEIGHT: + return "ImageHeight"; case TIFF.TAG_COMPRESSION: return "Compression"; case TIFF.TAG_ORIENTATION: @@ -82,6 +101,7 @@ final class EXIFEntry extends AbstractEntry { return "PixelXDimension"; case EXIF.TAG_PIXEL_Y_DIMENSION: return "PixelYDimension"; + // TODO: More field names } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java index 2118bc57..e4113a11 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java @@ -34,10 +34,15 @@ import com.twelvemonkeys.imageio.metadata.MetadataReader; import com.twelvemonkeys.lang.StringUtil; import javax.imageio.IIOException; +import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; +import java.io.File; import java.io.IOException; import java.nio.ByteOrder; +import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; /** @@ -48,6 +53,7 @@ import java.util.List; * @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$ */ public final class EXIFReader extends MetadataReader { + static final Collection KNOWN_IFDS = Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD); @Override public Directory read(final ImageInputStream pInput) throws IOException { @@ -56,10 +62,15 @@ public final class EXIFReader extends MetadataReader { if (bom[0] == 'I' && bom[1] == 'I') { pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); } - else if (!(bom[0] == 'M' && bom[1] == 'M')) { + else if (bom[0] == 'M' && bom[1] == 'M') { + pInput.setByteOrder(ByteOrder.BIG_ENDIAN); + } + else { throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); } + // TODO: BigTiff uses version 43 instead of TIFF's 42, and header is slightly different, see + // http://www.awaresystems.be/imaging/tiff/bigtiff.html int magic = pInput.readUnsignedShort(); if (magic != TIFF.TIFF_MAGIC) { throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC)); @@ -72,7 +83,6 @@ public final class EXIFReader extends MetadataReader { private EXIFDirectory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException { List entries = new ArrayList(); - pInput.seek(pOffset); int entryCount = pInput.readUnsignedShort(); @@ -82,6 +92,7 @@ public final class EXIFReader extends MetadataReader { long nextOffset = pInput.readUnsignedInt(); + // Read linked IFDs if (nextOffset != 0) { EXIFDirectory next = readDirectory(pInput, nextOffset); @@ -90,50 +101,158 @@ public final class EXIFReader extends MetadataReader { } } + // TODO: Make what sub-IFDs to parse optional? Or leave this to client code? At least skip the non-TIFF data? + readSubdirectories(pInput, entries, + Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD +// , TIFF.TAG_IPTC, TIFF.TAG_XMP +// , TIFF.TAG_ICC_PROFILE +// , TIFF.TAG_PHOTOSHOP +// ,TIFF.TAG_MODI_OLE_PROPERTY_SET + ) + ); + return new EXIFDirectory(entries); } +// private Directory readForeignMetadata(final MetadataReader reader, final byte[] bytes) throws IOException { +// return reader.read(ImageIO.createImageInputStream(new ByteArrayInputStream(bytes))); +// } + + // TODO: Might be better to leave this for client code, as it's tempting go really overboard and support any possible embedded format.. + private void readSubdirectories(ImageInputStream input, List entries, List subIFDs) throws IOException { + if (subIFDs == null || subIFDs.isEmpty()) { + return; + } + + for (int i = 0, entriesSize = entries.size(); i < entriesSize; i++) { + EXIFEntry entry = (EXIFEntry) entries.get(i); + int tagId = (Integer) entry.getIdentifier(); + + if (subIFDs.contains(tagId)) { + try { + Object directory; + + /* + if (tagId == TIFF.TAG_IPTC) { + directory = readForeignMetadata(new IPTCReader(), (byte[]) entry.getValue()); + } + else if (tagId == TIFF.TAG_XMP) { + directory = readForeignMetadata(new XMPReader(), (byte[]) entry.getValue()); + } + else if (tagId == TIFF.TAG_PHOTOSHOP) { + // TODO: This is waaay too fragile.. Might need registry-based meta data parsers? + try { + Class cl = Class.forName("com.twelvemonkeys.imageio.plugins.psd.PSDImageResource"); + Method method = cl.getMethod("read", ImageInputStream.class); + method.setAccessible(true); + directory = method.invoke(null, ImageIO.createImageInputStream(new ByteArrayInputStream((byte[]) entry.getValue()))); + } + catch (Exception ignore) { + continue; + } + } + else if (tagId == TIFF.TAG_ICC_PROFILE) { + directory = ICC_Profile.getInstance((byte[]) entry.getValue()); + } + else if (tagId == TIFF.TAG_MODI_OLE_PROPERTY_SET) { + // TODO: Encapsulate in something more useful? + directory = new CompoundDocument(new ByteArrayInputStream((byte[]) entry.getValue())).getRootEntry(); + } + else*/ if (KNOWN_IFDS.contains(tagId)) { + directory = readDirectory(input, getPointerOffset(entry)); + } + else { + continue; + } + + // Replace the entry with parsed data + entries.set(i, new EXIFEntry(tagId, directory, entry.getType())); + } + catch (IIOException e) { + // TODO: Issue warning without crashing...? + e.printStackTrace(); + } + } + } + } + + private long getPointerOffset(final Entry entry) throws IIOException { + long offset; + Object value = entry.getValue(); + + if (value instanceof Byte) { + offset = ((Byte) value & 0xff); + } + else if (value instanceof Short) { + offset = ((Short) value & 0xffff); + } + else if (value instanceof Integer) { + offset = ((Integer) value & 0xffffffffL); + } + else if (value instanceof Long) { + offset = (Long) value; + } + else { + throw new IIOException(String.format("Unknown pointer type: %s", (value != null ? value.getClass() : null))); + } + + return offset; + } + private EXIFEntry readEntry(final ImageInputStream pInput) throws IOException { + // TODO: BigTiff entries are different int tagId = pInput.readUnsignedShort(); short type = pInput.readShort(); int count = pInput.readInt(); // Number of values - Object value; + if (count < 0) { + throw new IIOException(String.format("Illegal count %d for tag %s type %s @%08x", count, tagId, type, pInput.getStreamPosition())); + } + + Object value; + int valueLength = getValueLength(type, count); + + if (type < 0 || type > 13) { + // Invalid tag, this is just for debugging + System.err.printf("offset: %08x%n", pInput.getStreamPosition() - 8l); + System.err.println("tagId: " + tagId); + System.err.println("type: " + type + " (INVALID)"); + System.err.println("count: " + count); - if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) { - // Parse sub IFDs - long offset = pInput.readUnsignedInt(); pInput.mark(); + pInput.seek(pInput.getStreamPosition() - 8); try { - value = readDirectory(pInput, offset); + byte[] bytes = new byte[8 + Math.max(20, valueLength)]; + pInput.readFully(bytes); + + System.err.print("data: " + HexDump.dump(bytes)); + System.err.println(bytes.length < valueLength ? "..." : ""); } finally { pInput.reset(); } } - else { - int valueLength = getValueLength(type, count); - if (valueLength > 0 && valueLength <= 4) { - value = readValueInLine(pInput, type, count); - pInput.skipBytes(4 - valueLength); - } - else { - long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes - value = readValue(pInput, valueOffset, type, count); - } + // TODO: For BigTiff allow size <= 8 + if (valueLength > 0 && valueLength <= 4) { + value = readValueInLine(pInput, type, count); + pInput.skipBytes(4 - valueLength); + } + else { + long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes + value = readValueAt(pInput, valueOffset, type, count); } return new EXIFEntry(tagId, value, type); } - private Object readValue(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException { + private Object readValueAt(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException { long pos = pInput.getStreamPosition(); try { pInput.seek(pOffset); - return readValueInLine(pInput, pType, pCount); + return readValue(pInput, pType, pCount); } finally { pInput.seek(pos); @@ -141,53 +260,82 @@ public final class EXIFReader extends MetadataReader { } private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { - return readValueDirect(pInput, pType, pCount); + return readValue(pInput, pType, pCount); } - private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { + private static Object readValue(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { + // TODO: Review value "widening" for the unsigned types. Right now it's inconsistent. Should we leave it to client code? + + long pos = pInput.getStreamPosition(); + switch (pType) { - case 2: - // TODO: This might be UTF-8 or ISO-8859-1, even though spec says ASCII + case 2: // ASCII + // TODO: This might be UTF-8 or ISO-8859-x, even though spec says ASCII byte[] ascii = new byte[pCount]; pInput.readFully(ascii); return StringUtil.decode(ascii, 0, ascii.length, "UTF-8"); // UTF-8 is ASCII compatible - case 1: + case 1: // BYTE if (pCount == 1) { return pInput.readUnsignedByte(); } - case 6: + // else fall through + case 6: // SBYTE if (pCount == 1) { return pInput.readByte(); } - case 7: + // else fall through + case 7: // UNDEFINED byte[] bytes = new byte[pCount]; pInput.readFully(bytes); + + // NOTE: We don't change (unsigned) BYTE array wider Java type, as most often BYTE array means + // binary data and we want to keep that as a byte array for clients to parse futher + return bytes; - case 3: + case 3: // SHORT if (pCount == 1) { return pInput.readUnsignedShort(); } - case 8: + case 8: // SSHORT if (pCount == 1) { return pInput.readShort(); } short[] shorts = new short[pCount]; pInput.readFully(shorts, 0, shorts.length); + + if (pType == 3) { + int[] ints = new int[pCount]; + for (int i = 0; i < pCount; i++) { + ints[i] = shorts[i] & 0xffff; + } + return ints; + } + return shorts; - case 4: + case 13: // IFD + case 4: // LONG if (pCount == 1) { return pInput.readUnsignedInt(); } - case 9: + case 9: // SLONG if (pCount == 1) { return pInput.readInt(); } int[] ints = new int[pCount]; pInput.readFully(ints, 0, ints.length); + + if (pType == 4 || pType == 13) { + long[] longs = new long[pCount]; + for (int i = 0; i < pCount; i++) { + longs[i] = ints[i] & 0xffffffffL; + } + return longs; + } + return ints; - case 11: + case 11: // FLOAT if (pCount == 1) { return pInput.readFloat(); } @@ -195,7 +343,7 @@ public final class EXIFReader extends MetadataReader { float[] floats = new float[pCount]; pInput.readFully(floats, 0, floats.length); return floats; - case 12: + case 12: // DOUBLE if (pCount == 1) { return pInput.readDouble(); } @@ -204,7 +352,7 @@ public final class EXIFReader extends MetadataReader { pInput.readFully(doubles, 0, doubles.length); return doubles; - case 5: + case 5: // RATIONAL if (pCount == 1) { return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt()); } @@ -215,7 +363,7 @@ public final class EXIFReader extends MetadataReader { } return rationals; - case 10: + case 10: // SRATIONAL if (pCount == 1) { return new Rational(pInput.readInt(), pInput.readInt()); } @@ -227,8 +375,32 @@ public final class EXIFReader extends MetadataReader { return srationals; + // BigTiff: + case 16: // LONG8 + case 17: // SLONG8 + case 18: // IFD8 + // TODO: Assert BigTiff (version == 43) + + if (pCount == 1) { + long val = pInput.readLong(); + if (pType != 17 && val < 0) { + throw new IIOException(String.format("Value > %s", Long.MAX_VALUE)); + } + return val; + } + + long[] longs = new long[pCount]; + for (int i = 0; i < pCount; i++) { + longs[i] = pInput.readLong(); + } + + return longs; + default: - throw new IIOException(String.format("Unknown EXIF type '%s'", pType)); + // Spec says skip unknown values: + // TODO: Rather just return null, UNKNOWN_TYPE or new Unknown(type, count, offset) for value? + return new Unknown(pType, pCount, pos); +// throw new IIOException(String.format("Unknown EXIF type '%s' at pos %d", pType, pInput.getStreamPosition())); } } @@ -239,4 +411,115 @@ public final class EXIFReader extends MetadataReader { return -1; } + + public static void main(String[] args) throws IOException { + EXIFReader reader = new EXIFReader(); + ImageInputStream stream = ImageIO.createImageInputStream(new File(args[0])); + + long pos = 0; + if (args.length > 1) { + if (args[1].startsWith("0x")) { + pos = Integer.parseInt(args[1].substring(2), 16); + } + else { + pos = Long.parseLong(args[1]); + } + + stream.setByteOrder(pos < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); + + pos = Math.abs(pos); + + stream.seek(pos); + } + + try { + Directory directory; + + if (args.length > 1) { + directory = reader.readDirectory(stream, pos); + } + else { + directory = reader.read(stream); + } + + for (Entry entry : directory) { + System.err.println(entry); + + Object value = entry.getValue(); + if (value instanceof byte[]) { + byte[] bytes = (byte[]) value; + System.err.println(HexDump.dump(bytes, 0, Math.min(bytes.length, 128))); + } + } + } + finally { + stream.close(); + } + } + + ////////////////////// + // TODO: Stream based hex dump util? + public static class HexDump { + private HexDump() {} + + private static final int WIDTH = 32; + + public static String dump(byte[] bytes) { + return dump(bytes, 0, bytes.length); + } + + public static String dump(byte[] bytes, int off, int len) { + StringBuilder builder = new StringBuilder(); + + int i; + for (i = 0; i < len; i++) { + if (i % WIDTH == 0) { + if (i > 0 ) { + builder.append("\n"); + } + builder.append(String.format("%08x: ", i + off)); + } + else if (i > 0 && i % 2 == 0) { + builder.append(" "); + } + + builder.append(String.format("%02x", bytes[i + off])); + + int next = i + 1; + if (next % WIDTH == 0 || next == len) { + int leftOver = (WIDTH - (next % WIDTH)) % WIDTH; + + if (leftOver != 0) { + // Pad: 5 spaces for every 2 bytes... Special care if padding is non-even. + int pad = leftOver / 2; + + if (len % 2 != 0) { + builder.append(" "); + } + + for (int j = 0; j < pad; j++) { + builder.append(" "); + } + } + + builder.append(" "); + builder.append(toAsciiString(bytes, next - (WIDTH - leftOver) + off, next + off)); + } + } + + return builder.toString(); + } + + private static String toAsciiString(final byte[] bytes, final int from, final int to) { + byte[] range = Arrays.copyOfRange(bytes, from, to); + + for (int i = 0; i < range.length; i++) { + if (range[i] < 32 || range[i] > 126) { + range[i] = '.'; // Unreadable char + } + } + + return new String(range, Charset.forName("ascii")); + } + } } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index c7fc6e27..00194d2e 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -47,7 +47,7 @@ public interface TIFF { 5 = RATIONAL Two LONGs: the first represents the numerator of a fraction; the second, the denominator. - TIFF 6.0 and above: + TIFF 6.0 and above: 6 = SBYTE An 8-bit signed (twos-complement) integer. 7 = UNDEFINED An 8-bit byte that may contain anything, depending on the definition of the field. @@ -57,21 +57,39 @@ public interface TIFF { fraction, the second the denominator. 11 = FLOAT Single precision (4-byte) IEEE format. 12 = DOUBLE Double precision (8-byte) IEEE format. + + TODO: Verify IFD type + See http://www.awaresystems.be/imaging/tiff/tifftags/subifds.html + 13 = IFD, same as LONG + + TODO: BigTiff specifies more types + See http://www.awaresystems.be/imaging/tiff/bigtiff.html, http://www.remotesensing.org/libtiff/bigtiffdesign.html + (what about 14-15??) + 16 = TIFF_LONG8, being unsigned 8byte integer + 17 = TIFF_SLONG8, being signed 8byte integer + 18 = TIFF_IFD8, being a new unsigned 8byte IFD offset. + Should probably all map to Java long (and fail if high bit is set for the unsigned types???) */ String[] TYPE_NAMES = { "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", - "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", + "IFD", + null, null, + "LONG8", "SLONG8", "IFD8" }; int[] TYPE_LENGTHS = { 1, 1, 2, 4, 8, - 1, 1, 2, 4, 8, 4, 8, + 4, + -1, -1, + 8, 8, 8 }; - int IFD_EXIF = 0x8769; - int IFD_GPS = 0x8825; - int IFD_INTEROP = 0xA005; + /// EXIF defined TIFF tags + + int TAG_EXIF_IFD = 34665; + int TAG_GPS_IFD = 34853; + int TAG_INTEROP_IFD = 40965; /// A. Tags relating to image data structure: @@ -114,4 +132,22 @@ public interface TIFF { int TAG_SOFTWARE = 305; int TAG_ARTIST = 315; int TAG_COPYRIGHT = 33432; + + int TAG_SUB_IFD = 330; + + int TAG_XMP = 700; + int TAG_IPTC = 33723; + int TAG_PHOTOSHOP = 34377; + int TAG_ICC_PROFILE = 34675; + + // Microsoft Office Document Imaging (MODI) + // http://msdn.microsoft.com/en-us/library/aa167596%28office.11%29.aspx + int TAG_MODI_BLC = 34718; + int TAG_MODI_VECTOR = 34719; + int TAG_MODI_PTC = 34720; + + // http://blogs.msdn.com/b/openspecification/archive/2009/12/08/details-of-three-tiff-tag-extensions-that-microsoft-office-document-imaging-modi-software-may-write-into-the-tiff-files-it-generates.aspx + int TAG_MODI_PLAIN_TEXT = 37679; + int TAG_MODI_OLE_PROPERTY_SET = 37680; + int TAG_MODI_TEXT_POS_INFO = 37681; } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Unknown.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Unknown.java new file mode 100644 index 00000000..dc5fb965 --- /dev/null +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Unknown.java @@ -0,0 +1,45 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +/** + * Unknown + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: Unknown.java,v 1.0 Oct 8, 2010 3:38:45 PM haraldk Exp$ + */ +final class Unknown { + private final short type; + private final int count; + private final long pos; + + public Unknown(final short type, final int count, final long pos) { + this.type = type; + this.count = count; + this.pos = pos; + } + + @Override + public int hashCode() { + return (int) (pos ^ (pos >>> 32)) + count * 37 + type * 97; + } + + @Override + public boolean equals(Object other) { + if (other != null && other.getClass() == getClass()){ + Unknown unknown = (Unknown) other; + return pos == unknown.pos && type == unknown.type && count == unknown.count; + } + + return false; + } + + @Override + public String toString() { + if (count == 1) { + return String.format("Unknown(%d)@%08x", type, pos); + } + else { + return String.format("Unknown(%d)[%d]@%08x", type, count, pos); + } + } +} diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java index 5e674057..9d1ba02f 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/jpeg/JPEGSegmentUtil.java @@ -49,12 +49,12 @@ public final class JPEGSegmentUtil { private JPEGSegmentUtil() {} - // TODO: Allow for multiple images (multiple SOI markers), using specified index? - public static List readSegments(final ImageInputStream stream, final int appMarker, final String segmentName) throws IOException { + // TODO: Allow for multiple images (multiple SOI markers), using specified index, or document that stream must be placed before SOI of wanted image + public static List readSegments(final ImageInputStream stream, final int imageIndex, final int appMarker, final String segmentName) throws IOException { return readSegments(stream, Collections.singletonMap(appMarker, Collections.singletonList(segmentName))); } - public static List readSegments(final ImageInputStream stream, Map> segmentIdentifiers) throws IOException { + public static List readSegments(final ImageInputStream stream, final Map> segmentIdentifiers) throws IOException { readSOI(stream); List segments = Collections.emptyList(); diff --git a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java index 9d4e9a96..4115998a 100644 --- a/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java +++ b/imageio/imageio-metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java @@ -69,7 +69,7 @@ public class XMPScannerTestCase extends TestCase { } public void testScanForUTF8singleQuote() throws IOException { - InputStream stream = createXMPStream(XMP, "UTF-8".replace("\"", "'")); + InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-8"); Reader reader = XMPScanner.scanForXMPPacket(stream); @@ -85,7 +85,7 @@ public class XMPScannerTestCase extends TestCase { } public void testScanForUTF16BEsingleQuote() throws IOException { - InputStream stream = createXMPStream(XMP, "UTF-16BE".replace("\"", "'")); + InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-16BE"); Reader reader = XMPScanner.scanForXMPPacket(stream); @@ -101,7 +101,7 @@ public class XMPScannerTestCase extends TestCase { } public void testScanForUTF16LEsingleQuote() throws IOException { - InputStream stream = createXMPStream(XMP, "UTF-16LE".replace("\"", "'")); + InputStream stream = createXMPStream(XMP.replace("\"", "'"), "UTF-16LE"); Reader reader = XMPScanner.scanForXMPPacket(stream); diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java index bce32188..1ab9fff8 100644 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageReader.java @@ -28,9 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Parts of this software is based on JVG/JIS. See http://www.cs.hut.fi/~framling/JVG/index.html for more information. -Redistribution under BSD authorized by Kary Främling: +Redistribution under BSD authorized by Kary Fr�mling: -Copyright (c) 2003, Kary Främling +Copyright (c) 2003, Kary Fr�mling All rights reserved. Redistribution and use in source and binary forms, with or without @@ -85,7 +85,7 @@ import java.util.List; *

* * @author Harald Kuhr - * @author Kary Främling (original PICT/QuickDraw parsing) + * @author Kary Fr�mling (original PICT/QuickDraw parsing) * @author Matthias Wiesmann (original embedded QuickTime parsing) * @version $Id: PICTReader.java,v 1.0 05.apr.2006 15:20:48 haku Exp$ */ @@ -151,7 +151,7 @@ public class PICTImageReader extends ImageReaderBase { private Rectangle getPICTFrame() throws IOException { if (mFrame == null) { // Read in header information - readPICTHeader(mImageInput); + readPICTHeader(imageInput); if (DEBUG) { System.out.println("Done reading PICT header!"); } @@ -343,7 +343,7 @@ public class PICTImageReader extends ImageReaderBase { private void drawOnto(Graphics2D pGraphics) throws IOException { mContext = new QuickDrawContext(pGraphics); - readPICTopcodes(mImageInput); + readPICTopcodes(imageInput); if (DEBUG) { System.out.println("Done reading PICT body!"); } @@ -1666,7 +1666,7 @@ public class PICTImageReader extends ImageReaderBase { catch (EOFException e) { String pos; try { - pos = String.format("position %d", mImageInput.getStreamPosition()); + pos = String.format("position %d", imageInput.getStreamPosition()); } catch (IOException ignore) { pos = "unknown position"; @@ -1977,7 +1977,7 @@ public class PICTImageReader extends ImageReaderBase { unPackBits.readFully(pixArray, pixBufOffset, pBounds.width); /*} else { - mImageInput.readFully(dstBytes); + imageInput.readFully(dstBytes); }*/ // TODO: Use TYPE_USHORT_555_RGB for 16 bit @@ -2294,7 +2294,7 @@ public class PICTImageReader extends ImageReaderBase { unPackBits.readFully(dstBytes); } else { - mImageInput.readFully(dstBytes); + imageInput.readFully(dstBytes); } if (packType == 3) { diff --git a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java index 9149fb5b..84d6267c 100755 --- a/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java +++ b/imageio/imageio-pict/src/main/java/com/twelvemonkeys/imageio/plugins/pict/PICTImageWriter.java @@ -28,9 +28,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Parts of this software is based on JVG/JIS. See http://www.cs.hut.fi/~framling/JVG/index.html for more information. -Redistribution under BSD authorized by Kary Främling: +Redistribution under BSD authorized by Kary Fr�mling: -Copyright (c) 2003, Kary Främling +Copyright (c) 2003, Kary Fr�mling All rights reserved. Redistribution and use in source and binary forms, with or without @@ -79,7 +79,7 @@ import java.io.*; * Images are stored using the "opDirectBitsRect" opcode, which directly * stores RGB values (using PackBits run-length encoding). * - * @author Kary Främling + * @author Kary Fr�mling * @author Harald Kuhr * @version $Id: PICTWriter.java,v 1.0 05.apr.2006 15:20:48 haku Exp$ */ @@ -116,121 +116,121 @@ public class PICTImageWriter extends ImageWriterBase { // TODO: Make 512 byte header optional // Write empty 512-byte header byte[] buf = new byte[PICT.PICT_NULL_HEADER_SIZE]; - mImageOutput.write(buf); + imageOutput.write(buf); // Write out the size, leave as 0, this is ok - mImageOutput.writeShort(0); + imageOutput.writeShort(0); // Write image frame (same as image bounds) - mImageOutput.writeShort(0); - mImageOutput.writeShort(0); - mImageOutput.writeShort(pImage.getHeight()); - mImageOutput.writeShort(pImage.getWidth()); + imageOutput.writeShort(0); + imageOutput.writeShort(0); + imageOutput.writeShort(pImage.getHeight()); + imageOutput.writeShort(pImage.getWidth()); // Write version, version 2 - mImageOutput.writeShort(PICT.OP_VERSION); - mImageOutput.writeShort(PICT.OP_VERSION_2); + imageOutput.writeShort(PICT.OP_VERSION); + imageOutput.writeShort(PICT.OP_VERSION_2); // Version 2 HEADER_OP, extended version. - mImageOutput.writeShort(PICT.OP_HEADER_OP); - mImageOutput.writeInt(PICT.HEADER_V2_EXT); // incl 2 bytes reseverd + imageOutput.writeShort(PICT.OP_HEADER_OP); + imageOutput.writeInt(PICT.HEADER_V2_EXT); // incl 2 bytes reseverd // Image resolution, 72 dpi - mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI); - mImageOutput.writeShort(0); - mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI); - mImageOutput.writeShort(0); + imageOutput.writeShort(PICT.MAC_DEFAULT_DPI); + imageOutput.writeShort(0); + imageOutput.writeShort(PICT.MAC_DEFAULT_DPI); + imageOutput.writeShort(0); // Optimal source rectangle (same as image bounds) - mImageOutput.writeShort(0); - mImageOutput.writeShort(0); - mImageOutput.writeShort(pImage.getHeight()); - mImageOutput.writeShort(pImage.getWidth()); + imageOutput.writeShort(0); + imageOutput.writeShort(0); + imageOutput.writeShort(pImage.getHeight()); + imageOutput.writeShort(pImage.getWidth()); // Reserved (4 bytes) - mImageOutput.writeInt(0); + imageOutput.writeInt(0); // TODO: The header really ends here... // Highlight - mImageOutput.writeShort(PICT.OP_DEF_HILITE); + imageOutput.writeShort(PICT.OP_DEF_HILITE); // Set the clip rectangle - mImageOutput.writeShort(PICT.OP_CLIP_RGN); - mImageOutput.writeShort(10); - mImageOutput.writeShort(0); - mImageOutput.writeShort(0); - mImageOutput.writeShort(pImage.getHeight()); - mImageOutput.writeShort(pImage.getWidth()); + imageOutput.writeShort(PICT.OP_CLIP_RGN); + imageOutput.writeShort(10); + imageOutput.writeShort(0); + imageOutput.writeShort(0); + imageOutput.writeShort(pImage.getHeight()); + imageOutput.writeShort(pImage.getWidth()); // Pixmap operation - mImageOutput.writeShort(PICT.OP_DIRECT_BITS_RECT); + imageOutput.writeShort(PICT.OP_DIRECT_BITS_RECT); // PixMap pointer (always 0x000000FF); - mImageOutput.writeInt(0x000000ff); + imageOutput.writeInt(0x000000ff); // Write rowBytes, this is 4 times the width. // Set the high bit, to indicate a PixMap. mRowBytes = 4 * pImage.getWidth(); - mImageOutput.writeShort(0x8000 | mRowBytes); + imageOutput.writeShort(0x8000 | mRowBytes); // Write bounds rectangle (same as image bounds) - mImageOutput.writeShort(0); - mImageOutput.writeShort(0); - mImageOutput.writeShort(pImage.getHeight()); // TODO: Handle overflow? - mImageOutput.writeShort(pImage.getWidth()); + imageOutput.writeShort(0); + imageOutput.writeShort(0); + imageOutput.writeShort(pImage.getHeight()); // TODO: Handle overflow? + imageOutput.writeShort(pImage.getWidth()); // PixMap record version - mImageOutput.writeShort(0); + imageOutput.writeShort(0); // Packing format (always 4: PackBits) - mImageOutput.writeShort(4); + imageOutput.writeShort(4); // Size of packed data (leave as 0) - mImageOutput.writeInt(0); + imageOutput.writeInt(0); // Pixmap resolution, 72 dpi - mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI); - mImageOutput.writeShort(0); - mImageOutput.writeShort(PICT.MAC_DEFAULT_DPI); - mImageOutput.writeShort(0); + imageOutput.writeShort(PICT.MAC_DEFAULT_DPI); + imageOutput.writeShort(0); + imageOutput.writeShort(PICT.MAC_DEFAULT_DPI); + imageOutput.writeShort(0); // Pixel type, 16 is allright for direct pixels - mImageOutput.writeShort(16); + imageOutput.writeShort(16); // Pixel size - mImageOutput.writeShort(32); + imageOutput.writeShort(32); // TODO: Allow alpha? Allow 5 bit per pixel component (16 bit)? // Pixel component count - mImageOutput.writeShort(3); + imageOutput.writeShort(3); // Pixel component size - mImageOutput.writeShort(8); + imageOutput.writeShort(8); // PlaneBytes, ignored for now - mImageOutput.writeInt(0); + imageOutput.writeInt(0); // TODO: Allow IndexColorModel? // ColorTable record (for RGB direct pixels, just write 0) - mImageOutput.writeInt(0); + imageOutput.writeInt(0); // Reserved (4 bytes) - mImageOutput.writeInt(0); + imageOutput.writeInt(0); // Source and dest rect (both are same as image bounds) - mImageOutput.writeShort(0); - mImageOutput.writeShort(0); - mImageOutput.writeShort(pImage.getHeight()); - mImageOutput.writeShort(pImage.getWidth()); + imageOutput.writeShort(0); + imageOutput.writeShort(0); + imageOutput.writeShort(pImage.getHeight()); + imageOutput.writeShort(pImage.getWidth()); - mImageOutput.writeShort(0); - mImageOutput.writeShort(0); - mImageOutput.writeShort(pImage.getHeight()); - mImageOutput.writeShort(pImage.getWidth()); + imageOutput.writeShort(0); + imageOutput.writeShort(0); + imageOutput.writeShort(pImage.getHeight()); + imageOutput.writeShort(pImage.getWidth()); // Transfer mode - mImageOutput.writeShort(QuickDraw.SRC_COPY); + imageOutput.writeShort(QuickDraw.SRC_COPY); // TODO: Move to writePICTData? // TODO: Alpha support @@ -282,13 +282,13 @@ public class PICTImageWriter extends ImageWriterBase { packBits.write(mScanlineBytes); if (mRowBytes > 250) { - mImageOutput.writeShort(bytes.size()); + imageOutput.writeShort(bytes.size()); } else { - mImageOutput.writeByte(bytes.size()); + imageOutput.writeByte(bytes.size()); } - bytes.writeTo(IIOUtil.createStreamAdapter(mImageOutput)); + bytes.writeTo(IIOUtil.createStreamAdapter(imageOutput)); mScanWidthLeft = w; } @@ -327,13 +327,13 @@ public class PICTImageWriter extends ImageWriterBase { packBits.write(mScanlineBytes); if (mRowBytes > 250) { - mImageOutput.writeShort(bytes.size()); + imageOutput.writeShort(bytes.size()); } else { - mImageOutput.writeByte(bytes.size()); + imageOutput.writeByte(bytes.size()); } - bytes.writeTo(IIOUtil.createStreamAdapter(mImageOutput)); + bytes.writeTo(IIOUtil.createStreamAdapter(imageOutput)); mScanWidthLeft = w; } @@ -342,15 +342,15 @@ public class PICTImageWriter extends ImageWriterBase { private void writePICTTrailer() throws IOException { // Write out end opcode. Be sure to be word-aligned. - long length = mImageOutput.length(); + long length = imageOutput.length(); if (length == -1) { throw new IIOException("Cannot write trailer without knowing length"); } if ((length & 1) > 0) { - mImageOutput.writeByte(0); + imageOutput.writeByte(0); } - mImageOutput.writeShort(PICT.OP_END_OF_PICTURE); + imageOutput.writeShort(PICT.OP_END_OF_PICTURE); } public void write(IIOMetadata pStreamMetadata, IIOImage pImage, ImageWriteParam pParam) throws IOException { diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 6adb8e65..81c41483 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -30,6 +30,7 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.imageio.ImageReaderBase; +import com.twelvemonkeys.imageio.color.ColorSpaces; import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier; import com.twelvemonkeys.xml.XMLSerializer; import org.w3c.dom.Node; @@ -63,6 +64,7 @@ import java.util.List; */ // TODO: Implement ImageIO meta data interface // TODO: Allow reading the extra alpha channels (index after composite data) +// TODO: Figure out of we should assume Adobe RGB (1998) color model, if no embedded profile? // TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0) // TODO: Support for API for reading separate layers (index after composite data, and optional alpha channels) // TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers @@ -151,6 +153,7 @@ public class PSDImageReader extends ImageReaderBase { case PSD.COLOR_MODE_RGB: cs = getEmbeddedColorSpace(); if (cs == null) { + // TODO: Should probably be Adobe RGB (1998), not sRGB. Or..? cs = ColorSpace.getInstance(ColorSpace.CS_sRGB); } @@ -174,7 +177,7 @@ public class PSDImageReader extends ImageReaderBase { case PSD.COLOR_MODE_CMYK: cs = getEmbeddedColorSpace(); if (cs == null) { - cs = CMYKColorSpace.getInstance(); + cs = ColorSpaces.getColorSpace(ColorSpaces.CS_GENERIC_CMYK); } if (mHeader.mChannels == 4 && mHeader.mBits == 8) { @@ -198,6 +201,7 @@ public class PSDImageReader extends ImageReaderBase { // TODO: Implement case PSD.COLOR_MODE_LAB: // TODO: Implement + // TODO: If there's a color profile embedded, it should be easy, otherwise we're out of luck... default: throw new IIOException( String.format("Unsupported PSD MODE: %s (%d channels/%d bits)", mHeader.mMode, mHeader.mChannels, mHeader.mBits) @@ -219,12 +223,26 @@ public class PSDImageReader extends ImageReaderBase { case PSD.COLOR_MODE_RGB: // Prefer interleaved versions as they are much faster to display if (mHeader.mChannels == 3 && mHeader.mBits == 8) { - // Basically same as BufferedImage.TYPE_3BYTE_BGR - types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)); + // TODO: ColorConvertOp to CS_sRGB + // TODO: Integer raster + // types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_RGB)); + types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR)); + + if (!cs.isCS_sRGB()) { + // Basically BufferedImage.TYPE_3BYTE_BGR, with corrected ColorSpace. Possibly slow. + types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)); + } } else if (mHeader.mChannels >= 4 && mHeader.mBits == 8) { - // Basically same as BufferedImage.TYPE_4BYTE_ABGR - types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false)); + // TODO: ColorConvertOp to CS_sRGB + // TODO: Integer raster + // types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.INT_ARGB)); + types.add(ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR)); +// + if (!cs.isCS_sRGB()) { + // Basically BufferedImage.TYPE_4BYTE_ABGR, with corrected ColorSpace. Possibly slow. + types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, true, false)); + } } else if (mHeader.mChannels == 3 && mHeader.mBits == 16) { types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {2, 1, 0}, DataBuffer.TYPE_USHORT, false, false)); @@ -235,9 +253,11 @@ public class PSDImageReader extends ImageReaderBase { break; case PSD.COLOR_MODE_CMYK: // Prefer interleaved versions as they are much faster to display + // TODO: ColorConvertOp to CS_sRGB // TODO: We should convert these to their RGB equivalents while reading for the common-case, // as Java2D is extremely slow displaying custom images. // Converting to RGB is also correct behaviour, according to the docs. + // Doing this, will require rewriting the image reading, as the raw image data is channelled, not interleaved :-/ if (mHeader.mChannels == 4 && mHeader.mBits == 8) { types.add(ImageTypeSpecifier.createInterleaved(cs, new int[] {3, 2, 1, 0}, DataBuffer.TYPE_BYTE, false, false)); } @@ -255,7 +275,7 @@ public class PSDImageReader extends ImageReaderBase { // Just stick to the raw type } - // Finally add the + // Finally add the raw type types.add(rawType); return types.iterator(); @@ -275,7 +295,7 @@ public class PSDImageReader extends ImageReaderBase { } } - mColorSpace = profile == null ? null : new ICC_ColorSpace(profile); + mColorSpace = profile == null ? null : ColorSpaces.createColorSpace(profile); } return mColorSpace; @@ -307,6 +327,8 @@ public class PSDImageReader extends ImageReaderBase { // Otherwise, copy "through" ColorModel? // Copy pixels from temp raster // If possible, leave the destination image "untouched" (accelerated) + // See Jim Grahams comments: + // http://forums.java.net/jive/message.jspa?messageID=295758#295758 // TODO: Doing a per line color convert will be expensive, as data is channelled... // Will need to either convert entire image, or skip back/forth between channels... @@ -336,7 +358,7 @@ public class PSDImageReader extends ImageReaderBase { processImageStarted(pIndex); int[] byteCounts = null; - int compression = mImageInput.readShort(); + int compression = imageInput.readShort(); // TODO: Need to make sure compression is set in metadata, even without reading the image data! mMetadata.mCompression = compression; @@ -347,7 +369,7 @@ public class PSDImageReader extends ImageReaderBase { // NOTE: Byte counts will allow us to easily skip rows before AOI byteCounts = new int[mHeader.mChannels * mHeader.mHeight]; for (int i = 0; i < byteCounts.length; i++) { - byteCounts[i] = mImageInput.readUnsignedShort(); + byteCounts[i] = imageInput.readUnsignedShort(); } break; case PSD.COMPRESSION_ZIP: @@ -400,24 +422,15 @@ public class PSDImageReader extends ImageReaderBase { switch (mHeader.mBits) { case 1: byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer(); - byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData(); - - read1bitChannel(c, mHeader.mChannels, data1, interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, pCompression == PSD.COMPRESSION_RLE); + read1bitChannel(c, mHeader.mChannels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, pCompression == PSD.COMPRESSION_RLE); break; case 8: byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer(); - byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData(); - - read8bitChannel(c, mHeader.mChannels, data8, interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, c * mHeader.mHeight, pCompression == PSD.COMPRESSION_RLE); + read8bitChannel(c, mHeader.mChannels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, c * mHeader.mHeight, pCompression == PSD.COMPRESSION_RLE); break; case 16: short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); - DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer(); - short[] data16 = banded ? buffer16.getData(c) : buffer16.getData(); - - read16bitChannel(c, mHeader.mChannels, data16, interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, c * mHeader.mHeight, pCompression == PSD.COMPRESSION_RLE); + read16bitChannel(c, mHeader.mChannels, raster.getDataBuffer(), interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, c * mHeader.mHeight, pCompression == PSD.COMPRESSION_RLE); break; default: throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits)); @@ -430,12 +443,16 @@ public class PSDImageReader extends ImageReaderBase { if (mHeader.mBits == 8) { // Compose out the background of the semi-transparent pixels, as PS somehow has the background composed in - decomposeAlpha(destCM, (DataBufferByte) raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands()); + decomposeAlpha(destCM, raster.getDataBuffer(), pDest.width, pDest.height, raster.getNumBands()); } } + private void processImageProgressForChannel(int channel, int channelCount, int y, int height) { + processImageProgress(100f * channel / channelCount + 100f * y / (height * channelCount)); + } + private void read16bitChannel(final int pChannel, final int pChannelCount, - final short[] pData, final int pBands, final int pBandOffset, + final DataBuffer pData, final int pBands, final int pBandOffset, final ColorModel pSourceColorModel, final short[] pRow, final Rectangle pSource, final Rectangle pDest, @@ -446,6 +463,7 @@ public class PSDImageReader extends ImageReaderBase { final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK; final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents(); + final boolean banded = pData.getNumBanks() > 1; for (int y = 0; y < pChannelHeight; y++) { // NOTE: Length is in *16 bit values* (shorts) @@ -455,7 +473,7 @@ public class PSDImageReader extends ImageReaderBase { // Read entire line, if within source region and sampling if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) { if (pRLECompressed) { - DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length); + DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length); try { for (int x = 0; x < pChannelWidth; x++) { pRow[x] = input.readShort(); @@ -466,7 +484,7 @@ public class PSDImageReader extends ImageReaderBase { } } else { - mImageInput.readFully(pRow, 0, pChannelWidth); + imageInput.readFully(pRow, 0, pChannelWidth); } // TODO: Destination offset...?? @@ -480,22 +498,22 @@ public class PSDImageReader extends ImageReaderBase { value = (short) (65535 - value & 0xffff); } - pData[offset + x * pBands] = value; + pData.setElem(banded ? pChannel : 0, offset + x * pBands, value); } } else { - mImageInput.skipBytes(length); + imageInput.skipBytes(length); } if (abortRequested()) { break; } - processImageProgress((pChannel * y * 100) / pChannelCount * pChannelHeight); + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } private void read8bitChannel(final int pChannel, final int pChannelCount, - final byte[] pData, final int pBands, final int pBandOffset, + final DataBuffer pData, final int pBands, final int pBandOffset, final ColorModel pSourceColorModel, final byte[] pRow, final Rectangle pSource, final Rectangle pDest, @@ -506,6 +524,7 @@ public class PSDImageReader extends ImageReaderBase { final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK; final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents(); + final boolean banded = pData.getNumBanks() > 1; for (int y = 0; y < pChannelHeight; y++) { int length = pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth; @@ -514,7 +533,7 @@ public class PSDImageReader extends ImageReaderBase { // Read entire line, if within source region and sampling if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) { if (pRLECompressed) { - DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length); + DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length); try { input.readFully(pRow, 0, pChannelWidth); } @@ -523,7 +542,7 @@ public class PSDImageReader extends ImageReaderBase { } } else { - mImageInput.readFully(pRow, 0, pChannelWidth); + imageInput.readFully(pRow, 0, pChannelWidth); } // TODO: If banded and not sub sampling/cmyk, we could just copy using System.arraycopy @@ -538,23 +557,23 @@ public class PSDImageReader extends ImageReaderBase { value = (byte) (255 - value & 0xff); } - pData[offset + x * pBands] = value; + pData.setElem(banded ? pChannel : 0, offset + x * pBands, value); } } else { - mImageInput.skipBytes(length); + imageInput.skipBytes(length); } if (abortRequested()) { break; } - processImageProgress((pChannel * y * 100) / pChannelCount * pChannelHeight); + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } @SuppressWarnings({"UnusedDeclaration"}) private void read1bitChannel(final int pChannel, final int pChannelCount, - final byte[] pData, final int pBands, final int pBandOffset, + final DataBuffer pData, final int pBands, final int pBandOffset, final ColorModel pSourceColorModel, final byte[] pRow, final Rectangle pSource, final Rectangle pDest, @@ -564,6 +583,7 @@ public class PSDImageReader extends ImageReaderBase { // NOTE: 1 bit channels only occurs once final int destWidth = (pDest.width + 7) / 8; + final boolean banded = pData.getNumBanks() > 1; for (int y = 0; y < pChannelHeight; y++) { int length = pRLECompressed ? pRowByteCounts[y] : pChannelWidth; @@ -572,7 +592,7 @@ public class PSDImageReader extends ImageReaderBase { // Read entire line, if within source region and sampling if (y >= pSource.y && y < pSource.y + pSource.height && y % pYSub == 0) { if (pRLECompressed) { - DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length); + DataInputStream input = PSDUtil.createPackBitsStream(imageInput, length); try { input.readFully(pRow, 0, pRow.length); } @@ -581,7 +601,7 @@ public class PSDImageReader extends ImageReaderBase { } } else { - mImageInput.readFully(pRow, 0, pRow.length); + imageInput.readFully(pRow, 0, pRow.length); } // TODO: Destination offset...?? @@ -591,7 +611,7 @@ public class PSDImageReader extends ImageReaderBase { for (int i = 0; i < destWidth; i++) { byte value = pRow[pSource.x / 8 + i * pXSub]; // NOTE: Invert bits to match Java's default monochrome - pData[offset + i] = (byte) (~value & 0xff); + pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~value & 0xff)); } } else { @@ -615,22 +635,22 @@ public class PSDImageReader extends ImageReaderBase { } // NOTE: Invert bits to match Java's default monochrome - pData[offset + i] = (byte) (~result & 0xff); + pData.setElem(banded ? pChannel : 0, offset + i, (byte) (~result & 0xff)); } } } else { - mImageInput.skipBytes(length); + imageInput.skipBytes(length); } if (abortRequested()) { break; } - processImageProgress((pChannel * y * 100) / pChannelCount * pChannelHeight); + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } - private void decomposeAlpha(final ColorModel pModel, final DataBufferByte pBuffer, + private void decomposeAlpha(final ColorModel pModel, final DataBuffer pBuffer, final int pWidth, final int pHeight, final int pChannels) { // TODO: Is the document background always white!? // TODO: What about CMYK + alpha? @@ -638,48 +658,45 @@ public class PSDImageReader extends ImageReaderBase { // TODO: Probably faster to do this in line.. if (pBuffer.getNumBanks() > 1) { - byte[][] data = pBuffer.getBankData(); for (int y = 0; y < pHeight; y++) { for (int x = 0; x < pWidth; x++) { int offset = (x + y * pWidth); // ARGB format - int alpha = data[pChannels - 1][offset] & 0xff; + int alpha = pBuffer.getElem(pChannels - 1, offset) & 0xff; if (alpha != 0) { double normalizedAlpha = alpha / 255.0; for (int i = 0; i < pChannels - 1; i++) { - data[i][offset] = decompose(data[i][offset] & 0xff, normalizedAlpha); + pBuffer.setElem(i, offset, decompose(pBuffer.getElem(i, offset) & 0xff, normalizedAlpha)); } } else { for (int i = 0; i < pChannels - 1; i++) { - data[i][offset] = 0; + pBuffer.setElem(i, offset, 0); } } } } } else { - byte[] data = pBuffer.getData(); - for (int y = 0; y < pHeight; y++) { for (int x = 0; x < pWidth; x++) { int offset = (x + y * pWidth) * pChannels; // ABGR format - int alpha = data[offset] & 0xff; + int alpha = pBuffer.getElem(offset) & 0xff; if (alpha != 0) { double normalizedAlpha = alpha / 255.0; for (int i = 1; i < pChannels; i++) { - data[offset + i] = decompose(data[offset + i] & 0xff, normalizedAlpha); + pBuffer.setElem(offset + i, decompose(pBuffer.getElem(offset + i) & 0xff, normalizedAlpha)); } } else { for (int i = 1; i < pChannels; i++) { - data[offset + i] = 0; + pBuffer.setElem(offset + i, 0); } } } @@ -697,7 +714,7 @@ public class PSDImageReader extends ImageReaderBase { private void readHeader() throws IOException { assertInput(); if (mHeader == null) { - mHeader = new PSDHeader(mImageInput); + mHeader = new PSDHeader(imageInput); mMetadata = new PSDMetadata(); mMetadata.mHeader = mHeader; @@ -714,17 +731,17 @@ public class PSDImageReader extends ImageReaderBase { around as a black box for use when saving the file. */ if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { - mMetadata.mColorData = new PSDColorData(mImageInput); + mMetadata.mColorData = new PSDColorData(imageInput); } else { // TODO: We need to store the duotone spec if we decide to create a writer... // Skip color mode data for other modes - long length = mImageInput.readUnsignedInt(); - mImageInput.skipBytes(length); + long length = imageInput.readUnsignedInt(); + imageInput.skipBytes(length); } // Don't need the header again - mImageInput.flushBefore(mImageInput.getStreamPosition()); + imageInput.flushBefore(imageInput.getStreamPosition()); } } @@ -732,40 +749,40 @@ public class PSDImageReader extends ImageReaderBase { // TODO: Obey ignoreMetadata private void readImageResources(final boolean pParseData) throws IOException { // TODO: Avoid unnecessary stream repositioning - long pos = mImageInput.getFlushedPosition(); - mImageInput.seek(pos); + long pos = imageInput.getFlushedPosition(); + imageInput.seek(pos); - long length = mImageInput.readUnsignedInt(); + long length = imageInput.readUnsignedInt(); if (pParseData && length > 0) { if (mMetadata.mImageResources == null) { mMetadata.mImageResources = new ArrayList(); - long expectedEnd = mImageInput.getStreamPosition() + length; + long expectedEnd = imageInput.getStreamPosition() + length; - while (mImageInput.getStreamPosition() < expectedEnd) { + while (imageInput.getStreamPosition() < expectedEnd) { // TODO: Have PSDImageResources defer actual parsing? (Just store stream offsets) - PSDImageResource resource = PSDImageResource.read(mImageInput); + PSDImageResource resource = PSDImageResource.read(imageInput); mMetadata.mImageResources.add(resource); } - if (mImageInput.getStreamPosition() != expectedEnd) { + if (imageInput.getStreamPosition() != expectedEnd) { throw new IIOException("Corrupt PSD document"); // ..or maybe just a bug in the reader.. ;-) } } } - mImageInput.seek(pos + length + 4); + imageInput.seek(pos + length + 4); } // TODO: Flags or list of interesting resources to parse // TODO: Obey ignoreMetadata private void readLayerAndMaskInfo(final boolean pParseData) throws IOException { // TODO: Make sure we are positioned correctly - long length = mImageInput.readUnsignedInt(); + long length = imageInput.readUnsignedInt(); if (pParseData && length > 0) { - long pos = mImageInput.getStreamPosition(); + long pos = imageInput.getStreamPosition(); - long layerInfoLength = mImageInput.readUnsignedInt(); + long layerInfoLength = imageInput.readUnsignedInt(); /* "Layer count. If it is a negative number, its absolute value is the number of @@ -773,19 +790,19 @@ public class PSDImageReader extends ImageReaderBase { merged result." */ // TODO: Figure out what the last part of that sentence means in practice... - int layers = mImageInput.readShort(); + int layers = imageInput.readShort(); PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layers)]; for (int i = 0; i < layerInfos.length; i++) { - layerInfos[i] = new PSDLayerInfo(mImageInput); + layerInfos[i] = new PSDLayerInfo(imageInput); } mMetadata.mLayerInfo = Arrays.asList(layerInfos); // TODO: Clean-up - mImageInput.mark(); + imageInput.mark(); ImageTypeSpecifier raw = getRawImageTypeInternal(0); ImageTypeSpecifier imageType = getImageTypes(0).next(); - mImageInput.reset(); + imageInput.reset(); for (PSDLayerInfo layerInfo : layerInfos) { // TODO: If not explicitly needed, skip layers... @@ -797,30 +814,30 @@ public class PSDImageReader extends ImageReaderBase { // } } - long read = mImageInput.getStreamPosition() - pos; + long read = imageInput.getStreamPosition() - pos; long diff = layerInfoLength - (read - 4); // - 4 for the layerInfoLength field itself // System.out.println("diff: " + diff); - mImageInput.skipBytes(diff); + imageInput.skipBytes(diff); // TODO: Global LayerMaskInfo (18 bytes or more..?) // 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad) - long layerMaskInfoLength = mImageInput.readUnsignedInt(); + long layerMaskInfoLength = imageInput.readUnsignedInt(); // System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength); if (layerMaskInfoLength > 0) { - mMetadata.mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput); + mMetadata.mGlobalLayerMask = new PSDGlobalLayerMask(imageInput); // System.out.println("mGlobalLayerMask: " + mGlobalLayerMask); } - read = mImageInput.getStreamPosition() - pos; + read = imageInput.getStreamPosition() - pos; long toSkip = length - read; // System.out.println("toSkip: " + toSkip); - mImageInput.skipBytes(toSkip); + imageInput.skipBytes(toSkip); } else { // Skip entire layer and mask section - mImageInput.skipBytes(length); + imageInput.skipBytes(length); } } @@ -853,13 +870,13 @@ public class PSDImageReader extends ImageReaderBase { final int interleavedBands = banded ? 1 : raster.getNumBands(); for (PSDChannelInfo channelInfo : pLayerInfo.mChannelInfo) { - int compression = mImageInput.readUnsignedShort(); + int compression = imageInput.readUnsignedShort(); // Skip layer if we can't read it // channelId == -2 means "user supplied layer mask", whatever that is... if (width <= 0 || height <= 0 || channelInfo.mChannelId == -2 || (compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) { - mImageInput.skipBytes(channelInfo.mLength - 2); + imageInput.skipBytes(channelInfo.mLength - 2); } else { // 0 = red, 1 = green, etc @@ -881,7 +898,7 @@ public class PSDImageReader extends ImageReaderBase { // each count stored as a two*byte value. byteCounts = new int[pLayerInfo.mBottom - pLayerInfo.mTop]; for (int i = 0; i < byteCounts.length; i++) { - byteCounts[i] = mImageInput.readUnsignedShort(); + byteCounts[i] = imageInput.readUnsignedShort(); } break; @@ -897,24 +914,25 @@ public class PSDImageReader extends ImageReaderBase { switch (mHeader.mBits) { case 1: byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer(); - byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData(); +// DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer(); +// byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData(); - read1bitChannel(c, imageType.getNumBands(), data1, interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE); + read1bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE); break; case 8: byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData(); - DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer(); - byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData(); +// DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer(); +// byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData(); - read8bitChannel(c, imageType.getNumBands(), data8, interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); +// read8bitChannel(c, imageType.getNumBands(), data8, interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); + read8bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); break; case 16: short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData(); - DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer(); - short[] data16 = banded ? buffer16.getData(c) : buffer16.getData(); +// DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer(); +// short[] data16 = banded ? buffer16.getData(c) : buffer16.getData(); - read16bitChannel(c, imageType.getNumBands(), data16, interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); + read16bitChannel(c, imageType.getNumBands(), raster.getDataBuffer(), interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); break; default: throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits)); @@ -995,7 +1013,7 @@ public class PSDImageReader extends ImageReaderBase { readLayerAndMaskInfo(true); // TODO: Need to make sure compression is set in metadata, even without reading the image data! - mMetadata.mCompression = mImageInput.readShort(); + mMetadata.mCompression = imageInput.readShort(); // mMetadata.mHeader = mHeader; // mMetadata.mColorData = mColorData; diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java index 7aa60dc4..fed3d818 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java @@ -43,7 +43,7 @@ import java.lang.reflect.Field; * @author last modified by $Author: haraldk$ * @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$ */ -class PSDImageResource { +public class PSDImageResource { // TODO: Refactor image resources to separate package // TODO: Change constructor to store stream offset and length only (+ possibly the name), defer reading diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/YCbCrColorSpace.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/YCbCrColorSpace.java deleted file mode 100755 index d0d020f1..00000000 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/YCbCrColorSpace.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2008, Harald Kuhr - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * Neither the name "TwelveMonkeys" 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 OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.twelvemonkeys.imageio.plugins.psd; - -import java.awt.color.ColorSpace; - -/** - * YCbCrColorSpace - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: YCbCrColorSpace.java,v 1.0 Jun 28, 2008 3:30:50 PM haraldk Exp$ - */ -// TODO: Move to com.twlevemonkeys.image? -// TODO: Read an ICC YCbCr profile from classpath resource? Is there such a thing? -final class YCbCrColorSpace extends ColorSpace { - - static final ColorSpace INSTANCE = new CMYKColorSpace(); - final ColorSpace sRGB = getInstance(CS_sRGB); - - YCbCrColorSpace() { - super(ColorSpace.TYPE_YCbCr, 3); - } - - public static ColorSpace getInstance() { - return INSTANCE; - } - - // http://www.w3.org/Graphics/JPEG/jfif.txt - /* - Conversion to and from RGB - - Y, Cb, and Cr are converted from R, G, and B as defined in CCIR Recommendation 601 - but are normalized so as to occupy the full 256 levels of a 8-bit binary encoding. More - precisely: - - Y = 256 * E'y - Cb = 256 * [ E'Cb ] + 128 - Cr = 256 * [ E'Cr ] + 128 - - where the E'y, E'Cb and E'Cb are defined as in CCIR 601. Since values of E'y have a - range of 0 to 1.0 and those for E'Cb and E'Cr have a range of -0.5 to +0.5, Y, Cb, and Cr - must be clamped to 255 when they are maximum value. - - RGB to YCbCr Conversion - - YCbCr (256 levels) can be computed directly from 8-bit RGB as follows: - - Y = 0.299 R + 0.587 G + 0.114 B - Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128 - Cr = 0.5 R - 0.4187 G - 0.0813 B + 128 - - NOTE - Not all image file formats store image samples in the order R0, G0, - B0, ... Rn, Gn, Bn. Be sure to verify the sample order before converting an - RGB file to JFIF. - - YCbCr to RGB Conversion - - RGB can be computed directly from YCbCr (256 levels) as follows: - - R = Y + 1.402 (Cr-128) - G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128) - B = Y + 1.772 (Cb-128) - */ - public float[] toRGB(float[] colorvalue) { -// R = Y + 1.402 (Cr-128) -// G = Y - 0.34414 (Cb-128) - 0.71414 (Cr-128) -// B = Y + 1.772 (Cb-128) - return new float[] { - colorvalue[0] + 1.402f * (colorvalue[2] - .5f), - colorvalue[0] - 0.34414f * (colorvalue[1] - .5f) - 0.71414f * (colorvalue[2] - .5f), - colorvalue[0] + 1.772f * (colorvalue[1] - .5f), - }; - // TODO: Convert via CIEXYZ space using sRGB space, as suggested in docs - // return sRGB.fromCIEXYZ(toCIEXYZ(colorvalue)); - } - - public float[] fromRGB(float[] rgbvalue) { -// Y = 0.299 R + 0.587 G + 0.114 B -// Cb = - 0.1687 R - 0.3313 G + 0.5 B + 128 -// Cr = 0.5 R - 0.4187 G - 0.0813 B + 128 - return new float[] { - 0.299f * rgbvalue[0] + 0.587f * rgbvalue[1] + 0.114f * rgbvalue[2], - -0.1687f * rgbvalue[0] - 0.3313f * rgbvalue[1] + 0.5f * rgbvalue[2] + .5f, - 0.5f * rgbvalue[0] - 0.4187f * rgbvalue[1] - 0.0813f * rgbvalue[2] + .5f - }; - } - - public float[] toCIEXYZ(float[] colorvalue) { - throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement - } - - public float[] fromCIEXYZ(float[] colorvalue) { - throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement - } -} diff --git a/imageio/imageio-psd/todo.txt b/imageio/imageio-psd/todo.txt index c7d6d53c..7d731b62 100755 --- a/imageio/imageio-psd/todo.txt +++ b/imageio/imageio-psd/todo.txt @@ -1,6 +1,7 @@ -Implement source subsampling and region of interest -Separate package for the resources (seems to be a lot)? -Possibility to read only some resources? readResources(int[] resourceKeys)? - - Probably faster when we only need the color space -Support for Photoshop specific TIFF tags (extension for TIFFImageReader)? -PSDImageWriter? \ No newline at end of file +- Implement source subsampling and region of interest +- Separate package for the resources (seems to be a lot)? + - Move to metadata package, or implement metadata interface, as this is (similar|equivalent) to TIFF/JPEG APP13 segment tag? +- Possibility to read only some resources? readResources(int[] resourceKeys)? + - Probably faster when we only need the color space +- Support for Photoshop specific TIFF tags (extension for TIFFImageReader)? +- PSDImageWriter? diff --git a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java index 56b80623..67249499 100644 --- a/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java +++ b/imageio/imageio-thumbsdb/src/main/java/com/twelvemonkeys/imageio/plugins/thumbsdb/ThumbsDBImageReader.java @@ -209,8 +209,8 @@ public class ThumbsDBImageReader extends ImageReaderBase { @Override public void setInput(Object pInput, boolean pSeekForwardOnly, boolean pIgnoreMetadata) { super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata); - if (mImageInput != null) { - mImageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + if (imageInput != null) { + imageInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); } } @@ -263,7 +263,7 @@ public class ThumbsDBImageReader extends ImageReaderBase { private void init() throws IOException { assertInput(); if (mRoot == null) { - mRoot = new CompoundDocument(mImageInput).getRootEntry(); + mRoot = new CompoundDocument(imageInput).getRootEntry(); SortedSet children = mRoot.getChildEntries(); mThumbnails = new BufferedImage[children.size() - 1];