From 5dab7eb1ff9403fbb831e8c345831e601d4ebd07 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 5 Nov 2009 16:59:29 +0100 Subject: [PATCH 01/21] Removed an old file that shouldn't have been committed... --- .../imageio/plugins/psd/PSDReader.java | 555 ------------------ 1 file changed, 555 deletions(-) delete mode 100755 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java deleted file mode 100755 index b38c6480..00000000 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDReader.java +++ /dev/null @@ -1,555 +0,0 @@ -package com.twelvemonkeys.imageio.plugins.psd; - -import java.awt.*; -import java.awt.image.*; -import java.net.*; -import java.io.*; - -/** - * Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames. - * Supports uncompressed or RLE-compressed RGB files only. Each layer may be - * retrieved as a full frame BufferedImage, or as a smaller image with an - * offset if the layer does not occupy the full frame size. Transparency - * in the original psd file is preserved in the returned BufferedImage's. - * Does not support additional features in PS versions higher than 3.0. - * Example: - *
- *    PSDReader r = new PSDReader();
- *    r.readData("sample.psd");
- *    int n = r.getFrameCount();
- *    for (int i = 0; i < n; i++) {
- *       BufferedImage image = r.getLayer(i);
- *       Point offset = r.getLayerOffset(i);
- *       // do something with image
- *    }
- * 
- * No copyright asserted on the source code of this class. May be used for - * any purpose. Please forward any corrections to kweiner@fmsware.com. - * - * @author Kevin Weiner, FM Software. - * @version 1.1 January 2004 [bug fix; add RLE support] - * - */ -// SEE: http://www.fileformat.info/format/psd/egff.htm#ADOBEPHO-DMYID.4 -class PSDReader { - - /** - * File readData status: No errors. - */ - public static final int STATUS_OK = 0; - - /** - * File readData status: Error decoding file (may be partially decoded) - */ - public static final int STATUS_FORMAT_ERROR = 1; - - /** - * File readData status: Unable to open source. - */ - public static final int STATUS_OPEN_ERROR = 2; - - /** - * File readData status: Unsupported format - */ - public static final int STATUS_UNSUPPORTED = 3; - - public static int ImageType = BufferedImage.TYPE_INT_ARGB; - - protected BufferedInputStream input; - protected int frameCount; - protected BufferedImage[] frames; - protected int status = 0; - protected int nChan; - protected int width; - protected int height; - protected int nLayers; - protected int miscLen; - protected boolean hasLayers; - protected LayerInfo[] layers; - protected short[] lineLengths; - protected int lineIndex; - protected boolean rleEncoded; - - protected class LayerInfo { - int x, y, w, h; - int nChan; - int[] chanID; - int alpha; - } - - /** - * Gets the number of layers readData from file. - * @return frame count - */ - public int getFrameCount() { - return frameCount; - } - - protected void setInput(InputStream stream) { - // open input stream - init(); - if (stream == null) { - status = STATUS_OPEN_ERROR; - } else { - if (stream instanceof BufferedInputStream) - input = (BufferedInputStream) stream; - else - input = new BufferedInputStream(stream); - } - } - - protected void setInput(String name) { - // open input file - init(); - try { - name = name.trim(); - if (name.startsWith("file:")) { - name = name.substring(5); - while (name.startsWith("/")) - name = name.substring(1); - } - if (name.indexOf("://") > 0) { - URL url = new URL(name); - input = new BufferedInputStream(url.openStream()); - } else { - input = new BufferedInputStream(new FileInputStream(name)); - } - } catch (IOException e) { - status = STATUS_OPEN_ERROR; - } - } - - /** - * Gets display duration for specified frame. Always returns 0. - * - */ - public int getDelay(int forFrame) { - return 0; - } - - /** - * Gets the image contents of frame n. Note that this expands the image - * to the full frame size (if the layer was smaller) and any subsequent - * use of getLayer() will return the full image. - * - * @return BufferedImage representation of frame, or null if n is invalid. - */ - public BufferedImage getFrame(int n) { - BufferedImage im = null; - if ((n >= 0) && (n < nLayers)) { - im = frames[n]; - LayerInfo info = layers[n]; - if ((info.w != width) || (info.h != height)) { - BufferedImage temp = - new BufferedImage(width, height, ImageType); - Graphics2D gc = temp.createGraphics(); - gc.drawImage(im, info.x, info.y, null); - gc.dispose(); - im = temp; - frames[n] = im; - } - } - return im; - } - - /** - * Gets maximum image size. Individual layers may be smaller. - * - * @return maximum image dimensions - */ - public Dimension getFrameSize() { - return new Dimension(width, height); - } - - /** - * Gets the first (or only) image readData. - * - * @return BufferedImage containing first frame, or null if none. - */ - public BufferedImage getImage() { - return getFrame(0); - } - - /** - * Gets the image contents of layer n. May be smaller than full frame - * size - use getFrameOffset() to obtain position of subimage within - * main image area. - * - * @return BufferedImage representation of layer, or null if n is invalid. - */ - public BufferedImage getLayer(int n) { - BufferedImage im = null; - if ((n >= 0) && (n < nLayers)) { - im = frames[n]; - } - return im; - } - - /** - * Gets the subimage offset of layer n if it is smaller than the - * full frame size. - * - * @return Point indicating offset from upper left corner of frame. - */ - public Point getLayerOffset(int n) { - Point p = null; - if ((n >= 0) && (n < nLayers)) { - int x = layers[n].x; - int y = layers[n].y; - p = new Point(x, y); - } - if (p == null) { - p = new Point(0, 0); - } - return p; - } - - /** - * Reads PhotoShop layers from stream. - * - * @param InputStream in PhotoShop format. - * @return readData status code (0 = no errors) - */ - public int read(InputStream stream) { - setInput(stream); - process(); - return status; - } - - /** - * Reads PhotoShop file from specified source (file or URL string) - * - * @param name String containing source - * @return readData status code (0 = no errors) - */ - public int read(String name) { - setInput(name); - process(); - return status; - } - - /** - * Closes input stream and discards contents of all frames. - * - */ - public void reset() { - init(); - } - - protected void close() { - if (input != null) { - try { - input.close(); - } catch (Exception e) {} - input = null; - } - } - protected boolean err() { - return status != STATUS_OK; - } - - protected byte[] fillBytes(int size, int value) { - // create byte array filled with given value - byte[] b = new byte[size]; - if (value != 0) { - byte v = (byte) value; - for (int i = 0; i < size; i++) { - b[i] = v; - } - } - return b; - } - - protected void init() { - close(); - frameCount = 0; - frames = null; - layers = null; - hasLayers = true; - status = STATUS_OK; - } - - protected void makeDummyLayer() { - // creat dummy layer for non-layered image - rleEncoded = readShort() == 1; - hasLayers = false; - nLayers = 1; - layers = new LayerInfo[1]; - LayerInfo layer = new LayerInfo(); - layers[0] = layer; - layer.h = height; - layer.w = width; - int nc = Math.min(nChan, 4); - if (rleEncoded) { - // get list of rle encoded line lengths for all channels - readLineLengths(height * nc); - } - layer.nChan = nc; - layer.chanID = new int[nc]; - for (int i = 0; i < nc; i++) { - int id = i; - if (i == 3) id = -1; - layer.chanID[i] = id; - } - } - - protected void readLineLengths(int nLines) { - // readData list of rle encoded line lengths - lineLengths = new short[nLines]; - for (int i = 0; i < nLines; i++) { - lineLengths[i] = readShort(); - } - lineIndex = 0; - } - - protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) { - // create image from given plane data - BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); - int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData(); - int n = w * h; - int j = 0; - while (j < n) { - try { - int ac = a[j] & 0xff; - int rc = r[j] & 0xff; - int gc = g[j] & 0xff; - int bc = b[j] & 0xff; - data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc; - } catch (Exception e) {} - j++; - } - return im; - } - - protected void process() { - // decode PSD file - if (err()) return; - readHeader(); - if (err()) return; - readLayerInfo(); - if (err()) return; - if (nLayers == 0) { - makeDummyLayer(); - if (err()) return; - } - readLayers(); - } - - protected int readByte() { - // readData single byte from input - int curByte = 0; - try { - curByte = input.read(); - } catch (IOException e) { - status = STATUS_FORMAT_ERROR; - } - return curByte; - } - - protected int readBytes(byte[] bytes, int n) { - // readData multiple bytes from input - if (bytes == null) return 0; - int r = 0; - try { - r = input.read(bytes, 0, n); - } catch (IOException e) { - status = STATUS_FORMAT_ERROR; - } - if (r < n) { - status = STATUS_FORMAT_ERROR; - } - return r; - } - - protected void readHeader() { - // readData PSD header info - String sig = readString(4); - int ver = readShort(); - skipBytes(6); - nChan = readShort(); - height = readInt(); - width = readInt(); - int depth = readShort(); - int mode = readShort(); - int cmLen = readInt(); - skipBytes(cmLen); - int imResLen = readInt(); - skipBytes(imResLen); - - // require 8-bit RGB data - if ((!sig.equals("8BPS")) || (ver != 1)) { - status = STATUS_FORMAT_ERROR; - } else if ((depth != 8) || (mode != 3)) { - status = STATUS_UNSUPPORTED; - } - } - - protected int readInt() { - // readData big-endian 32-bit integer - return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8) - | readByte(); - } - - protected void readLayerInfo() { - // readData layer header info - miscLen = readInt(); - if (miscLen == 0) { - return; // no layers, only base image - } - int layerInfoLen = readInt(); - nLayers = readShort(); - if (nLayers > 0) { - layers = new LayerInfo[nLayers]; - } - for (int i = 0; i < nLayers; i++) { - LayerInfo info = new LayerInfo(); - layers[i] = info; - info.y = readInt(); - info.x = readInt(); - info.h = readInt() - info.y; - info.w = readInt() - info.x; - info.nChan = readShort(); - info.chanID = new int[info.nChan]; - for (int j = 0; j < info.nChan; j++) { - int id = readShort(); - int size = readInt(); - info.chanID[j] = id; - } - String s = readString(4); - if (!s.equals("8BIM")) { - status = STATUS_FORMAT_ERROR; - return; - } - skipBytes(4); // blend mode - info.alpha = readByte(); - int clipping = readByte(); - int flags = readByte(); - readByte(); // filler - int extraSize = readInt(); - skipBytes(extraSize); - } - } - - protected void readLayers() { - // readData and convert each layer to BufferedImage - frameCount = nLayers; - frames = new BufferedImage[nLayers]; - for (int i = 0; i < nLayers; i++) { - LayerInfo info = layers[i]; - byte[] r = null, g = null, b = null, a = null; - for (int j = 0; j < info.nChan; j++) { - int id = info.chanID[j]; - switch (id) { - case 0 : r = readPlane(info.w, info.h); break; - case 1 : g = readPlane(info.w, info.h); break; - case 2 : b = readPlane(info.w, info.h); break; - case -1 : a = readPlane(info.w, info.h); break; - default : readPlane(info.w, info.h); - } - if (err()) break; - } - if (err()) break; - int n = info.w * info.h; - if (r == null) r = fillBytes(n, 0); - if (g == null) g = fillBytes(n, 0); - if (b == null) b = fillBytes(n, 0); - if (a == null) a = fillBytes(n, 255); - - BufferedImage im = makeImage(info.w, info.h, r, g, b, a); - frames[i] = im; - } - lineLengths = null; - if ((miscLen > 0) && !err()) { - int n = readInt(); // global layer mask info len - skipBytes(n); - } - } - - protected byte[] readPlane(int w, int h) { - // readData a single color plane - byte[] b = null; - int size = w * h; - if (hasLayers) { - // get RLE compression info for channel - rleEncoded = readShort() == 1; - if (rleEncoded) { - // list of encoded line lengths - readLineLengths(h); - } - } - - if (rleEncoded) { - b = readPlaneCompressed(w, h); - } else { - b = new byte[size]; - readBytes(b, size); - } - - return b; - - } - - protected byte[] readPlaneCompressed(int w, int h) { - byte[] b = new byte[w * h]; - byte[] s = new byte[w * 2]; - int pos = 0; - for (int i = 0; i < h; i++) { - if (lineIndex >= lineLengths.length) { - status = STATUS_FORMAT_ERROR; - return null; - } - int len = lineLengths[lineIndex++]; - readBytes(s, len); - decodeRLE(s, 0, len, b, pos); - pos += w; - } - return b; - } - - protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) { - try { - int max = sindex + slen; - while (sindex < max) { - byte b = src[sindex++]; - int n = (int) b; - if (n < 0) { - // dup next byte 1-n times - n = 1 - n; - b = src[sindex++]; - for (int i = 0; i < n; i++) { - dst[dindex++] = b; - } - } else { - // copy next n+1 bytes - n = n + 1; - System.arraycopy(src, sindex, dst, dindex, n); - dindex += n; - sindex += n; - } - } - } catch (Exception e) { - status = STATUS_FORMAT_ERROR; - } - } - - protected short readShort() { - // readData big-endian 16-bit integer - return (short) ((readByte() << 8) | readByte()); - } - - protected String readString(int len) { - // readData string of specified length - String s = ""; - for (int i = 0; i < len; i++) { - s = s + (char) readByte(); - } - return s; - } - - protected void skipBytes(int n) { - // skip over n input bytes - for (int i = 0; i < n; i++) { - readByte(); - } - } -} From def1d47344bbc06068db9ef55eb41e162b6d1e6d Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 6 Nov 2009 02:26:47 +0100 Subject: [PATCH 02/21] Work in progress for PSD metadata support: - Added PSDMetadata and PSDMetadataFormat - Implemented most of standard format - Start of native format definintion - Updated SPI and Reader to return new format --- .../util/ImageReaderAbstractTestCase.java | 27 +- .../imageio/plugins/psd/PSDColorData.java | 4 +- .../imageio/plugins/psd/PSDDisplayInfo.java | 20 +- .../imageio/plugins/psd/PSDEXIF1Data.java | 2 +- .../imageio/plugins/psd/PSDImageReader.java | 107 ++++- .../plugins/psd/PSDImageReaderSpi.java | 8 +- .../imageio/plugins/psd/PSDMetadata.java | 446 ++++++++++++++++++ .../plugins/psd/PSDMetadataFormat.java | 165 +++++++ .../plugins/psd/PSDResolutionInfo.java | 13 +- 9 files changed, 748 insertions(+), 44 deletions(-) create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java diff --git a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java index 11be9e0d..601ff974 100644 --- a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java +++ b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java @@ -36,6 +36,7 @@ import org.jmock.core.Stub; import javax.imageio.*; import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; @@ -1320,7 +1321,9 @@ public abstract class ImageReaderAbstractTestCase extends assertEquals(type.getColorModel(), result.getColorModel()); -// assertEquals(type.getSampleModel(), result.getSampleModel()); + // The following logically tests + // assertEquals(type.getSampleModel(), result.getSampleModel()); + // but SampleModel does not have a proper equals method. SampleModel expectedModel = type.getSampleModel(); SampleModel resultModel = result.getSampleModel(); @@ -1335,10 +1338,6 @@ public abstract class ImageReaderAbstractTestCase extends } } -// public void testSetDestinationTypeIllegal() throws IOException { -// throw new UnsupportedOperationException("Method testSetDestinationTypeIllegal not implemented"); // TODO: Implement -// } -// // public void testSetDestinationBands() throws IOException { // throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement // } @@ -1347,6 +1346,24 @@ public abstract class ImageReaderAbstractTestCase extends // throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement // } + public void testProviderAndMetadataFormatNamesMatch() throws IOException { + ImageReaderSpi provider = createProvider(); + + ImageReader reader = createReader(); + reader.setInput(getTestData().get(0).getInputStream()); + + IIOMetadata imageMetadata = reader.getImageMetadata(0); + if (imageMetadata != null) { + assertEquals(provider.getNativeImageMetadataFormatName(), imageMetadata.getNativeMetadataFormatName()); + } + + IIOMetadata streamMetadata = reader.getStreamMetadata(); + if (streamMetadata != null) { + assertEquals(provider.getNativeStreamMetadataFormatName(), streamMetadata.getNativeMetadataFormatName()); + } + } + + protected URL getClassLoaderResource(final String pName) { return getClass().getResource(pName); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java index 2d64c5a8..d8e67cde 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java @@ -47,7 +47,7 @@ class PSDColorData { final byte[] mColors; private IndexColorModel mColorModel; - PSDColorData(ImageInputStream pInput) throws IOException { + PSDColorData(final ImageInputStream pInput) throws IOException { int length = pInput.readInt(); if (length == 0) { throw new IIOException("No palette information in PSD"); @@ -72,7 +72,7 @@ class PSDColorData { return mColorModel; } - private int[] toInterleavedRGB(byte[] pColors) { + private static int[] toInterleavedRGB(final byte[] pColors) { int[] rgb = new int[pColors.length / 3]; for (int i = 0; i < rgb.length; i++) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java index 6b04cf12..d4d41347 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java @@ -40,7 +40,7 @@ import java.io.IOException; * @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$ */ class PSDDisplayInfo extends PSDImageResource { - // TODO: Size of this struct should be 14.. Does not compute... + // TODO: Size of this struct should be 14.. Does not compute... Something bogus here //typedef _DisplayInfo //{ // WORD ColorSpace; @@ -67,20 +67,20 @@ class PSDDisplayInfo extends PSDImageResource { // long left = mSize; // while (left > 0) { - mColorSpace = pInput.readShort(); + mColorSpace = pInput.readShort(); - // Color[4]...? + // Color[4]...? mColors = new short[4]; - mColors[0] = pInput.readShort(); - mColors[1] = pInput.readShort(); - mColors[2] = pInput.readShort(); - mColors[3] = pInput.readShort(); + mColors[0] = pInput.readShort(); + mColors[1] = pInput.readShort(); + mColors[2] = pInput.readShort(); + mColors[3] = pInput.readShort(); - mOpacity = pInput.readShort(); + mOpacity = pInput.readShort(); - mKind = pInput.readByte(); + mKind = pInput.readByte(); - pInput.readByte(); // Pad + pInput.readByte(); // Pad // left -= 14; // } pInput.skipBytes(mSize - 14); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java index a54bb6a8..a2f6f486 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java @@ -35,7 +35,7 @@ final class PSDEXIF1Data extends PSDImageResource { protected void readData(final ImageInputStream pInput) throws IOException { // This is in essence an embedded TIFF file. // TODO: Extract TIFF parsing to more general purpose package - // TODO: Instead, read the byte data, store for later parsing + // TODO: Instead, read the byte data, store for later parsing (or store offset, and read on request) MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize)); byte[] bom = new byte[2]; diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 52680c9c..d2f05792 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -31,8 +31,15 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier; +import com.twelvemonkeys.xml.XMLSerializer; +import org.w3c.dom.Node; -import javax.imageio.*; +import javax.imageio.IIOException; +import javax.imageio.ImageIO; +import javax.imageio.ImageReadParam; +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -43,13 +50,11 @@ import java.awt.image.*; import java.io.DataInputStream; import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; +import java.util.*; import java.util.List; /** - * ImageReader for Adobe Photoshop Document format. + * ImageReader for Adobe Photoshop Document (PSD) format. * * @see Adobe Photoshop File Format Summary * @author Harald Kuhr @@ -57,7 +62,7 @@ import java.util.List; * @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$ */ // TODO: Implement ImageIO meta data interface -// TODO: Allow reading separate (or some?) layers +// TODO: API for reading separate layers // TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers // http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ // See http://www.codeproject.com/KB/graphics/PSDParser.aspx @@ -115,7 +120,7 @@ public class PSDImageReader extends ImageReaderBase { ); case PSD.COLOR_MODE_INDEXED: - // TODO: 16 bit indexed?! + // TODO: 16 bit indexed?! Does it exist? if (mHeader.mChannels == 1 && mHeader.mBits == 8) { return IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel()); } @@ -184,6 +189,11 @@ public class PSDImageReader extends ImageReaderBase { throw new IIOException( String.format("Unsupported channel count/bit depth for CMYK PSD: %d channels/%d bits", mHeader.mChannels, mHeader.mBits) ); + + case PSD.COLOR_MODE_MULTICHANNEL: + // TODO: Implement + case PSD.COLOR_MODE_LAB: + // TODO: Implement default: throw new IIOException( String.format("Unsupported PSD MODE: %s (%d channels/%d bits)", mHeader.mMode, mHeader.mChannels, mHeader.mBits) @@ -404,7 +414,7 @@ public class PSDImageReader extends ImageReaderBase { 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); break; default: - throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits); + throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits)); } if (abortRequested()) { @@ -536,6 +546,7 @@ public class PSDImageReader extends ImageReaderBase { } } + @SuppressWarnings({"UnusedDeclaration"}) private void read1bitChannel(final int pChannel, final int pChannelCount, final byte[] pData, final int pBands, final int pBandOffset, final ColorModel pSourceColorModel, @@ -697,6 +708,7 @@ public class PSDImageReader extends ImageReaderBase { mColorData = new PSDColorData(mImageInput); } 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); @@ -708,6 +720,7 @@ public class PSDImageReader extends ImageReaderBase { } // TODO: Flags or list of interesting resources to parse + // TODO: Obey ignoreMetadata private void readImageResources(final boolean pParseData) throws IOException { // TODO: Avoid unnecessary stream repositioning long pos = mImageInput.getFlushedPosition(); @@ -735,6 +748,8 @@ public class PSDImageReader extends ImageReaderBase { mImageInput.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(); @@ -767,7 +782,7 @@ public class PSDImageReader extends ImageReaderBase { // TODO: If not explicitly needed, skip layers... BufferedImage layer = readLayerData(layerInfo, raw, imageType); - // TODO: Don't show! Store in metadata somehow... + // TODO: Don't show! Store in meta data somehow... if (layer != null) { showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString()); } @@ -795,6 +810,7 @@ public class PSDImageReader extends ImageReaderBase { mImageInput.skipBytes(toSkip); } else { + // Skip entire layer and mask section mImageInput.skipBytes(length); } } @@ -838,7 +854,7 @@ public class PSDImageReader extends ImageReaderBase { } else { // 0 = red, 1 = green, etc - // ?1 = transparency mask; ?2 = user supplied layer mask + // -1 = transparency mask; -2 = user supplied layer mask int c = channelInfo.mChannelId == -1 ? pLayerInfo.mChannelInfo.length - 1 : channelInfo.mChannelId; // NOTE: For layers, byte counts are written per channel, while for the composite data @@ -892,7 +908,7 @@ public class PSDImageReader extends ImageReaderBase { read16bitChannel(c, imageType.getNumBands(), data16, interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE); break; default: - throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits); + throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits)); } if (abortRequested()) { @@ -931,6 +947,57 @@ public class PSDImageReader extends ImageReaderBase { return pOriginal; } + /// Layer support + // TODO: For now, leave as Metadata + + /* + int getNumLayers(int pImageIndex) throws IOException; + + boolean hasLayers(int pImageIndex) throws IOException; + + BufferedImage readLayer(int pImageIndex, int pLayerIndex, ImageReadParam pParam) throws IOException; + + int getLayerWidth(int pImageIndex, int pLayerIndex) throws IOException; + + int getLayerHeight(int pImageIndex, int pLayerIndex) throws IOException; + + // ? + Point getLayerOffset(int pImageIndex, int pLayerIndex) throws IOException; + + */ + + /// Metadata support + // TODO + + @Override + public IIOMetadata getStreamMetadata() throws IOException { + // null might be appropriate here + // "For image formats that contain a single image, only image metadata is used." + return super.getStreamMetadata(); + } + + @Override + public IIOMetadata getImageMetadata(final int pImageIndex) throws IOException { + // TODO: Implement + checkBounds(pImageIndex); + + readHeader(); + readImageResources(true); + readLayerAndMaskInfo(true); + + PSDMetadata metadata = new PSDMetadata(); + metadata.mHeader = mHeader; + metadata.mColorData = mColorData; + metadata.mImageResources = mImageResources; + return metadata; + } + + @Override + public IIOMetadata getImageMetadata(final int imageIndex, final String formatName, final Set nodeNames) throws IOException { + // TODO: This might make sense, as there's loads of meta data in the file + return super.getImageMetadata(imageIndex, formatName, nodeNames); + } + /// Thumbnail support @Override public boolean readerSupportsThumbnails() { @@ -965,13 +1032,13 @@ public class PSDImageReader extends ImageReaderBase { } @Override - public int getNumThumbnails(int pIndex) throws IOException { + public int getNumThumbnails(final int pIndex) throws IOException { List thumbnails = getThumbnailResources(pIndex); return thumbnails == null ? 0 : thumbnails.size(); } - private PSDThumbnail getThumbnailResource(int pImageIndex, int pThumbnailIndex) throws IOException { + private PSDThumbnail getThumbnailResource(final int pImageIndex, final int pThumbnailIndex) throws IOException { List thumbnails = getThumbnailResources(pImageIndex); if (thumbnails == null) { @@ -982,17 +1049,17 @@ public class PSDImageReader extends ImageReaderBase { } @Override - public int getThumbnailWidth(int pImageIndex, int pThumbnailIndex) throws IOException { + public int getThumbnailWidth(final int pImageIndex, final int pThumbnailIndex) throws IOException { return getThumbnailResource(pImageIndex, pThumbnailIndex).getWidth(); } @Override - public int getThumbnailHeight(int pImageIndex, int pThumbnailIndex) throws IOException { + public int getThumbnailHeight(final int pImageIndex, final int pThumbnailIndex) throws IOException { return getThumbnailResource(pImageIndex, pThumbnailIndex).getHeight(); } @Override - public BufferedImage readThumbnail(int pImageIndex, int pThumbnailIndex) throws IOException { + public BufferedImage readThumbnail(final int pImageIndex, final int pThumbnailIndex) throws IOException { // TODO: Thumbnail progress listeners... PSDThumbnail thumbnail = getThumbnailResource(pImageIndex, pThumbnailIndex); @@ -1001,6 +1068,7 @@ public class PSDImageReader extends ImageReaderBase { processThumbnailStarted(pImageIndex, pThumbnailIndex); processThumbnailComplete(); + // TODO: Returning a cached mutable thumbnail is not really safe... return thumbnail.getThumbnail(); } @@ -1052,12 +1120,17 @@ public class PSDImageReader extends ImageReaderBase { // System.out.println("imageReader.mHeader: " + imageReader.mHeader); imageReader.readImageResources(true); -// System.out.println("imageReader.mImageResources: " + imageReader.mImageResources); + System.out.println("imageReader.mImageResources: " + imageReader.mImageResources); imageReader.readLayerAndMaskInfo(true); System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo); // System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask); + IIOMetadata metadata = imageReader.getImageMetadata(0); + Node node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + XMLSerializer serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); + serializer.serialize(node, true); + if (imageReader.hasThumbnails(0)) { int thumbnails = imageReader.getNumThumbnails(0); for (int i = 0; i < thumbnails; i++) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java index 5a27b1c5..0b63af8d 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java @@ -67,8 +67,12 @@ public class PSDImageReaderSpi extends ImageReaderSpi { STANDARD_INPUT_TYPE, // new String[]{"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"}, null, - true, null, null, null, null, - true, null, null, null, null + true, // supports standard stream metadata + null, null, // native stream format name and class + null, null, // extra stream formats + true, // supports standard image metadata + PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, + null, null // extra image metadata formats ); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java new file mode 100644 index 00000000..a9755fad --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -0,0 +1,446 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import com.twelvemonkeys.lang.StringUtil; +import org.w3c.dom.Node; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.metadata.IIOMetadataNode; +import java.awt.image.IndexColorModel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * PSDMetadata + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDMetadata.java,v 1.0 Nov 4, 2009 5:28:12 PM haraldk Exp$ + */ +public final class PSDMetadata extends IIOMetadata implements Cloneable { + + static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_1.0"; + static final String NATIVE_METADATA_FORMAT_CLASS_NAME = "com.twelvemonkeys.imageio.plugins.psd.PSDMetadataFormat"; + + // TODO: Move fields from PSDImageReader (header, color map, resources, etc) here + PSDHeader mHeader; + PSDColorData mColorData; + List mImageResources; + PSDGlobalLayerMask mGlobalLayerMask; + List mLayerInfo; + + protected PSDMetadata() { + // TODO: Allow XMP, EXIF and IPTC as extra formats? + super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); + } + + @Override + public boolean isReadOnly() { + // TODO: Extract to abstract metadata impl class? + return true; + } + + @Override + public Node getAsTree(final String pFormatName) { + validateFormatName(pFormatName); + + if (pFormatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } + else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } + + throw new AssertionError("Unreachable"); + } + + @Override + public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException { + // TODO: Extract to abstract metadata impl class? + assertMutable(); + + validateFormatName(pFormatName); + + if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) { + throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot); + } + + Node node = pRoot.getFirstChild(); + while (node != null) { + // TODO: Merge values from node into this + + // Move to the next sibling + node = node.getNextSibling(); + } + } + + @Override + public void reset() { + // TODO: Extract to abstract metadata impl class? + assertMutable(); + + throw new UnsupportedOperationException("Method reset not implemented"); // TODO: Implement + } + + // TODO: Extract to abstract metadata impl class? + private void assertMutable() { + if (isReadOnly()) { + throw new IllegalStateException("Metadata is read-only"); + } + } + + // TODO: Extract to abstract metadata impl class? + private void validateFormatName(final String pFormatName) { + String[] metadataFormatNames = getMetadataFormatNames(); + + if (metadataFormatNames != null) { + for (String metadataFormatName : metadataFormatNames) { + if (metadataFormatName.equals(pFormatName)) { + return; // Found, we're ok! + } + } + } + + throw new IllegalArgumentException( + String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames)) + ); + } + + @Override + public Object clone() { + // TODO: Make it a deep clone + try { + return super.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + private Node getNativeTree() { + throw new UnsupportedOperationException("getNativeTree"); + } + + /// Standard format support + + @Override + protected IIOMetadataNode getStandardChromaNode() { + IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); + IIOMetadataNode node; // scratch node + + node = new IIOMetadataNode("ColorSpaceType"); + String cs; + switch (mHeader.mMode) { + case PSD.COLOR_MODE_MONOCHROME: + case PSD.COLOR_MODE_GRAYSCALE: + case PSD.COLOR_MODE_DUOTONE: // Rationale is spec says treat as gray... + cs = "GRAY"; + break; + case PSD.COLOR_MODE_RGB: + case PSD.COLOR_MODE_INDEXED: + cs = "RGB"; + break; + case PSD.COLOR_MODE_CMYK: + cs = "CMYK"; + break; + case PSD.COLOR_MODE_MULTICHANNEL: + // TODO: FixMe + cs = "???"; + break; + case PSD.COLOR_MODE_LAB: + cs = "Lab"; + break; + default: + throw new AssertionError("Unreachable"); + } + node.setAttribute("name", cs); + chroma_node.appendChild(node); + + // TODO: Channels might be 5 for RGB + A + Mask... + node = new IIOMetadataNode("NumChannels"); + node.setAttribute("value", Integer.toString(mHeader.mChannels)); + chroma_node.appendChild(node); + +// if (gAMA_present) { +// node = new IIOMetadataNode("Gamma"); +// node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); +// chroma_node.appendChild(node); +// } + + // TODO: Check if this is correct with bitmap (monchrome) + node = new IIOMetadataNode("BlackIsZero"); + node.setAttribute("value", "true"); + chroma_node.appendChild(node); + + if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { + node = new IIOMetadataNode("Palette"); + + IndexColorModel cm = mColorData.getIndexColorModel(); + for (int i = 0; i < cm.getMapSize(); i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(cm.getRed(i))); + entry.setAttribute("green", + Integer.toString(cm.getGreen(i))); + entry.setAttribute("blue", + Integer.toString(cm.getBlue(i))); + + node.appendChild(entry); + } + chroma_node.appendChild(node); + } + +// if (bKGD_present) { +// if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { +// node = new IIOMetadataNode("BackgroundIndex"); +// node.setAttribute("value", Integer.toString(bKGD_index)); +// } else { +// node = new IIOMetadataNode("BackgroundColor"); +// int r, g, b; +// +// if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { +// r = g = b = bKGD_gray; +// } else { +// r = bKGD_red; +// g = bKGD_green; +// b = bKGD_blue; +// } +// node.setAttribute("red", Integer.toString(r)); +// node.setAttribute("green", Integer.toString(g)); +// node.setAttribute("blue", Integer.toString(b)); +// } +// chroma_node.appendChild(node); +// } + + return chroma_node; + } + + @Override + protected IIOMetadataNode getStandardCompressionNode() { + IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode node; // scratch node + + node = new IIOMetadataNode("CompressionTypeName"); + // TODO: Only if set... + node.setAttribute("value", "PackBits"); + compression_node.appendChild(node); + + node = new IIOMetadataNode("Lossless"); + node.setAttribute("value", "true"); + compression_node.appendChild(node); + +// compression_node.appendChild(node); + + return compression_node; + } + + @Override + protected IIOMetadataNode getStandardDataNode() { + IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode node; // scratch node + + node = new IIOMetadataNode("PlanarConfiguration"); + node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec + data_node.appendChild(node); + + node = new IIOMetadataNode("SampleFormat"); + node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral"); + data_node.appendChild(node); + + String bitDepth = Integer.toString(mHeader.mBits); // bits per plane + // TODO: Channels might be 5 for RGB + A + Mask... + String[] bps = new String[mHeader.mChannels]; + Arrays.fill(bps, bitDepth); + + node = new IIOMetadataNode("BitsPerSample"); + node.setAttribute("value", StringUtil.toCSVString(bps, " ")); + data_node.appendChild(node); + + // TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed? + + return data_node; + } + + @Override + protected IIOMetadataNode getStandardDimensionNode() { + IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode node; // scratch node + + node = new IIOMetadataNode("PixelAspectRatio"); + // TODO: This is not incorrect wrt resolution info + float ratio = 1f; + node.setAttribute("value", Float.toString(ratio)); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("ImageOrientation"); + node.setAttribute("value", "Normal"); + dimension_node.appendChild(node); + + List resolutionInfos = getResources(PSDResolutionInfo.class); + if (!resolutionInfos.isEmpty()) { + PSDResolutionInfo resolutionInfo = resolutionInfos.get(0); + + node = new IIOMetadataNode("HorizontalPixelSize"); + node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes))); + dimension_node.appendChild(node); + + node = new IIOMetadataNode("VerticalPixelSize"); + node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes))); + dimension_node.appendChild(node); + } + + // TODO: + /* + + + + + + + + + + + + + + + + + + + + + */ + return dimension_node; + } + + private static float asMM(final short pUnit, final float pResolution) { + // Unit: 1 -> pixels per inch, 2 -> pixels pr cm + return (pUnit == 1 ? 25.4f : 10) / pResolution; + } + + @Override + protected IIOMetadataNode getStandardDocumentNode() { + // TODO: PSDVersionInfo + +// if (!tIME_present) { +// return null; +// } +// +// IIOMetadataNode document_node = new IIOMetadataNode("Document"); +// IIOMetadataNode node = null; // scratch node +// +// node = new IIOMetadataNode("ImageModificationTime"); +// node.setAttribute("year", Integer.toString(tIME_year)); +// node.setAttribute("month", Integer.toString(tIME_month)); +// node.setAttribute("day", Integer.toString(tIME_day)); +// node.setAttribute("hour", Integer.toString(tIME_hour)); +// node.setAttribute("minute", Integer.toString(tIME_minute)); +// node.setAttribute("second", Integer.toString(tIME_second)); +// document_node.appendChild(node); +// +// return document_node; + return null; + } + + @Override + protected IIOMetadataNode getStandardTextNode() { + // TODO: CaptionDigest?, EXIF, XMP + +// int numEntries = tEXt_keyword.size() + +// iTXt_keyword.size() + zTXt_keyword.size(); +// if (numEntries == 0) { +// return null; +// } +// +// IIOMetadataNode text_node = new IIOMetadataNode("Text"); +// IIOMetadataNode node = null; // scratch node +// +// for (int i = 0; i < tEXt_keyword.size(); i++) { +// node = new IIOMetadataNode("TextEntry"); +// node.setAttribute("keyword", (String)tEXt_keyword.get(i)); +// node.setAttribute("value", (String)tEXt_text.get(i)); +// node.setAttribute("encoding", "ISO-8859-1"); +// node.setAttribute("compression", "none"); +// +// text_node.appendChild(node); +// } +// +// for (int i = 0; i < iTXt_keyword.size(); i++) { +// node = new IIOMetadataNode("TextEntry"); +// node.setAttribute("keyword", iTXt_keyword.get(i)); +// node.setAttribute("value", iTXt_text.get(i)); +// node.setAttribute("language", +// iTXt_languageTag.get(i)); +// if (iTXt_compressionFlag.get(i)) { +// node.setAttribute("compression", "deflate"); +// } else { +// node.setAttribute("compression", "none"); +// } +// +// text_node.appendChild(node); +// } +// +// for (int i = 0; i < zTXt_keyword.size(); i++) { +// node = new IIOMetadataNode("TextEntry"); +// node.setAttribute("keyword", (String)zTXt_keyword.get(i)); +// node.setAttribute("value", (String)zTXt_text.get(i)); +// node.setAttribute("compression", "deflate"); +// +// text_node.appendChild(node); +// } +// +// return text_node; + return null; + + } + + @Override + protected IIOMetadataNode getStandardTileNode() { + return super.getStandardTileNode(); + } + + @Override + protected IIOMetadataNode getStandardTransparencyNode() { + IIOMetadataNode transparency_node = + new IIOMetadataNode("Transparency"); + IIOMetadataNode node; // scratch node + + node = new IIOMetadataNode("Alpha"); + node.setAttribute("value", hasAlpha() ? "nonpremultipled" : "none"); // TODO: Check spec + transparency_node.appendChild(node); + + return transparency_node; + } + + private boolean hasAlpha() { + return mHeader.mMode == PSD.COLOR_MODE_RGB && mHeader.mChannels >= 4 || + mHeader.mMode == PSD.COLOR_MODE_CMYK & mHeader.mChannels >= 5; + } + + // TODO: Replace with filter iterator? + List getResources(final Class pResourceType) { + List filtered = null; + + for (PSDImageResource resource : mImageResources) { + if (pResourceType.isInstance(resource)) { + if (filtered == null) { + filtered = new ArrayList(); + } + + filtered.add(pResourceType.cast(resource)); + } + } + + return filtered; + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java new file mode 100644 index 00000000..072748ce --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -0,0 +1,165 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import javax.imageio.ImageTypeSpecifier; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import java.util.Arrays; + +/** + * PSDMetadataFormat + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDMetadataFormat.java,v 1.0 Nov 4, 2009 5:27:53 PM haraldk Exp$ + */ +public final class PSDMetadataFormat extends IIOMetadataFormatImpl { + + private final static PSDMetadataFormat sInstance = new PSDMetadataFormat(); + + /** + * Private constructor. + *

+ * The {@link javax.imageio.metadata.IIOMetadata} class will instantiate this class + * by reflection, invoking the static {@code getInstance()} method. + * + * @see javax.imageio.metadata.IIOMetadata#getMetadataFormat + * @see #getInstance() + */ + private PSDMetadataFormat() { + // Defines the root element + super(PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME); + + // root -> PSDHeader + // TODO: How do I specify that the header is required? + addElement("PSDHeader", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); + + // TODO: Do the first two make sense? + addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); + addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1")); + + addAttribute("PSDHeader", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); + // rows? + addAttribute("PSDHeader", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true); + // columns? + addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); + addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); + addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList( + String.valueOf(PSD.COLOR_MODE_MONOCHROME), + String.valueOf(PSD.COLOR_MODE_GRAYSCALE), + String.valueOf(PSD.COLOR_MODE_INDEXED), + String.valueOf(PSD.COLOR_MODE_RGB), + String.valueOf(PSD.COLOR_MODE_CMYK), + String.valueOf(PSD.COLOR_MODE_MULTICHANNEL), + String.valueOf(PSD.COLOR_MODE_DUOTONE), + String.valueOf(PSD.COLOR_MODE_LAB) + )); + + /* + Contains the required data to define the color mode. + + For indexed color images, the count will be equal to 768, and the mode data + will contain the color table for the image, in non-interleaved order. + + For duotone images, the mode data will contain the duotone specification, + the format of which is not documented. Non-Photoshop readers can treat + the duotone image as a grayscale image, and keep the duotone specification + around as a black box for use when saving the file. + */ + // root -> Palette + // Color map for indexed, optional + // NOTE: Palette, PaletteEntry naming taken from the standard format, native PSD naming is ColorModeData + // NOTE: PSD stores these as 256 Red, 256 Green, 256 Blue.. Should we do the same in the meta data? + addElement("Palette", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, 256, 256); // 768 = 256 * 3 + addElement("PaletteEntry", "PSDColorData", CHILD_POLICY_EMPTY); + addAttribute("PaletteEntry", "index", DATATYPE_INTEGER, true, null, "0", "255", true, true); + addAttribute("PaletteEntry", "red", DATATYPE_INTEGER, true, null, "0", "255", true, true); + addAttribute("PaletteEntry", "green", DATATYPE_INTEGER, true, null, "0", "255", true, true); + addAttribute("PaletteEntry", "blue", DATATYPE_INTEGER, true, null, "0", "255", true, true); + // No alpha allowed in indexed color PSD + + // TODO: Duotone spec, optional (use same element as palette?) + // Or use object or raw bytes.. + + // root -> ImageResources + // Image resources, optional + addElement("ImageResources", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_SEQUENCE); // SOME? + + // root -> ImageResources -> ImageResource + // Generic resource + addElement("ImageResource", "ImageResources", CHILD_POLICY_ALL); + // TODO: Allow arbitrary values to be added as a generic resource... + + // root -> ImageResources -> AlphaChannelInfo + addElement("AlphaChannelInfo", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("AlphaChannelInfo", "names", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); + + // root -> ImageResources -> DisplayInfo + addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null); + addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4); + addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true); + addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList("0", "1")); + + // root -> ImageResources -> EXIF1Data + addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL); + // TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?) + + // root -> ImageResources -> PrintFlags + addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY); + addBooleanAttribute("PrintFlags", "labels", false, false); + addBooleanAttribute("PrintFlags", "cropMasks", false, false); + addBooleanAttribute("PrintFlags", "colorBars", false, false); + addBooleanAttribute("PrintFlags", "registrationMarks", false, false); + addBooleanAttribute("PrintFlags", "negative", false, false); + addBooleanAttribute("PrintFlags", "flip", false, false); + addBooleanAttribute("PrintFlags", "interpolate", false, false); + addBooleanAttribute("PrintFlags", "caption", false, false); + + // root -> ImageResources -> PrintFlagsInformation + addElement("PrintFlagsInformation", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, true, null); + addBooleanAttribute("PrintFlagsInformation", "cropMarks", false, false); + addAttribute("PrintFlagsInformation", "field", DATATYPE_INTEGER, true, null); + addAttribute("PrintFlagsInformation", "bleedWidth", DATATYPE_INTEGER, true, null, "0", String.valueOf(Long.MAX_VALUE), true, true); // TODO: LONG??! + addAttribute("PrintFlagsInformation", "bleedScale", DATATYPE_INTEGER, true, null, "0", String.valueOf(Integer.MAX_VALUE), true, true); + + // root -> ImageResources -> ResolutionInfo + addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null); + // TODO: Or use string and more friendly names? "pixels/inch"/"pixels/cm" and "inch"/"cm"/"pt"/"pica"/"column" + addAttribute("ResolutionInfo", "hResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); + addAttribute("ResolutionInfo", "widthUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5")); + addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null); + // TODO: Or use more friendly names? + addAttribute("ResolutionInfo", "vResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); + addAttribute("ResolutionInfo", "heightUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5")); + + // ??? addElement("Thumbnail", "ImageResources", CHILD_POLICY_CHOICE); + + // root -> ImageResources -> XMPData + addElement("XMPData", "ImageResources", CHILD_POLICY_CHOICE); + // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) + + // TODO: Layers + //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); + + // TODO: Global layer mask info + } + + + + @Override + public boolean canNodeAppear(final String pElementName, final ImageTypeSpecifier pImageType) { + // TODO: PSDColorData and PaletteEntry only for indexed color model + throw new UnsupportedOperationException("Method canNodeAppear not implemented"); // TODO: Implement + } + + /** + * Returns the shared instance of the {@code PSDMetadataFormat}. + * + * @return the shared instance. + * @see javax.imageio.metadata.IIOMetadata#getMetadataFormat + */ + public static PSDMetadataFormat getInstance() { + return sInstance; + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java index 43102599..629f3a9c 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java @@ -50,13 +50,12 @@ class PSDResolutionInfo extends PSDImageResource { // WORD HeightUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */ // } RESOLUTIONINFO; - private float mHRes; - private short mHResUnit; - private short mWidthUnit; - private float mVRes; - private short mVResUnit; - private short mHeightUnit; - + float mHRes; + short mHResUnit; + short mWidthUnit; + float mVRes; + short mVResUnit; + short mHeightUnit; PSDResolutionInfo(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); From bf5c6e9d471b54584c9edd60719b2c9754d15919 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Fri, 6 Nov 2009 23:54:48 +0100 Subject: [PATCH 03/21] Work in progress for PSD metadata support: - Implemented more of standard support - Changes to native format spec - Implemented more of native format - Minor changes in various resources due to meta data implementation --- .../imageio/plugins/psd/PSD.java | 2 +- .../plugins/psd/PSDAlphaChannelInfo.java | 4 +- .../imageio/plugins/psd/PSDDisplayInfo.java | 25 +- .../imageio/plugins/psd/PSDEXIF1Data.java | 21 +- .../imageio/plugins/psd/PSDHeader.java | 5 +- .../imageio/plugins/psd/PSDImageReader.java | 82 +++-- .../imageio/plugins/psd/PSDImageResource.java | 23 +- .../imageio/plugins/psd/PSDLayerInfo.java | 12 +- .../imageio/plugins/psd/PSDMetadata.java | 303 ++++++++++++++---- .../plugins/psd/PSDMetadataFormat.java | 29 +- .../imageio/plugins/psd/PSDPrintFlags.java | 16 +- .../plugins/psd/PSDPrintFlagsInformation.java | 2 +- .../imageio/plugins/psd/PSDThumbnail.java | 2 +- .../imageio/plugins/psd/PSDUtil.java | 23 +- .../imageio/plugins/psd/PSDVersionInfo.java | 57 ++++ 15 files changed, 447 insertions(+), 159 deletions(-) create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java index 30c77218..55a3ba45 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java @@ -101,7 +101,7 @@ interface PSD { int COMPRESSION_ZIP = 2; /** ZIP compression with prediction */ - int COMPRESSION_ZIP_PREDICTON = 3; + int COMPRESSION_ZIP_PREDICTION = 3; // Color Modes /** Bitmap (monochrome) */ diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java index 7d3ae079..ed0b4edd 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java @@ -48,11 +48,11 @@ class PSDAlphaChannelInfo extends PSDImageResource { } @Override - protected void readData(ImageInputStream pInput) throws IOException { + protected void readData(final ImageInputStream pInput) throws IOException { mNames = new ArrayList(); long left = mSize; while (left > 0) { - String name = PSDUtil.readPascalStringByte(pInput); + String name = PSDUtil.readPascalString(pInput); mNames.add(name); left -= name.length() + 1; } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java index d4d41347..f74e5a7b 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java @@ -40,7 +40,22 @@ import java.io.IOException; * @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$ */ class PSDDisplayInfo extends PSDImageResource { - // TODO: Size of this struct should be 14.. Does not compute... Something bogus here + // TODO: Size of this struct should be 14.. Does not compute... Something bogus here + + // ColorSpace definitions: + // PSD_CS_RGB = 0, /* RGB */ + // PSD_CS_HSB = 1, /* Hue, Saturation, Brightness */ + // PSD_CS_CMYK = 2, /* CMYK */ + // PSD_CS_PANTONE = 3, /* Pantone matching system (Lab)*/ + // PSD_CS_FOCOLTONE = 4, /* Focoltone colour system (CMYK)*/ + // PSD_CS_TRUMATCH = 5, /* Trumatch color (CMYK)*/ + // PSD_CS_TOYO = 6, /* Toyo 88 colorfinder 1050 (Lab)*/ + // PSD_CS_LAB = 7, /* L*a*b*/ + // PSD_CS_GRAYSCALE = 8, /* Grey scale */ + // PSD_CS_HKS = 10, /* HKS colors (CMYK)*/ + // PSD_CS_DIC = 11, /* DIC color guide (Lab)*/ + // PSD_CS_ANPA = 3000, /* Anpa color (Lab)*/ + //typedef _DisplayInfo //{ // WORD ColorSpace; @@ -50,10 +65,10 @@ class PSDDisplayInfo extends PSDImageResource { // BYTE Padding; /* Always zero */ //} DISPLAYINFO; - private int mColorSpace; - private short[] mColors; - private short mOpacity; - private byte mKind; + int mColorSpace; + short[] mColors; + short mOpacity; + byte mKind; PSDDisplayInfo(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java index a2f6f486..17f181aa 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; /** @@ -67,8 +68,8 @@ final class PSDEXIF1Data extends PSDImageResource { } // TIFF Image file directory (IFD) - private static class Directory { - List mEntries = new ArrayList(); + static class Directory implements Iterable { + private List mEntries = new ArrayList(); private Directory() {} @@ -90,6 +91,20 @@ final class PSDEXIF1Data extends PSDImageResource { return directory; } + public Entry get(int pTag) { + for (Entry entry : mEntries) { + if (entry.mTag == pTag) { + return entry; + } + } + + return null; + } + + public Iterator iterator() { + return mEntries.iterator(); + } + @Override public String toString() { return String.format("Directory%s", mEntries); @@ -97,7 +112,7 @@ final class PSDEXIF1Data extends PSDImageResource { } // TIFF IFD Entry - private static class Entry { + static class Entry { private static final int EXIF_IFD = 0x8769; private final static String[] TYPE_NAMES = { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java index 6f26d147..77e6659a 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java @@ -28,6 +28,9 @@ package com.twelvemonkeys.imageio.plugins.psd; +import org.w3c.dom.Node; + +import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; import javax.imageio.IIOException; import java.io.IOException; @@ -61,7 +64,7 @@ class PSDHeader { final short mBits; final short mMode; - PSDHeader(ImageInputStream pInput) throws IOException { + PSDHeader(final ImageInputStream pInput) throws IOException { int signature = pInput.readInt(); if (signature != PSD.SIGNATURE_8BPS) { throw new IIOException("Not a PSD document, expected signature \"8BPS\": \"" + PSDUtil.intToStr(signature) + "\" (0x" + Integer.toHexString(signature) + ")"); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index d2f05792..5eb7112e 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -69,11 +69,12 @@ import java.util.List; // See http://www.adobeforums.com/webx?14@@.3bc381dc/0 public class PSDImageReader extends ImageReaderBase { private PSDHeader mHeader; - private PSDColorData mColorData; - private List mImageResources; - private PSDGlobalLayerMask mGlobalLayerMask; - private List mLayerInfo; +// private PSDColorData mColorData; +// private List mImageResources; +// private PSDGlobalLayerMask mGlobalLayerMask; +// private List mLayerInfo; private ICC_ColorSpace mColorSpace; + protected PSDMetadata mMetadata; protected PSDImageReader(final ImageReaderSpi pOriginatingProvider) { super(pOriginatingProvider); @@ -81,8 +82,9 @@ public class PSDImageReader extends ImageReaderBase { protected void resetMembers() { mHeader = null; - mColorData = null; - mImageResources = null; +// mColorData = null; +// mImageResources = null; + mMetadata = null; mColorSpace = null; } @@ -122,7 +124,7 @@ public class PSDImageReader extends ImageReaderBase { case PSD.COLOR_MODE_INDEXED: // TODO: 16 bit indexed?! Does it exist? if (mHeader.mChannels == 1 && mHeader.mBits == 8) { - return IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel()); + return IndexedImageTypeSpecifier.createFromIndexColorModel(mMetadata.mColorData.getIndexColorModel()); } throw new IIOException( @@ -264,7 +266,7 @@ public class PSDImageReader extends ImageReaderBase { if (mColorSpace == null) { ICC_Profile profile = null; - for (PSDImageResource resource : mImageResources) { + for (PSDImageResource resource : mMetadata.mImageResources) { if (resource instanceof ICCProfile) { profile = ((ICCProfile) resource).getProfile(); break; @@ -333,6 +335,8 @@ public class PSDImageReader extends ImageReaderBase { int[] byteCounts = null; int compression = mImageInput.readShort(); + // TODO: Need to make sure compression is set in metadata, even without reading the image data! + mMetadata.mCompression = compression; switch (compression) { case PSD.COMPRESSION_NONE: @@ -346,7 +350,7 @@ public class PSDImageReader extends ImageReaderBase { break; case PSD.COMPRESSION_ZIP: // TODO: Could probably use the ZIPDecoder (DeflateDecoder) here.. - case PSD.COMPRESSION_ZIP_PREDICTON: + case PSD.COMPRESSION_ZIP_PREDICTION: // TODO: Need to find out if the normal java.util.zip can handle this... // Could be same as PNG prediction? Read up... throw new IIOException("ZIP compression not supported yet"); @@ -693,6 +697,9 @@ public class PSDImageReader extends ImageReaderBase { if (mHeader == null) { mHeader = new PSDHeader(mImageInput); + mMetadata = new PSDMetadata(); + mMetadata.mHeader = mHeader; + /* Contains the required data to define the color mode. @@ -705,7 +712,7 @@ public class PSDImageReader extends ImageReaderBase { around as a black box for use when saving the file. */ if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { - mColorData = new PSDColorData(mImageInput); + mMetadata.mColorData = new PSDColorData(mImageInput); } else { // TODO: We need to store the duotone spec if we decide to create a writer... @@ -729,14 +736,14 @@ public class PSDImageReader extends ImageReaderBase { long length = mImageInput.readUnsignedInt(); if (pParseData && length > 0) { - if (mImageResources == null) { - mImageResources = new ArrayList(); + if (mMetadata.mImageResources == null) { + mMetadata.mImageResources = new ArrayList(); long expectedEnd = mImageInput.getStreamPosition() + length; while (mImageInput.getStreamPosition() < expectedEnd) { // TODO: Have PSDImageResources defer actual parsing? (Just store stream offsets) PSDImageResource resource = PSDImageResource.read(mImageInput); - mImageResources.add(resource); + mMetadata.mImageResources.add(resource); } if (mImageInput.getStreamPosition() != expectedEnd) { @@ -770,7 +777,7 @@ public class PSDImageReader extends ImageReaderBase { for (int i = 0; i < layerInfos.length; i++) { layerInfos[i] = new PSDLayerInfo(mImageInput); } - mLayerInfo = Arrays.asList(layerInfos); + mMetadata.mLayerInfo = Arrays.asList(layerInfos); // TODO: Clean-up mImageInput.mark(); @@ -783,9 +790,9 @@ public class PSDImageReader extends ImageReaderBase { BufferedImage layer = readLayerData(layerInfo, raw, imageType); // TODO: Don't show! Store in meta data somehow... - if (layer != null) { - showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString()); - } +// if (layer != null) { +// showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString()); +// } } long read = mImageInput.getStreamPosition() - pos; @@ -799,7 +806,7 @@ public class PSDImageReader extends ImageReaderBase { long layerMaskInfoLength = mImageInput.readUnsignedInt(); // System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength); if (layerMaskInfoLength > 0) { - mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput); + mMetadata.mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput); // System.out.println("mGlobalLayerMask: " + mGlobalLayerMask); } @@ -877,7 +884,7 @@ public class PSDImageReader extends ImageReaderBase { break; case PSD.COMPRESSION_ZIP: - case PSD.COMPRESSION_ZIP_PREDICTON: + case PSD.COMPRESSION_ZIP_PREDICTION: default: // Explicitly skipped above throw new AssertionError(String.format("Unsupported layer data. Compression: %d", compression)); @@ -985,16 +992,19 @@ public class PSDImageReader extends ImageReaderBase { readImageResources(true); readLayerAndMaskInfo(true); - PSDMetadata metadata = new PSDMetadata(); - metadata.mHeader = mHeader; - metadata.mColorData = mColorData; - metadata.mImageResources = mImageResources; - return metadata; + // TODO: Need to make sure compression is set in metadata, even without reading the image data! + mMetadata.mCompression = mImageInput.readShort(); + +// mMetadata.mHeader = mHeader; +// mMetadata.mColorData = mColorData; +// mMetadata.mImageResources = mImageResources; + + return mMetadata; // TODO: clone if we change to mutable metadata } @Override public IIOMetadata getImageMetadata(final int imageIndex, final String formatName, final Set nodeNames) throws IOException { - // TODO: This might make sense, as there's loads of meta data in the file + // TODO: It might make sense to overload this, as there's loads of meta data in the file return super.getImageMetadata(imageIndex, formatName, nodeNames); } @@ -1011,14 +1021,14 @@ public class PSDImageReader extends ImageReaderBase { List thumbnails = null; - if (mImageResources == null) { + if (mMetadata.mImageResources == null) { // TODO: Need flag here, to specify what resources to read... readImageResources(true); // TODO: Skip this, requires storing some stream offsets readLayerAndMaskInfo(false); } - for (PSDImageResource resource : mImageResources) { + for (PSDImageResource resource : mMetadata.mImageResources) { if (resource instanceof PSDThumbnail) { if (thumbnails == null) { thumbnails = new ArrayList(); @@ -1120,15 +1130,25 @@ public class PSDImageReader extends ImageReaderBase { // System.out.println("imageReader.mHeader: " + imageReader.mHeader); imageReader.readImageResources(true); - System.out.println("imageReader.mImageResources: " + imageReader.mImageResources); + System.out.println("imageReader.mImageResources: " + imageReader.mMetadata.mImageResources); + System.out.println(); imageReader.readLayerAndMaskInfo(true); - System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo); + System.out.println("imageReader.mLayerInfo: " + imageReader.mMetadata.mLayerInfo); // System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask); + System.out.println(); IIOMetadata metadata = imageReader.getImageMetadata(0); - Node node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); - XMLSerializer serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); + Node node; + XMLSerializer serializer; + + node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); + serializer.serialize(node, true); + System.out.println(); + + node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME); + serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); serializer.serialize(node, true); if (imageReader.hasThumbnails(0)) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java index 5aa95625..8dd3770a 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java @@ -28,6 +28,8 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.lang.StringUtil; + import javax.imageio.stream.ImageInputStream; import javax.imageio.IIOException; import java.io.IOException; @@ -50,6 +52,12 @@ class PSDImageResource { mName = PSDUtil.readPascalString(pInput); + // Skip pad + int nameSize = mName.length() + 1; + if (nameSize % 2 != 0) { + pInput.readByte(); + } + mSize = pInput.readUnsignedInt(); readData(pInput); @@ -84,7 +92,10 @@ class PSDImageResource { protected StringBuilder toStringBuilder() { StringBuilder builder = new StringBuilder(getClass().getSimpleName()); - builder.append(resourceTypeForId(mId)); + String fakeType = resourceTypeForId(mId); + if (fakeType != null) { + builder.append("(").append(fakeType).append(")"); + } builder.append("[ID: 0x"); builder.append(Integer.toHexString(mId)); @@ -106,23 +117,25 @@ class PSDImageResource { case PSD.RES_THUMBNAIL_PS4: case PSD.RES_THUMBNAIL: case PSD.RES_ICC_PROFILE: + case PSD.RES_VERSION_INFO: case PSD.RES_EXIF_DATA_1: // case PSD.RES_EXIF_DATA_3: case PSD.RES_XMP_DATA: case PSD.RES_PRINT_FLAGS_INFORMATION: - return ""; + return null; default: try { for (Field field : PSD.class.getDeclaredFields()) { if (field.getName().startsWith("RES_") && field.getInt(null) == pId) { - return "(" + field.getName().substring(4) + ")"; + String name = field.getName().substring(4); + return StringUtil.lispToCamel(name.replace("_", "-").toLowerCase(), true); } } } catch (IllegalAccessException ignore) { } - return "(unknown resource)"; + return "unknown resource"; } } @@ -149,6 +162,8 @@ class PSDImageResource { return new PSDThumbnail(id, pInput); case PSD.RES_ICC_PROFILE: return new ICCProfile(id, pInput); + case PSD.RES_VERSION_INFO: + return new PSDVersionInfo(id, pInput); case PSD.RES_EXIF_DATA_1: return new PSDEXIF1Data(id, pInput); case PSD.RES_XMP_DATA: diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java index da10552c..56a62f26 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java @@ -98,14 +98,12 @@ class PSDLayerInfo { mLayerName = PSDUtil.readPascalString(pInput); int layerNameSize = mLayerName.length() + 1; - // readPascalString has already read pad byte for word alignment - if (layerNameSize % 2 != 0) { - layerNameSize++; - } - // Skip two more pad bytes if needed + + // Skip pad bytes for long word alignment if (layerNameSize % 4 != 0) { - pInput.skipBytes(2); - layerNameSize += 2; + int skip = layerNameSize % 4; + pInput.skipBytes(skip); + layerNameSize += skip; } // TODO: There's some data skipped here... diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index a9755fad..69188525 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -1,15 +1,20 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.lang.StringUtil; +import com.twelvemonkeys.util.FilterIterator; +import org.w3c.dom.Document; import org.w3c.dom.Node; +import org.xml.sax.InputSource; import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; import java.awt.image.IndexColorModel; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.List; /** @@ -21,16 +26,28 @@ import java.util.List; */ public final class PSDMetadata extends IIOMetadata implements Cloneable { - static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_1.0"; + // TODO: Decide on image/stream metadata... + static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0"; static final String NATIVE_METADATA_FORMAT_CLASS_NAME = "com.twelvemonkeys.imageio.plugins.psd.PSDMetadataFormat"; - // TODO: Move fields from PSDImageReader (header, color map, resources, etc) here PSDHeader mHeader; PSDColorData mColorData; + int mCompression = -1; List mImageResources; PSDGlobalLayerMask mGlobalLayerMask; List mLayerInfo; + static final String[] COLOR_MODES = { + "MONOCHROME", "GRAYSCALE", "INDEXED", "RGB", "CMYK", null, null, "MULTICHANNEL", "DUOTONE", "LAB" + }; + + static final String[] DISPLAY_INFO_CS = { + "RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC", + null, // ... (until index 2999), + "ANPA" + }; + static final String[] DISPLAY_INFO_KINDS = {"selected", "protected"}; + protected PSDMetadata() { // TODO: Allow XMP, EXIF and IPTC as extra formats? super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); @@ -119,8 +136,112 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { } } + /// Native format support + private Node getNativeTree() { - throw new UnsupportedOperationException("getNativeTree"); + IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME); + + root.appendChild(createHeaderNode()); + + if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { + root.appendChild(createPaletteNode()); + } + + if (mImageResources != null && !mImageResources.isEmpty()) { + root.appendChild(createImageResourcesNode()); + } + + return root; + } + + private Node createHeaderNode() { + IIOMetadataNode header = new IIOMetadataNode("PSDHeader"); + + header.setAttribute("version", "1"); + header.setAttribute("channels", Integer.toString(mHeader.mChannels)); + header.setAttribute("height", Integer.toString(mHeader.mHeight)); + header.setAttribute("width", Integer.toString(mHeader.mWidth)); + header.setAttribute("bits", Integer.toString(mHeader.mBits)); + header.setAttribute("mode", COLOR_MODES[mHeader.mMode]); + + return header; + } + + private Node createImageResourcesNode() { + IIOMetadataNode resource = new IIOMetadataNode("ImageResources"); + IIOMetadataNode node; + + for (PSDImageResource imageResource : mImageResources) { + // TODO: Always add name (if set) and id (as resourceId) to all nodes? + // Resource Id is useful for people with access to the PSD spec.. + + if (imageResource instanceof PSDAlphaChannelInfo) { + PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource; + + node = new IIOMetadataNode("AlphaChannelInfo"); + + for (String name : alphaChannelInfo.mNames) { + IIOMetadataNode nameNode = new IIOMetadataNode("Name"); + nameNode.setAttribute("value", name); + node.appendChild(nameNode); + } + + resource.appendChild(node); + } + else if (imageResource instanceof PSDDisplayInfo) { + PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource; + + node = new IIOMetadataNode("DisplayInfo"); + node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]); + + StringBuilder builder = new StringBuilder(); + for (short color : displayInfo.mColors) { + if (builder.length() > 0) { + builder.append(" "); + } + builder.append(Integer.toString(color)); + } + + node.setAttribute("colors", builder.toString()); + node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity)); + node.setAttribute("kind", DISPLAY_INFO_KINDS[displayInfo.mKind]); + + resource.appendChild(node); + } + else if (imageResource instanceof PSDXMPData) { + // TODO: Revise/rethink this... + PSDXMPData xmp = (PSDXMPData) imageResource; + + node = new IIOMetadataNode("XMPData"); + + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new InputSource(xmp.getData())); + + // Set the entire XMP document as user data + node.setUserObject(document); + } + catch (Exception e) { + e.printStackTrace(); + } + + resource.appendChild(node); + } + else { + // Generic resource.. + node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId)); + + resource.appendChild(node); + } + + + // TODO: More resources + + node.setAttribute("resourceId", Integer.toHexString(imageResource.mId)); + } + + return resource; } /// Standard format support @@ -135,7 +256,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { switch (mHeader.mMode) { case PSD.COLOR_MODE_MONOCHROME: case PSD.COLOR_MODE_GRAYSCALE: - case PSD.COLOR_MODE_DUOTONE: // Rationale is spec says treat as gray... + case PSD.COLOR_MODE_DUOTONE: // Rationale: Spec says treat as gray... cs = "GRAY"; break; case PSD.COLOR_MODE_RGB: @@ -146,8 +267,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { cs = "CMYK"; break; case PSD.COLOR_MODE_MULTICHANNEL: - // TODO: FixMe - cs = "???"; + cs = getMultiChannelCS(mHeader.mChannels); break; case PSD.COLOR_MODE_LAB: cs = "Lab"; @@ -158,42 +278,22 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { node.setAttribute("name", cs); chroma_node.appendChild(node); - // TODO: Channels might be 5 for RGB + A + Mask... + // TODO: Channels might be 5 for RGB + A + Mask... Probably not correct node = new IIOMetadataNode("NumChannels"); node.setAttribute("value", Integer.toString(mHeader.mChannels)); chroma_node.appendChild(node); -// if (gAMA_present) { -// node = new IIOMetadataNode("Gamma"); -// node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); -// chroma_node.appendChild(node); -// } - // TODO: Check if this is correct with bitmap (monchrome) node = new IIOMetadataNode("BlackIsZero"); node.setAttribute("value", "true"); chroma_node.appendChild(node); if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { - node = new IIOMetadataNode("Palette"); - - IndexColorModel cm = mColorData.getIndexColorModel(); - for (int i = 0; i < cm.getMapSize(); i++) { - IIOMetadataNode entry = - new IIOMetadataNode("PaletteEntry"); - entry.setAttribute("index", Integer.toString(i)); - entry.setAttribute("red", - Integer.toString(cm.getRed(i))); - entry.setAttribute("green", - Integer.toString(cm.getGreen(i))); - entry.setAttribute("blue", - Integer.toString(cm.getBlue(i))); - - node.appendChild(entry); - } + node = createPaletteNode(); chroma_node.appendChild(node); } + // TODO: Hardcode background color to white? // if (bKGD_present) { // if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { // node = new IIOMetadataNode("BackgroundIndex"); @@ -219,22 +319,59 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { return chroma_node; } + private IIOMetadataNode createPaletteNode() { + IIOMetadataNode node = new IIOMetadataNode("Palette"); + IndexColorModel cm = mColorData.getIndexColorModel(); + + for (int i = 0; i < cm.getMapSize(); i++) { + IIOMetadataNode entry = new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", Integer.toString(cm.getRed(i))); + entry.setAttribute("green", Integer.toString(cm.getGreen(i))); + entry.setAttribute("blue", Integer.toString(cm.getBlue(i))); + + node.appendChild(entry); + } + + return node; + } + + private String getMultiChannelCS(short pChannels) { + if (pChannels < 16) { + return Integer.toHexString(pChannels) + "CLR"; + } + + throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels"); + } + @Override protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("CompressionTypeName"); - // TODO: Only if set... - node.setAttribute("value", "PackBits"); + String compression; + switch (mCompression) { + case PSD.COMPRESSION_NONE: + compression = "none"; + break; + case PSD.COMPRESSION_RLE: + compression = "packbits"; + break; + case PSD.COMPRESSION_ZIP: + case PSD.COMPRESSION_ZIP_PREDICTION: + compression = "zip"; + break; + default: + throw new AssertionError("Unreachable"); + } + node.setAttribute("value", compression); compression_node.appendChild(node); node = new IIOMetadataNode("Lossless"); node.setAttribute("value", "true"); compression_node.appendChild(node); -// compression_node.appendChild(node); - return compression_node; } @@ -280,9 +417,9 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { node.setAttribute("value", "Normal"); dimension_node.appendChild(node); - List resolutionInfos = getResources(PSDResolutionInfo.class); - if (!resolutionInfos.isEmpty()) { - PSDResolutionInfo resolutionInfo = resolutionInfos.get(0); + Iterator resolutionInfos = getResources(PSDResolutionInfo.class); + if (!resolutionInfos.hasNext()) { + PSDResolutionInfo resolutionInfo = resolutionInfos.next(); node = new IIOMetadataNode("HorizontalPixelSize"); node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes))); @@ -330,31 +467,49 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { @Override protected IIOMetadataNode getStandardDocumentNode() { - // TODO: PSDVersionInfo + IIOMetadataNode document_node = new IIOMetadataNode("Document"); + IIOMetadataNode node; // scratch node -// if (!tIME_present) { -// return null; -// } -// -// IIOMetadataNode document_node = new IIOMetadataNode("Document"); -// IIOMetadataNode node = null; // scratch node -// -// node = new IIOMetadataNode("ImageModificationTime"); -// node.setAttribute("year", Integer.toString(tIME_year)); -// node.setAttribute("month", Integer.toString(tIME_month)); -// node.setAttribute("day", Integer.toString(tIME_day)); -// node.setAttribute("hour", Integer.toString(tIME_hour)); -// node.setAttribute("minute", Integer.toString(tIME_minute)); -// node.setAttribute("second", Integer.toString(tIME_second)); -// document_node.appendChild(node); -// -// return document_node; - return null; + node = new IIOMetadataNode("FormatVersion"); + node.setAttribute("value", "1"); // PSD format version is always 1 + document_node.appendChild(node); + + // Get EXIF data if present + Iterator exif = getResources(PSDEXIF1Data.class); + if (exif.hasNext()) { + PSDEXIF1Data data = exif.next(); + + // Get the EXIF DateTime (aka ModifyDate) tag if present + PSDEXIF1Data.Entry dateTime = data.mDirectory.get(0x0132); // TODO: Constant + if (dateTime != null) { + node = new IIOMetadataNode("ImageModificationTime"); + // Format: "YYYY:MM:DD hh:mm:ss" (with quotes! :-P) + String value = dateTime.getValueAsString(); + + node.setAttribute("year", value.substring(1, 5)); + node.setAttribute("month", value.substring(6, 8)); + node.setAttribute("day", value.substring(9, 11)); + node.setAttribute("hour", value.substring(12, 14)); + node.setAttribute("minute", value.substring(15, 17)); + node.setAttribute("second", value.substring(18, 20)); + + document_node.appendChild(node); + } + } + + return document_node; } @Override protected IIOMetadataNode getStandardTextNode() { // TODO: CaptionDigest?, EXIF, XMP + + Iterator textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class); + + while (textResources.hasNext()) { + PSDImageResource textResource = textResources.next(); + + } // int numEntries = tEXt_keyword.size() + // iTXt_keyword.size() + zTXt_keyword.size(); @@ -411,8 +566,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { @Override protected IIOMetadataNode getStandardTransparencyNode() { - IIOMetadataNode transparency_node = - new IIOMetadataNode("Transparency"); + IIOMetadataNode transparency_node = new IIOMetadataNode("Transparency"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("Alpha"); @@ -427,20 +581,31 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { mHeader.mMode == PSD.COLOR_MODE_CMYK & mHeader.mChannels >= 5; } - // TODO: Replace with filter iterator? - List getResources(final Class pResourceType) { - List filtered = null; + Iterator getResources(final Class pResourceType) { + // NOTE: The cast here is wrong, strictly speaking, but it does not matter... + @SuppressWarnings({"unchecked"}) + Iterator iterator = (Iterator) mImageResources.iterator(); - for (PSDImageResource resource : mImageResources) { - if (pResourceType.isInstance(resource)) { - if (filtered == null) { - filtered = new ArrayList(); + return new FilterIterator(iterator, new FilterIterator.Filter() { + public boolean accept(final T pElement) { + return pResourceType.isInstance(pElement); + } + }); + } + + Iterator getResources(final Class... pResourceTypes) { + Iterator iterator = mImageResources.iterator(); + + return new FilterIterator(iterator, new FilterIterator.Filter() { + public boolean accept(final PSDImageResource pElement) { + for (Class type : pResourceTypes) { + if (type.isInstance(pElement)) { + return true; + } } - filtered.add(pResourceType.cast(resource)); + return false; } - } - - return filtered; + }); } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java index 072748ce..6d2a765d 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -1,5 +1,7 @@ package com.twelvemonkeys.imageio.plugins.psd; +import org.w3c.dom.Document; + import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadataFormatImpl; import java.util.Arrays; @@ -33,7 +35,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { addElement("PSDHeader", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); // TODO: Do the first two make sense? - addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); +// addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1")); addAttribute("PSDHeader", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); @@ -42,16 +44,8 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // columns? addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); - addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList( - String.valueOf(PSD.COLOR_MODE_MONOCHROME), - String.valueOf(PSD.COLOR_MODE_GRAYSCALE), - String.valueOf(PSD.COLOR_MODE_INDEXED), - String.valueOf(PSD.COLOR_MODE_RGB), - String.valueOf(PSD.COLOR_MODE_CMYK), - String.valueOf(PSD.COLOR_MODE_MULTICHANNEL), - String.valueOf(PSD.COLOR_MODE_DUOTONE), - String.valueOf(PSD.COLOR_MODE_LAB) - )); + // TODO: Consider using more readable names?! + addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.COLOR_MODES)); /* Contains the required data to define the color mode. @@ -69,7 +63,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // NOTE: Palette, PaletteEntry naming taken from the standard format, native PSD naming is ColorModeData // NOTE: PSD stores these as 256 Red, 256 Green, 256 Blue.. Should we do the same in the meta data? addElement("Palette", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, 256, 256); // 768 = 256 * 3 - addElement("PaletteEntry", "PSDColorData", CHILD_POLICY_EMPTY); + addElement("PaletteEntry", "Palette", CHILD_POLICY_EMPTY); addAttribute("PaletteEntry", "index", DATATYPE_INTEGER, true, null, "0", "255", true, true); addAttribute("PaletteEntry", "red", DATATYPE_INTEGER, true, null, "0", "255", true, true); addAttribute("PaletteEntry", "green", DATATYPE_INTEGER, true, null, "0", "255", true, true); @@ -89,15 +83,19 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // TODO: Allow arbitrary values to be added as a generic resource... // root -> ImageResources -> AlphaChannelInfo - addElement("AlphaChannelInfo", "ImageResources", CHILD_POLICY_EMPTY); - addAttribute("AlphaChannelInfo", "names", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); + addElement("AlphaChannelInfo", "ImageResources", 0, Integer.MAX_VALUE); // The format probably does not support that many layers.. + addElement("Name", "AlphaChannelInfo", CHILD_POLICY_EMPTY); + addAttribute("Name", "value", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); // root -> ImageResources -> DisplayInfo addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY); + // TODO: Consider using human readable strings + // TODO: Limit values (0-8, 10, 11, 3000) addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null); addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4); addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true); - addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList("0", "1")); + // TODO: Consider using human readable strings + addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS)); // root -> ImageResources -> EXIF1Data addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL); @@ -138,6 +136,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> ImageResources -> XMPData addElement("XMPData", "ImageResources", CHILD_POLICY_CHOICE); // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) + addObjectValue("XMPData", Document.class, true, null); // TODO: Layers //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java index c64380b9..bb802354 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java @@ -26,14 +26,14 @@ final class PSDPrintFlags extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { - mLabels = pInput.readUnsignedByte() != 0; - mCropMasks = pInput.readUnsignedByte() != 0; - mColorBars = pInput.readUnsignedByte() != 0; - mRegistrationMarks = pInput.readUnsignedByte() != 0; - mNegative = pInput.readUnsignedByte() != 0; - mFlip = pInput.readUnsignedByte() != 0; - mInterpolate = pInput.readUnsignedByte() != 0; - mCaption = pInput.readUnsignedByte() != 0; + mLabels = pInput.readBoolean(); + mCropMasks = pInput.readBoolean(); + mColorBars = pInput.readBoolean(); + mRegistrationMarks = pInput.readBoolean(); + mNegative = pInput.readBoolean(); + mFlip = pInput.readBoolean(); + mInterpolate = pInput.readBoolean(); + mCaption = pInput.readBoolean(); pInput.skipBytes(mSize - 8); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java index d7c879d0..c3aa31d3 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java @@ -24,7 +24,7 @@ final class PSDPrintFlagsInformation extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { mVersion = pInput.readUnsignedShort(); - mCropMasks = pInput.readUnsignedByte() != 0; + mCropMasks = pInput.readBoolean(); mField = pInput.readUnsignedByte(); mBleedWidth = pInput.readUnsignedInt(); mBleedScale = pInput.readUnsignedShort(); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java index 4d989865..be49a19f 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java @@ -37,7 +37,7 @@ class PSDThumbnail extends PSDImageResource { */ @Override protected void readData(final ImageInputStream pInput) throws IOException { - // TODO: Support for RAW RGB (format == 0) + // TODO: Support for RAW RGB (format == 0): Extract RAW reader from PICT RAW QuickTime decompressor int format = pInput.readInt(); switch (format) { case 0: diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java index 58a5fd4c..f3e1ab41 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java @@ -31,8 +31,10 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; +import com.twelvemonkeys.lang.StringUtil; import javax.imageio.stream.ImageInputStream; +import java.io.DataInput; import java.io.DataInputStream; import java.io.IOException; import java.util.zip.ZipInputStream; @@ -57,23 +59,22 @@ final class PSDUtil { ); } - // TODO: Proably also useful for PICT reader, move to some common util? - static String readPascalString(ImageInputStream pInput) throws IOException { + // TODO: Proably also useful for PICT reader, move to some common util? + // TODO: Is this REALLY different from the previous method? Maybe the pad should not be read.. + static String readPascalString(final DataInput pInput) throws IOException { int length = pInput.readUnsignedByte(); -// int length = pInput.readUnsignedShort(); byte[] bytes = new byte[length]; pInput.readFully(bytes); - if (length % 2 == 0) { - pInput.readByte(); // Pad - } - return new String(bytes); + + return StringUtil.decode(bytes, 0, bytes.length, "ASCII"); } - static String readPascalStringByte(ImageInputStream pInput) throws IOException { - int length = pInput.readUnsignedByte(); - byte[] bytes = new byte[length]; + static String readUTF16String(final DataInput pInput) throws IOException { + int length = pInput.readInt(); + byte[] bytes = new byte[length * 2]; pInput.readFully(bytes); - return new String(bytes); + + return StringUtil.decode(bytes, 0, bytes.length, "UTF-16"); } static DataInputStream createPackBitsStream(final ImageInputStream pInput, long pLength) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java new file mode 100644 index 00000000..683fa3a3 --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java @@ -0,0 +1,57 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +/** + * PSDVersionInfo + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDVersionInfo.java,v 1.0 Nov 6, 2009 1:02:19 PM haraldk Exp$ + */ +final class PSDVersionInfo extends PSDImageResource { + + int mVersion; + boolean mHasRealMergedData; + String mWriter; + String mReader; + int mFileVersion; + + PSDVersionInfo(final short pId, final ImageInputStream pInput) throws IOException { + super(pId, pInput); + } + + @Override + protected void readData(final ImageInputStream pInput) throws IOException { + /* + 4 bytes version + 1 byte hasRealMergedData + Unicode string: writer name + Unicode string: reader name + 4 bytes file version. + */ + + mVersion = pInput.readInt(); + mHasRealMergedData = pInput.readBoolean(); + + mWriter = PSDUtil.readUTF16String(pInput); + mReader = PSDUtil.readUTF16String(pInput); + + mFileVersion = pInput.readInt(); + } + + @Override + public String toString() { + StringBuilder builder = toStringBuilder(); + + builder.append(", version: ").append(mVersion); + builder.append(", hasRealMergedData: ").append(mHasRealMergedData); + builder.append(", writer: ").append(mWriter); + builder.append(", reader: ").append(mReader); + builder.append(", file version: ").append(mFileVersion); + builder.append("]"); + + return builder.toString(); + } +} From 54cf727dee70bb5696f84864dd443bac1afdb7c3 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:39:32 +0100 Subject: [PATCH 04/21] Work in progress for PSD metadata support: - Changes to native format spec - Implemented more of native format - Added several more resource type implementations - IPTC metadata support --- .../imageio/plugins/psd/PSD.java | 6 +- .../plugins/psd/PSDGridAndGuideInfo.java | 58 +++ .../imageio/plugins/psd/PSDIPTCData.java | 418 ++++++++++++++++++ .../imageio/plugins/psd/PSDImageResource.java | 21 +- .../imageio/plugins/psd/PSDMetadata.java | 151 ++++++- .../plugins/psd/PSDMetadataFormat.java | 100 +++-- .../plugins/psd/PSDPixelAspectRatio.java | 27 ++ .../imageio/plugins/psd/PSDPrintFlags.java | 16 +- .../plugins/psd/PSDPrintFlagsInformation.java | 12 +- .../imageio/plugins/psd/PSDPrintScale.java | 35 ++ .../plugins/psd/PSDUnicodeAlphaNames.java | 33 ++ twelvemonkeys-imageio/psd/todo.txt | 3 +- 12 files changed, 821 insertions(+), 59 deletions(-) create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGridAndGuideInfo.java create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPixelAspectRatio.java create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintScale.java create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java index 55a3ba45..04f8c678 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java @@ -225,11 +225,11 @@ interface PSD { // 03f8 /** Color transfer functions */ - int RES_COLOR_TRANSFER_FUNCITON = 0x03f8; + int RES_COLOR_TRANSFER_FUNCTION = 0x03f8; // 03f9 /** Duotone transfer functions */ - int RES_DUOTONE_TRANSFER_FUNCITON = 0x03f9; + int RES_DUOTONE_TRANSFER_FUNCTOON = 0x03f9; // 03fa /** Duotone image information */ @@ -385,7 +385,7 @@ interface PSD { * (Photoshop 5.0) Unicode Alpha Names * Unicode string (4 bytes length followed by string). */ - int RES_UNICODE_ALPHA_NAME = 0x0415; + int RES_UNICODE_ALPHA_NAMES = 0x0415; // 1046 /** diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGridAndGuideInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGridAndGuideInfo.java new file mode 100644 index 00000000..2b4e0b14 --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGridAndGuideInfo.java @@ -0,0 +1,58 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +/** + * PSDGridAndGuideInfo + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDGridAndGuideInfo.java,v 1.0 Nov 7, 2009 8:46:13 PM haraldk Exp$ + */ +final class PSDGridAndGuideInfo extends PSDImageResource { +/* Grid & guide header */ +//typedef struct { +// guint32 fVersion; /* Version - always 1 for PS */ +// guint32 fGridCycleV; /* Vertical grid size */ +// guint32 fGridCycleH; /* Horizontal grid size */ +// guint32 fGuideCount; /* Number of guides */ +//} GuideHeader; + +/* Guide resource block */ +//typedef struct { +// guint32 fLocation; /* Guide position in Pixels * 100 */ +// gchar fDirection; /* Guide orientation */ +//} GuideResource; + + int mVersion; + int mGridCycleVertical; + int mGridCycleHorizontal; + int mGuideCount; + + GuideResource[] mGuides; + + PSDGridAndGuideInfo(final short pId, final ImageInputStream pInput) throws IOException { + super(pId, pInput); + } + + @Override + protected void readData(final ImageInputStream pInput) throws IOException { + mVersion = pInput.readInt(); + mGridCycleVertical = pInput.readInt(); + mGridCycleHorizontal = pInput.readInt(); + mGuideCount = pInput.readInt(); + + mGuides = new GuideResource[mGuideCount]; + + for (GuideResource guide : mGuides) { + guide.mLocation = pInput.readInt(); + guide.mDirection = pInput.readByte(); + } + } + + static class GuideResource { + int mLocation; + byte mDirection; // 0: vertical, 1: horizontal + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java new file mode 100644 index 00000000..000084e7 --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java @@ -0,0 +1,418 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.*; +import java.util.*; + +/** + * PSDIPTCData + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$ + */ +final class PSDIPTCData extends PSDImageResource { + // TODO: Refactor to be more like PSDEXIF1Data... + // TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s) + Directory mDirectory; + + PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException { + super(pId, pInput); + } + + @Override + protected void readData(final ImageInputStream pInput) throws IOException { + mDirectory = Directory.read(pInput, mSize); + } + + @Override + public String toString() { + StringBuilder builder = toStringBuilder(); + builder.append(", ").append(mDirectory); + builder.append("]"); + return builder.toString(); + } + + static class Entry { + private int mTagId; + private String mValue; + + public Entry(int pTagId, String pValue) { + mTagId = pTagId; + mValue = pValue; + } + + @Override + public String toString() { + return (mTagId >> 8) + ":" + (mTagId & 0xff) + ": " + mValue; + } + } + + static class Directory implements Iterable { + private static final int ENCODING_UNKNOWN = -1; + private static final int ENCODING_UNSPECIFIED = 0; + private static final int ENCODING_UTF_8 = 0x1b2547; + + private int mEncoding = ENCODING_UNSPECIFIED; + final List mEntries = new ArrayList(); + + private Directory() {} + + @Override + public String toString() { + return "Directory" + mEntries.toString(); + } + + public Iterator iterator() { + return mEntries.iterator(); + } + + public static Directory read(final ImageInputStream pInput, final long pSize) throws IOException { + Directory directory = new Directory(); + + final long streamEnd = pInput.getStreamPosition() + pSize; + + // For each tag + while (pInput.getStreamPosition() < streamEnd) { + // Identifies start of a tag + byte b = pInput.readByte(); + if (b != 0x1c) { + throw new IIOException("Corrupt IPTC stream segment"); + } + + // We need at least four bytes left to read a tag + if (pInput.getStreamPosition() + 4 >= streamEnd) { + break; + } + + int directoryType = pInput.readUnsignedByte(); + int tagType = pInput.readUnsignedByte(); + int tagByteCount = pInput.readUnsignedShort(); + + if (pInput.getStreamPosition() + tagByteCount > streamEnd) { + throw new IIOException("Data for tag extends beyond end of IPTC segment: " + (tagByteCount + pInput.getStreamPosition() - streamEnd)); + } + + directory.processTag(pInput, directoryType, tagType, tagByteCount); + } + + return directory; + } + + private void processTag(ImageInputStream pInput, int directoryType, int tagType, int tagByteCount) throws IOException { + int tagIdentifier = (directoryType << 8) | tagType; + + String str = null; + switch (tagIdentifier) { + case IPTC.TAG_CODED_CHARACTER_SET: + // TODO: Use this encoding!? + // TODO: Move somewhere else? + mEncoding = parseEncoding(pInput, tagByteCount); + return; + case IPTC.TAG_RECORD_VERSION: + // short + str = Integer.toString(pInput.readUnsignedShort()); + break; +// case IPTC.TAG_RELEASE_DATE: +// case IPTC.TAG_EXPIRATION_DATE: +// case IPTC.TAG_REFERENCE_DATE: +// case IPTC.TAG_DATE_CREATED: +// case IPTC.TAG_DIGITAL_CREATION_DATE: +// // Date object +// Date date = parseISO8601DatePart(pInput, tagByteCount); +// if (date != null) { +// directory.setDate(tagIdentifier, date); +// return; +// } +// case IPTC.TAG_RELEASE_TIME: +// case IPTC.TAG_EXPIRATION_TIME: +// case IPTC.TAG_TIME_CREATED: +// case IPTC.TAG_DIGITAL_CREATION_TIME: +// // NOTE: Spec says fields should be sent in order, so this is okay +// date = getDateForTime(directory, tagIdentifier); +// +// Date time = parseISO8601TimePart(pInput, tagByteCount, date); +// if (time != null) { +// directory.setDate(tagIdentifier, time); +// return; +// } +// + default: + // fall through + } + + // Skip non-Application fields, as they are typically not human readable + if (directoryType << 8 != IPTC.APPLICATION_RECORD) { + return; + } + + // If we don't have a value, treat it as a string + if (str == null) { + if (tagByteCount < 1) { + str = "(No value)"; + } + else { + str = String.format("\"%s\"", parseString(pInput, tagByteCount)); + } + } + + mEntries.add(new Entry(tagIdentifier, str)); + +// if (directory.containsTag(tagIdentifier)) { +// // TODO: Does that REALLY help for performance?! +// // this fancy string[] business avoids using an ArrayList for performance reasons +// String[] oldStrings; +// String[] newStrings; +// try { +// oldStrings = directory.getStringArray(tagIdentifier); +// } +// catch (MetadataException e) { +// oldStrings = null; +// } +// if (oldStrings == null) { +// newStrings = new String[1]; +// } +// else { +// newStrings = new String[oldStrings.length + 1]; +// System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length); +// } +// newStrings[newStrings.length - 1] = str; +// directory.setStringArray(tagIdentifier, newStrings); +// } +// else { +// directory.setString(tagIdentifier, str); +// } + } + +// private Date getDateForTime(final Directory directory, final int tagIdentifier) { +// int dateTag; +// +// switch (tagIdentifier) { +// case IPTC.TAG_RELEASE_TIME: +// dateTag = IPTC.TAG_RELEASE_DATE; +// break; +// case IPTC.TAG_EXPIRATION_TIME: +// dateTag = IPTC.TAG_EXPIRATION_DATE; +// break; +// case IPTC.TAG_TIME_CREATED: +// dateTag = IPTC.TAG_DATE_CREATED; +// break; +// case IPTC.TAG_DIGITAL_CREATION_TIME: +// dateTag = IPTC.TAG_DIGITAL_CREATION_DATE; +// break; +// default: +// return new Date(0l); +// } +// +// return directory.containsTag(dateTag) ? directory.getDate(dateTag) : new Date(0l); +// } + + + private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException { + return tagByteCount == 3 + && (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8 + ? ENCODING_UTF_8 : ENCODING_UNKNOWN; + } + +// private Date parseISO8601TimePart(final ImageInputStream pInputStream, int tagByteCount, final Date date) throws IOException { +// // ISO 8601: HHMMSS±HHMM +// if (tagByteCount >= 11) { +// String timeStr = parseString(pInputStream, tagByteCount); +// try { +// int hour = Integer.parseInt(timeStr.substring(0, 2)); +// int minute = Integer.parseInt(timeStr.substring(2, 4)); +// int second = Integer.parseInt(timeStr.substring(4, 6)); +// String tzOffset = timeStr.substring(6, 11); +// +// TimeZone zone = new SimpleTimeZone(Integer.parseInt(tzOffset.charAt(0) == '+' ? tzOffset.substring(1) : tzOffset), tzOffset); +// +// GregorianCalendar calendar = new GregorianCalendar(zone); +// calendar.setTime(date); +// +// calendar.add(Calendar.HOUR_OF_DAY, hour); +// calendar.add(Calendar.MINUTE, minute); +// calendar.add(Calendar.SECOND, second); +// +// return calendar.getTime(); +// } +// catch (NumberFormatException e) { +// // fall through and we'll store whatever was there as a String +// } +// } +// return null; +// } +// +// private Date parseISO8601DatePart(final ImageInputStream pInputStream, int tagByteCount) throws IOException { +// // ISO 8601: CCYYMMDD +// if (tagByteCount >= 8) { +// String dateStr = parseString(pInputStream, tagByteCount); +// try { +// int year = Integer.parseInt(dateStr.substring(0, 4)); +// int month = Integer.parseInt(dateStr.substring(4, 6)) - 1; +// int day = Integer.parseInt(dateStr.substring(6, 8)); +// GregorianCalendar calendar = new GregorianCalendar(year, month, day); +// return calendar.getTime(); +// } +// catch (NumberFormatException e) { +// // fall through and we'll store whatever was there as a String +// } +// } +// return null; +// } + + // TODO: Pass encoding as parameter? Use if specified + private String parseString(final ImageInputStream pInput, int length) throws IOException { + // NOTE: The IPTC "spec" says ISO 646 or ISO 2022 encoding. UTF-8 contains all 646 characters, but not 2022. + // This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html + // First try to decode using UTF-8 (which seems to be the de-facto standard) + String str; + Charset charset = Charset.forName("UTF-8"); + CharsetDecoder decoder = charset.newDecoder(); + CharBuffer chars; + byte[] data = new byte[length]; + pInput.readFully(data); + try { + // Will fail fast on illegal UTF-8-sequences + chars = decoder.onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(data)); + str = chars.toString(); + } + catch (CharacterCodingException notUTF8) { + if (mEncoding == ENCODING_UTF_8) { + throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8); + } + + // Fall back to use ISO-8859-1 + // This will not fail, but may may create wrong fallback-characters + str = StringUtil.decode(data, 0, data.length, "ISO8859_1"); + } + + return str; + } + } + + static interface IPTC { + static final int ENVELOPE_RECORD = 1 << 8; + static final int APPLICATION_RECORD = 2 << 8; + + static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90; + + /** 2:00 Record Version (mandatory) */ + public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200 +// /** 2:03 Object Type Reference */ +// public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3; +// /** 2:04 Object Attribute Reference (repeatable) */ +// public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4; +// /** 2:05 Object Name */ +// public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205 +// /** 2:07 Edit Status */ +// public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7; +// /** 2:08 Editorial Update */ +// public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8; +// /** 2:10 Urgency */ +// public static final int TAG_URGENCY = APPLICATION_RECORD | 10; +// /** 2:12 Subect Reference (repeatable) */ +// public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12; +// /** 2:15 Category */ +// public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f +// /** 2:20 Supplemental Category (repeatable) */ +// public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20; +// /** 2:22 Fixture Identifier */ +// public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22; +// /** 2:25 Keywords (repeatable) */ +// public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25; +// /** 2:26 Content Locataion Code (repeatable) */ +// public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26; +// /** 2:27 Content Locataion Name (repeatable) */ +// public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27; +// /** 2:30 Release Date */ +// public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30; +// /** 2:35 Release Time */ +// public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35; +// /** 2:37 Expiration Date */ +// public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37; +// /** 2:38 Expiration Time */ +// public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38; +// /** 2:40 Special Instructions */ +// public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228 +// /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */ +// public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42; +// /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */ +// public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45; +// /** 2:47 Reference Date (mandatory if 2:45 present) */ +// public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47; +// /** 2:50 Reference Number (mandatory if 2:45 present) */ +// public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50; +// /** 2:55 Date Created */ +// public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237 +// /** 2:60 Time Created */ +// public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60; +// /** 2:62 Digital Creation Date */ +// public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62; +// /** 2:63 Digital Creation Date */ +// public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63; +// /** 2:65 Originating Program */ +// public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65; +// /** 2:70 Program Version (only valid if 2:65 present) */ +// public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70; +// /** 2:75 Object Cycle (a: morning, p: evening, b: both) */ +// public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75; +// /** 2:80 By-line (repeatable) */ +// public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250 +// /** 2:85 By-line Title (repeatable) */ +// public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255 +// /** 2:90 City */ +// public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a +// /** 2:92 Sub-location */ +// public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92; +// /** 2:95 Province/State */ +// public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f +// /** 2:100 Country/Primary Location Code */ +// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100; +// /** 2:101 Country/Primary Location Name */ +// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265 +// /** 2:103 Original Transmission Reference */ +// public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267 +// /** 2:105 Headline */ +// public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269 +// /** 2:110 Credit */ +// public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e +// /** 2:115 Source */ +// public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273 +// /** 2:116 Copyright Notice */ +// public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274 +// /** 2:118 Contact */ +// public static final int TAG_CONTACT = APPLICATION_RECORD | 118; +// /** 2:120 Catption/Abstract */ +// public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278 +// /** 2:122 Writer/Editor (repeatable) */ +// public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a +// /** 2:125 Rasterized Caption (binary data) */ +// public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125; +// /** 2:130 Image Type */ +// public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130; +// /** 2:131 Image Orientation */ +// public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131; +// /** 2:135 Language Identifier */ +// public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135; +// +// // TODO: Should we expose this field? +// /** +// * 2:199 JobMinder Assignment Data (Custom IPTC field). +// * A common custom IPTC field used by a now discontinued application called JobMinder. +// * +// * @see JobMinder Homepage +// */ +// static final int CUSTOM_TAG_JOBMINDER_ASSIGMENT_DATA = APPLICATION_RECORD | 199; +// +// // TODO: 2:150-2:154 Audio and 2:200-2:202 Object Preview Data + + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java index 8dd3770a..180ce4d7 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java @@ -43,6 +43,9 @@ import java.lang.reflect.Field; * @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$ */ class PSDImageResource { + // TODO: Refactor image resources to separate package + // TODO: Change constructor to store stream offset and length only (+ possibly the name), defer reading + final short mId; final String mName; final long mSize; @@ -61,6 +64,8 @@ class PSDImageResource { mSize = pInput.readUnsignedInt(); readData(pInput); + // TODO: Sanity check reading here? + // Data is even-padded if (mSize % 2 != 0) { pInput.read(); @@ -114,6 +119,8 @@ class PSDImageResource { case PSD.RES_ALPHA_CHANNEL_INFO: case PSD.RES_DISPLAY_INFO: case PSD.RES_PRINT_FLAGS: + case PSD.RES_IPTC_NAA: + case PSD.RES_GRID_AND_GUIDES_INFO: case PSD.RES_THUMBNAIL_PS4: case PSD.RES_THUMBNAIL: case PSD.RES_ICC_PROFILE: @@ -121,6 +128,8 @@ class PSDImageResource { case PSD.RES_EXIF_DATA_1: // case PSD.RES_EXIF_DATA_3: case PSD.RES_XMP_DATA: + case PSD.RES_PRINT_SCALE: + case PSD.RES_PIXEL_ASPECT_RATIO: case PSD.RES_PRINT_FLAGS_INFORMATION: return null; default: @@ -135,7 +144,7 @@ class PSDImageResource { catch (IllegalAccessException ignore) { } - return "unknown resource"; + return "UnknownResource"; } } @@ -157,17 +166,27 @@ class PSDImageResource { return new PSDDisplayInfo(id, pInput); case PSD.RES_PRINT_FLAGS: return new PSDPrintFlags(id, pInput); + case PSD.RES_IPTC_NAA: + return new PSDIPTCData(id, pInput); + case PSD.RES_GRID_AND_GUIDES_INFO: + return new PSDGridAndGuideInfo(id, pInput); case PSD.RES_THUMBNAIL_PS4: case PSD.RES_THUMBNAIL: return new PSDThumbnail(id, pInput); case PSD.RES_ICC_PROFILE: return new ICCProfile(id, pInput); + case PSD.RES_UNICODE_ALPHA_NAMES: + return new PSDUnicodeAlphaNames(id, pInput); case PSD.RES_VERSION_INFO: return new PSDVersionInfo(id, pInput); case PSD.RES_EXIF_DATA_1: return new PSDEXIF1Data(id, pInput); case PSD.RES_XMP_DATA: return new PSDXMPData(id, pInput); + case PSD.RES_PRINT_SCALE: + return new PSDPrintScale(id, pInput); + case PSD.RES_PIXEL_ASPECT_RATIO: + return new PSDPixelAspectRatio(id, pInput); case PSD.RES_PRINT_FLAGS_INFORMATION: return new PSDPrintFlagsInformation(id, pInput); default: diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 69188525..e622bc01 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -43,11 +43,24 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { static final String[] DISPLAY_INFO_CS = { "RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC", - null, // ... (until index 2999), + null, // TODO: ... (until index 2999), "ANPA" }; static final String[] DISPLAY_INFO_KINDS = {"selected", "protected"}; + static final String[] RESOLUTION_UNITS = {null, "pixels/inch", "pixels/cm"}; + static final String[] DIMENSION_UNITS = {null, "in", "cm", "pt", "picas", "columns"}; + + static final String[] JAVA_CS = { + "XYZ", "Lab", "Yuv", "YCbCr", "Yxy", "RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY", + "2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR", "9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR" + }; + + static final String[] GUIDE_ORIENTATIONS = {"vertical", "horizontal"}; + + static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"}; + + protected PSDMetadata() { // TODO: Allow XMP, EXIF and IPTC as extra formats? super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); @@ -155,8 +168,9 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { } private Node createHeaderNode() { - IIOMetadataNode header = new IIOMetadataNode("PSDHeader"); + IIOMetadataNode header = new IIOMetadataNode("Header"); + header.setAttribute("type", "PSD"); header.setAttribute("version", "1"); header.setAttribute("channels", Integer.toString(mHeader.mChannels)); header.setAttribute("height", Integer.toString(mHeader.mHeight)); @@ -175,7 +189,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { // TODO: Always add name (if set) and id (as resourceId) to all nodes? // Resource Id is useful for people with access to the PSD spec.. - if (imageResource instanceof PSDAlphaChannelInfo) { + if (imageResource instanceof ICCProfile) { + ICCProfile profile = (ICCProfile) imageResource; + + // TODO: Format spec + node = new IIOMetadataNode("ICCProfile"); + node.setAttribute("colorSpaceType", JAVA_CS[profile.getProfile().getColorSpaceType()]); + node.setUserObject(profile.getProfile()); + } + else if (imageResource instanceof PSDAlphaChannelInfo) { PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource; node = new IIOMetadataNode("AlphaChannelInfo"); @@ -185,8 +207,6 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { nameNode.setAttribute("value", name); node.appendChild(nameNode); } - - resource.appendChild(node); } else if (imageResource instanceof PSDDisplayInfo) { PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource; @@ -205,14 +225,121 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { node.setAttribute("colors", builder.toString()); node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity)); node.setAttribute("kind", DISPLAY_INFO_KINDS[displayInfo.mKind]); + } + else if (imageResource instanceof PSDGridAndGuideInfo) { + PSDGridAndGuideInfo info = (PSDGridAndGuideInfo) imageResource; - resource.appendChild(node); + node = new IIOMetadataNode("GridAndGuideInfo"); + node.setAttribute("version", String.valueOf(info.mVersion)); + node.setAttribute("verticalGridCycle", String.valueOf(info.mGridCycleVertical)); + node.setAttribute("horizontalGridCycle", String.valueOf(info.mGridCycleHorizontal)); + + for (PSDGridAndGuideInfo.GuideResource guide : info.mGuides) { + IIOMetadataNode guideNode = new IIOMetadataNode("Guide"); + guideNode.setAttribute("location", Integer.toString(guide.mLocation)); + guideNode.setAttribute("orientation", GUIDE_ORIENTATIONS[guide.mDirection]); + } + } + else if (imageResource instanceof PSDPixelAspectRatio) { + PSDPixelAspectRatio aspectRatio = (PSDPixelAspectRatio) imageResource; + + node = new IIOMetadataNode("PixelAspectRatio"); + node.setAttribute("version", String.valueOf(aspectRatio.mVersion)); + node.setAttribute("aspectRatio", String.valueOf(aspectRatio.mAspect)); + } + else if (imageResource instanceof PSDPrintFlags) { + PSDPrintFlags flags = (PSDPrintFlags) imageResource; + + node = new IIOMetadataNode("PrintFlags"); + node.setAttribute("labels", String.valueOf(flags.mLabels)); + node.setAttribute("cropMarks", String.valueOf(flags.mCropMasks)); + node.setAttribute("colorBars", String.valueOf(flags.mColorBars)); + node.setAttribute("registrationMarks", String.valueOf(flags.mRegistrationMarks)); + node.setAttribute("negative", String.valueOf(flags.mNegative)); + node.setAttribute("flip", String.valueOf(flags.mFlip)); + node.setAttribute("interpolate", String.valueOf(flags.mInterpolate)); + node.setAttribute("caption", String.valueOf(flags.mCaption)); + } + else if (imageResource instanceof PSDPrintFlagsInformation) { + PSDPrintFlagsInformation information = (PSDPrintFlagsInformation) imageResource; + + node = new IIOMetadataNode("PrintFlagsInformation"); + node.setAttribute("version", String.valueOf(information.mVersion)); + node.setAttribute("cropMarks", String.valueOf(information.mCropMasks)); + node.setAttribute("field", String.valueOf(information.mField)); + node.setAttribute("bleedWidth", String.valueOf(information.mBleedWidth)); + node.setAttribute("bleedScale", String.valueOf(information.mBleedScale)); + } + else if (imageResource instanceof PSDPrintScale) { + PSDPrintScale printScale = (PSDPrintScale) imageResource; + + node = new IIOMetadataNode("PrintScale"); + node.setAttribute("style", PRINT_SCALE_STYLES[printScale.mStyle]); + node.setAttribute("xLocation", String.valueOf(printScale.mXLocation)); + node.setAttribute("yLocation", String.valueOf(printScale.mYlocation)); + node.setAttribute("scale", String.valueOf(printScale.mScale)); + } + else if (imageResource instanceof PSDResolutionInfo) { + PSDResolutionInfo information = (PSDResolutionInfo) imageResource; + + node = new IIOMetadataNode("ResolutionInfo"); + node.setAttribute("horizontalResolution", String.valueOf(information.mHRes)); + node.setAttribute("horizontalResolutionUnit", RESOLUTION_UNITS[information.mHResUnit]); + node.setAttribute("widthUnit", DIMENSION_UNITS[information.mWidthUnit]); + node.setAttribute("verticalResolution", String.valueOf(information.mVRes)); + node.setAttribute("verticalResolutionUnit", RESOLUTION_UNITS[information.mVResUnit]); + node.setAttribute("heightUnit", DIMENSION_UNITS[information.mHeightUnit]); + } + else if (imageResource instanceof PSDUnicodeAlphaNames) { + PSDUnicodeAlphaNames alphaNames = (PSDUnicodeAlphaNames) imageResource; + + node = new IIOMetadataNode("UnicodeAlphaNames"); + + for (String name : alphaNames.mNames) { + IIOMetadataNode nameNode = new IIOMetadataNode("Name"); + nameNode.setAttribute("value", name); + node.appendChild(nameNode); + } + } + else if (imageResource instanceof PSDVersionInfo) { + PSDVersionInfo information = (PSDVersionInfo) imageResource; + + node = new IIOMetadataNode("VersionInfo"); + node.setAttribute("version", String.valueOf(information.mVersion)); + node.setAttribute("hasRealMergedData", String.valueOf(information.mHasRealMergedData)); + node.setAttribute("writer", information.mWriter); + node.setAttribute("reader", information.mReader); + node.setAttribute("fileVersion", String.valueOf(information.mFileVersion)); + } + else if (imageResource instanceof PSDThumbnail) { + // TODO: Revise/rethink this... + PSDThumbnail thumbnail = (PSDThumbnail) imageResource; + + node = new IIOMetadataNode("Thumbnail"); + // TODO: Thumbnail attributes + access to data, to avoid JPEG re-compression problems + node.setUserObject(thumbnail.getThumbnail()); + } + else if (imageResource instanceof PSDIPTCData) { + // TODO: Revise/rethink this... + // Transcode to XMP? ;-) + PSDIPTCData iptc = (PSDIPTCData) imageResource; + + node = new IIOMetadataNode("IPTC"); + node.setUserObject(iptc.mDirectory); + } + else if (imageResource instanceof PSDEXIF1Data) { + // TODO: Revise/rethink this... + // Transcode to XMP? ;-) + PSDEXIF1Data exif = (PSDEXIF1Data) imageResource; + + node = new IIOMetadataNode("EXIF"); + node.setUserObject(exif.mDirectory); } else if (imageResource instanceof PSDXMPData) { - // TODO: Revise/rethink this... + // TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid... PSDXMPData xmp = (PSDXMPData) imageResource; - node = new IIOMetadataNode("XMPData"); + node = new IIOMetadataNode("XMP"); try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); @@ -225,20 +352,16 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { catch (Exception e) { e.printStackTrace(); } - - resource.appendChild(node); } else { // Generic resource.. node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId)); - - resource.appendChild(node); } - // TODO: More resources - node.setAttribute("resourceId", Integer.toHexString(imageResource.mId)); + node.setAttribute("resourceId", String.format("0x%04x", imageResource.mId)); + resource.appendChild(node); } return resource; diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java index 6d2a765d..38ac1276 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -4,6 +4,7 @@ import org.w3c.dom.Document; import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadataFormatImpl; +import java.awt.image.BufferedImage; import java.util.Arrays; /** @@ -32,20 +33,19 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> PSDHeader // TODO: How do I specify that the header is required? - addElement("PSDHeader", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); + addElement("Header", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); - // TODO: Do the first two make sense? -// addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); - addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1")); + addAttribute("Header", "type", DATATYPE_STRING, false, "PSD", Arrays.asList("PSD", "PSB")); + addAttribute("Header", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1")); - addAttribute("PSDHeader", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); + addAttribute("Header", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); // rows? - addAttribute("PSDHeader", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true); + addAttribute("Header", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true); // columns? - addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); - addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); + addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); + addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); // TODO: Consider using more readable names?! - addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.COLOR_MODES)); + addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES)); /* Contains the required data to define the color mode. @@ -85,22 +85,46 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> ImageResources -> AlphaChannelInfo addElement("AlphaChannelInfo", "ImageResources", 0, Integer.MAX_VALUE); // The format probably does not support that many layers.. addElement("Name", "AlphaChannelInfo", CHILD_POLICY_EMPTY); - addAttribute("Name", "value", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); + addAttribute("Name", "value", DATATYPE_STRING, true, null); // root -> ImageResources -> DisplayInfo addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY); // TODO: Consider using human readable strings // TODO: Limit values (0-8, 10, 11, 3000) - addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null); + addAttribute("DisplayInfo", "colorSpace", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_CS)); addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4); addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true); // TODO: Consider using human readable strings - addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS)); + addAttribute("DisplayInfo", "kind", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS)); - // root -> ImageResources -> EXIF1Data - addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL); + // root -> ImageResources -> EXIF + addElement("EXIF", "ImageResources", CHILD_POLICY_EMPTY); + addObjectValue("EXIF", PSDEXIF1Data.Directory.class, true, null); // TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?) + // root -> ImageResources -> GridAndGuideInfo + addElement("GridAndGuideInfo", "ImageResources", 0, Integer.MAX_VALUE); + addAttribute("GridAndGuideInfo", "version", DATATYPE_INTEGER, false, "1"); + addAttribute("GridAndGuideInfo", "verticalGridCycle", DATATYPE_INTEGER, false, "576"); + addAttribute("GridAndGuideInfo", "horizontalGridCycle", DATATYPE_INTEGER, false, "576"); + addElement("Guide", "GridAndGuideInfo", CHILD_POLICY_EMPTY); + addAttribute("Guide", "location", DATATYPE_INTEGER, true, null, "0", Integer.toString(Integer.MAX_VALUE), true, true); + addAttribute("Guide", "orientation", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.GUIDE_ORIENTATIONS)); + + // root -> ImageResources -> ICCProfile + addElement("ICCProfile", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("ICCProfile", "colorSpaceType", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.JAVA_CS)); + + // root -> ImageResources -> IPTC + addElement("IPTC", "ImageResources", CHILD_POLICY_EMPTY); + addObjectValue("IPTC", PSDIPTCData.Directory.class, true, null); + // TODO: Incorporate IPTC metadata here somehow... (or treat as opaque bytes?) + + // root -> ImageResources -> PixelAspectRatio + addElement("PixelAspectRatio", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("PixelAspectRatio", "version", DATATYPE_STRING, false, "1"); + addAttribute("PixelAspectRatio", "aspectRatio", DATATYPE_DOUBLE, true, null, "0", Double.toString(Double.POSITIVE_INFINITY), true, false); + // root -> ImageResources -> PrintFlags addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY); addBooleanAttribute("PrintFlags", "labels", false, false); @@ -114,29 +138,53 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> ImageResources -> PrintFlagsInformation addElement("PrintFlagsInformation", "ImageResources", CHILD_POLICY_EMPTY); - addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, true, null); + addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, false, "1"); addBooleanAttribute("PrintFlagsInformation", "cropMarks", false, false); - addAttribute("PrintFlagsInformation", "field", DATATYPE_INTEGER, true, null); + addAttribute("PrintFlagsInformation", "field", DATATYPE_INTEGER, true, "0"); addAttribute("PrintFlagsInformation", "bleedWidth", DATATYPE_INTEGER, true, null, "0", String.valueOf(Long.MAX_VALUE), true, true); // TODO: LONG??! addAttribute("PrintFlagsInformation", "bleedScale", DATATYPE_INTEGER, true, null, "0", String.valueOf(Integer.MAX_VALUE), true, true); + // root -> ImageResources -> PrintScale + addElement("PrintScale", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("PrintScale", "style", DATATYPE_STRING, false, null, Arrays.asList(PSDMetadata.PRINT_SCALE_STYLES)); + addAttribute("PrintScale", "xLocation", DATATYPE_FLOAT, true, null); + addAttribute("PrintScale", "yLocation", DATATYPE_FLOAT, true, null); + addAttribute("PrintScale", "scale", DATATYPE_FLOAT, true, null); + // root -> ImageResources -> ResolutionInfo addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY); addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null); - // TODO: Or use string and more friendly names? "pixels/inch"/"pixels/cm" and "inch"/"cm"/"pt"/"pica"/"column" - addAttribute("ResolutionInfo", "hResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); - addAttribute("ResolutionInfo", "widthUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5")); + addAttribute("ResolutionInfo", "hResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS)); + addAttribute("ResolutionInfo", "widthUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS)); addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null); - // TODO: Or use more friendly names? - addAttribute("ResolutionInfo", "vResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); - addAttribute("ResolutionInfo", "heightUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5")); + addAttribute("ResolutionInfo", "vResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS)); + addAttribute("ResolutionInfo", "heightUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS)); - // ??? addElement("Thumbnail", "ImageResources", CHILD_POLICY_CHOICE); + // root -> ImageResources -> UnicodeAlphaNames + addElement("UnicodeAlphaNames", "ImageResources", 0, Integer.MAX_VALUE); + addChildElement("UnicodeAlphaNames", "Name"); // TODO: Does this really work? - // root -> ImageResources -> XMPData - addElement("XMPData", "ImageResources", CHILD_POLICY_CHOICE); + + // root -> ImageResources -> VersionInfo + addElement("VersionInfo", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("VersionInfo", "version", DATATYPE_INTEGER, false, "1"); + addBooleanAttribute("VersionInfo", "hasRealMergedData", false, false); + addAttribute("VersionInfo", "writer", DATATYPE_STRING, true, null); + addAttribute("VersionInfo", "reader", DATATYPE_STRING, true, null); + addAttribute("VersionInfo", "fileVersion", DATATYPE_INTEGER, true, "1"); + + // root -> ImageResources -> Thumbnail + addElement("Thumbnail", "ImageResources", CHILD_POLICY_EMPTY); + addObjectValue("Thumbnail", BufferedImage.class, true, null); + + // root -> ImageResources -> UnicodeAlphaName + addElement("UnicodeAlphaName", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("UnicodeAlphaName", "value", DATATYPE_STRING, true, null); + + // root -> ImageResources -> XMP + addElement("XMP", "ImageResources", CHILD_POLICY_CHOICE); // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) - addObjectValue("XMPData", Document.class, true, null); + addObjectValue("XMP", Document.class, true, null); // TODO: Layers //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPixelAspectRatio.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPixelAspectRatio.java new file mode 100644 index 00000000..612e309a --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPixelAspectRatio.java @@ -0,0 +1,27 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +/** + * PSDPixelAspectRatio + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDPixelAspectRatio.java,v 1.0 Nov 7, 2009 8:23:09 PM haraldk Exp$ + */ +final class PSDPixelAspectRatio extends PSDImageResource { + // 4 bytes (version = 1), 8 bytes double, x / y of a pixel + int mVersion; + double mAspect; + + PSDPixelAspectRatio(final short pId, final ImageInputStream pInput) throws IOException { + super(pId, pInput); + } + + @Override + protected void readData(final ImageInputStream pInput) throws IOException { + mVersion = pInput.readInt(); + mAspect = pInput.readDouble(); + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java index bb802354..606fd156 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java @@ -11,14 +11,14 @@ import java.io.IOException; * @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$ */ final class PSDPrintFlags extends PSDImageResource { - private boolean mLabels; - private boolean mCropMasks; - private boolean mColorBars; - private boolean mRegistrationMarks; - private boolean mNegative; - private boolean mFlip; - private boolean mInterpolate; - private boolean mCaption; + boolean mLabels; + boolean mCropMasks; + boolean mColorBars; + boolean mRegistrationMarks; + boolean mNegative; + boolean mFlip; + boolean mInterpolate; + boolean mCaption; PSDPrintFlags(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java index c3aa31d3..d5de5eb2 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java @@ -11,11 +11,11 @@ import java.io.IOException; * @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$ */ final class PSDPrintFlagsInformation extends PSDImageResource { - private int mVersion; - private boolean mCropMasks; - private int mField; - private long mBleedWidth; - private int mBleedScale; + int mVersion; + boolean mCropMasks; + int mField; + long mBleedWidth; + int mBleedScale; PSDPrintFlagsInformation(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -25,7 +25,7 @@ final class PSDPrintFlagsInformation extends PSDImageResource { protected void readData(final ImageInputStream pInput) throws IOException { mVersion = pInput.readUnsignedShort(); mCropMasks = pInput.readBoolean(); - mField = pInput.readUnsignedByte(); + mField = pInput.readUnsignedByte(); // TODO: Is this really pad? mBleedWidth = pInput.readUnsignedInt(); mBleedScale = pInput.readUnsignedShort(); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintScale.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintScale.java new file mode 100644 index 00000000..35d14b1f --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintScale.java @@ -0,0 +1,35 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +/** + * PSDPrintScale + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDPrintScale.java,v 1.0 Nov 7, 2009 9:41:17 PM haraldk Exp$ + */ +final class PSDPrintScale extends PSDImageResource { + // 2 bytes style (0 = centered, 1 = size to fit, 2 = user defined). + // 4 bytes x location (floating point). + // 4 bytes y location (floating point). + // 4 bytes scale (floating point) + + short mStyle; + float mXLocation; + float mYlocation; + float mScale; + + PSDPrintScale(final short pId, final ImageInputStream pInput) throws IOException { + super(pId, pInput); + } + + @Override + protected void readData(final ImageInputStream pInput) throws IOException { + mStyle = pInput.readShort(); + mXLocation = pInput.readFloat(); + mYlocation = pInput.readFloat(); + mScale = pInput.readFloat(); + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java new file mode 100644 index 00000000..12c927e9 --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java @@ -0,0 +1,33 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * PSDUnicodeAlphaNames + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: PSDUnicodeAlphaNames.java,v 1.0 Nov 7, 2009 9:16:56 PM haraldk Exp$ + */ +final class PSDUnicodeAlphaNames extends PSDImageResource { + List mNames; + + PSDUnicodeAlphaNames(final short pId, final ImageInputStream pInput) throws IOException { + super(pId, pInput); + } + + @Override + protected void readData(final ImageInputStream pInput) throws IOException { + mNames = new ArrayList(); + + long left = mSize; + while (left > 0) { + String name = PSDUtil.readUTF16String(pInput); + mNames.add(name); + left -= name.length() * 2 + 4; + } + } +} diff --git a/twelvemonkeys-imageio/psd/todo.txt b/twelvemonkeys-imageio/psd/todo.txt index efe9ceee..c7d6d53c 100755 --- a/twelvemonkeys-imageio/psd/todo.txt +++ b/twelvemonkeys-imageio/psd/todo.txt @@ -2,4 +2,5 @@ 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 -PSDImageWriter \ No newline at end of file +Support for Photoshop specific TIFF tags (extension for TIFFImageReader)? +PSDImageWriter? \ No newline at end of file From dbca2fc0991e1fd1678a610a3da3039f2e70179f Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:40:55 +0100 Subject: [PATCH 05/21] Minor changes in ANNO chunk written. Clean-up in reader. --- .../twelvemonkeys/imageio/plugins/iff/IFFImageReader.java | 8 +------- .../twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java b/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java index 34d309af..dd06892f 100755 --- a/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java +++ b/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageReader.java @@ -94,13 +94,7 @@ public class IFFImageReader extends ImageReaderBase { // http://home.comcast.net/~erniew/lwsdk/docs/filefmts/ilbm.html // http://www.fileformat.info/format/iff/spec/7866a9f0e53c42309af667c5da3bd426/view.htm // - Contains definitions of some "new" chunks, as well as alternative FORM types - - // TODO: One other existing deep bit ordering that you may encounter is the 21-bit - // NewTek format. - // - // NewTek deep ILBM bit ordering: - // saved first ------------------------------------------------------> saved last - // R7 G7 B7 R6 G6 B6 R5 G5 B5 R4 G4 B4 R3 G3 B3 R2 G2 B2 R1 G1 B1 R0 G0 B0 + // http://amigan.1emu.net/reg/iff.html private BMHDChunk mHeader; private CMAPChunk mColorMap; diff --git a/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java b/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java index e1e03272..62e9bb08 100755 --- a/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java +++ b/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/IFFImageWriter.java @@ -175,7 +175,7 @@ public class IFFImageWriter extends ImageWriterBase { private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException { // Annotation ANNO chunk, 8 + annoData.length bytes - String annotation = "Written by " + getOriginatingProvider().getDescription(null); + String annotation = "Written by " + getOriginatingProvider().getDescription(null) + " by " + getOriginatingProvider().getVendorName(); GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes()); ColorModel cm = pImage.getColorModel(); From ceca94135b668902a3bdfc46296dd384a924b9ca Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:42:10 +0100 Subject: [PATCH 06/21] Added some TODOs.. --- .../src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java | 1 + .../src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java | 1 + 2 files changed, 2 insertions(+) diff --git a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java index 07ae7855..5cd672bf 100644 --- a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java +++ b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java @@ -60,6 +60,7 @@ public final class DecoderStream extends FilterInputStream { * @see java.io.FilterInputStream#in */ public DecoderStream(final InputStream pStream, final Decoder pDecoder) { + // TODO: Let the decoder decide preferred buffer size this(pStream, pDecoder, 1024); } diff --git a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java index 58e07e2a..b50556f1 100644 --- a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java +++ b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java @@ -107,6 +107,7 @@ public final class PackBitsDecoder implements Decoder { int read = 0; final int max = pBuffer.length; + // TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream... while (read < max) { int n; From 69d77071b952e07e00ebcd9a2bd989ada2eab116 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:43:16 +0100 Subject: [PATCH 07/21] Allows passing object array to toCSVString method. --- .../src/main/java/com/twelvemonkeys/lang/StringUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java index c1ecda91..b7dc34e1 100755 --- a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java +++ b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java @@ -1572,7 +1572,7 @@ public final class StringUtil { * @param pStringArray the string array * @return A string of comma-separated values */ - public static String toCSVString(String[] pStringArray) { + public static String toCSVString(Object[] pStringArray) { return toCSVString(pStringArray, ", "); } @@ -1584,7 +1584,7 @@ public final class StringUtil { * @return string of delimiter separated values * @throws IllegalArgumentException if {@code pDelimiterString == null} */ - public static String toCSVString(String[] pStringArray, String pDelimiterString) { + public static String toCSVString(Object[] pStringArray, String pDelimiterString) { if (pStringArray == null) { return ""; } From b7124585c5dcf976ddfe0a30c68842d565c386b0 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:43:39 +0100 Subject: [PATCH 08/21] Removed a misleading comment. --- .../src/main/java/com/twelvemonkeys/util/BASE64.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BASE64.java b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BASE64.java index 9b056e10..ad3d128b 100755 --- a/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BASE64.java +++ b/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/BASE64.java @@ -115,13 +115,7 @@ public class BASE64 { return buf.toString(); } - /** - * Quick implementation, using the undocumented - * {@code sun.misc.BASE64Decoder.decodeBuffer(String)}. - */ - public static byte[] decode(String pData) throws java.io.IOException { - //return DECODER.decodeBuffer(pData); - + public static byte[] decode(String pData) throws IOException { InputStream in = new DecoderStream(new ByteArrayInputStream(pData.getBytes()), new Base64Decoder()); ByteArrayOutputStream bytes = new FastByteArrayOutputStream(pData.length() * 3); FileUtil.copy(in, bytes); @@ -131,7 +125,7 @@ public class BASE64 { //private final static sun.misc.BASE64Decoder DECODER = new sun.misc.BASE64Decoder(); - public static void main(String[] pArgs) throws java.io.IOException { + public static void main(String[] pArgs) throws IOException { if (pArgs.length == 1) { System.out.println(encode(pArgs[0].getBytes())); } From d1cc3deec164d0375e1ee778846287330d42d25c Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:44:58 +0100 Subject: [PATCH 09/21] Bumping versions to 2.3-SNAPSHOT. --- twelvemonkeys-servlet/pom.xml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/twelvemonkeys-servlet/pom.xml b/twelvemonkeys-servlet/pom.xml index 46e302db..299f51f3 100644 --- a/twelvemonkeys-servlet/pom.xml +++ b/twelvemonkeys-servlet/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.twelvemonkeys twelvemonkeys-servlet - 2.2 + 2.3-SNAPSHOT TwelveMonkeys Servlet @@ -89,6 +89,22 @@ UTF-8 + + + org.apache.maven.plugins + maven-jar-plugin + 2.2 + + + + ${project.name} + TwelveMonkeys + ${project.version} + http://github.com/haraldk/TwelveMonkeys + + + + From 2ab9cbadee930954a23aab1853e2f48cb64845ab Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 8 Nov 2009 14:45:56 +0100 Subject: [PATCH 10/21] Some minor clean-up. Added package info to support packages. --- .../java/com/twelvemonkeys/imageio/spi/package-info.java | 4 ++++ .../java/com/twelvemonkeys/imageio/stream/package-info.java | 4 ++++ .../java/com/twelvemonkeys/imageio/util/package-info.java | 4 ++++ twelvemonkeys-imageio/pom.xml | 5 ----- twelvemonkeys-imageio/thumbsdb/todo.txt | 2 ++ 5 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java create mode 100644 twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java create mode 100644 twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java new file mode 100644 index 00000000..cc3d2c1b --- /dev/null +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/spi/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides helper classes for service provider implementations. + */ +package com.twelvemonkeys.imageio.spi; \ No newline at end of file diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java new file mode 100644 index 00000000..71618df3 --- /dev/null +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various additional stream implementations. + */ +package com.twelvemonkeys.imageio.stream; \ No newline at end of file diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java new file mode 100644 index 00000000..87091685 --- /dev/null +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/util/package-info.java @@ -0,0 +1,4 @@ +/** + * Provides various common utilities for reading and writing images. + */ +package com.twelvemonkeys.imageio.util; \ No newline at end of file diff --git a/twelvemonkeys-imageio/pom.xml b/twelvemonkeys-imageio/pom.xml index 3fcd3467..cd33e3fc 100644 --- a/twelvemonkeys-imageio/pom.xml +++ b/twelvemonkeys-imageio/pom.xml @@ -113,16 +113,11 @@ - org.apache.maven.plugins maven-jar-plugin 2.2 - - - - ${project.name} TwelveMonkeys diff --git a/twelvemonkeys-imageio/thumbsdb/todo.txt b/twelvemonkeys-imageio/thumbsdb/todo.txt index ca61a0e0..f91e7309 100755 --- a/twelvemonkeys-imageio/thumbsdb/todo.txt +++ b/twelvemonkeys-imageio/thumbsdb/todo.txt @@ -11,4 +11,6 @@ - http://vinetto.sourceforge.net/docs.html - We probably want to support all of these +- Thumbnail (API) support? Does it make sense? It's all thumbnails.. + DONE: From effd80d42fc5929bba2d8d1cfb3f358f02486349 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 9 Nov 2009 20:17:59 +0100 Subject: [PATCH 11/21] - Added SubImageInputStream with test case. - Changed BufferedImageInputStream to return -1 instead of the hack throwing of unchecked IOException. --- .../stream/BufferedImageInputStream.java | 9 +- .../imageio/stream/SubImageInputStream.java | 102 +++++++++++ .../stream/SubImageInputStreamTestCase.java | 168 ++++++++++++++++++ 3 files changed, 272 insertions(+), 7 deletions(-) create mode 100644 twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java create mode 100644 twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java index a3ca35b5..04111684 100644 --- a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/BufferedImageInputStream.java @@ -172,14 +172,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme try { return mStream.length(); } - catch (IOException e) { - throw unchecked(e, RuntimeException.class); + catch (IOException ignore) { } - } - @SuppressWarnings({"unchecked", "UnusedDeclaration"}) - private T unchecked(IOException pExcption, Class pClass) { - // Ugly hack to fool the compiler.. - return (T) pExcption; + return -1; } } diff --git a/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java new file mode 100644 index 00000000..2c4ce03c --- /dev/null +++ b/twelvemonkeys-imageio/core/src/main/java/com/twelvemonkeys/imageio/stream/SubImageInputStream.java @@ -0,0 +1,102 @@ +package com.twelvemonkeys.imageio.stream; + +import com.twelvemonkeys.lang.Validate; + +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageInputStreamImpl; +import java.io.IOException; + +/** + * A wrapper for {@link ImageInputStream} to limit the number of bytes that can be read. + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: SubImageInputStream.java,v 1.0 Nov 8, 2009 2:50:58 PM haraldk Exp$ + */ +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; + + /** + * Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream. + * + * @param pStream the underlying stream + * @param pLength the maximum length to read from the stream. + * Note that {@code pStream} may contain less than this maximum number of bytes. + * + * @throws IOException if {@code pStream}'s position can't be determined. + * @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0} + */ + public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException { + Validate.notNull(pStream, "stream"); + if (pLength < 0) { + throw new IllegalArgumentException("length < 0"); + } + + mStream = pStream; + mStartPos = pStream.getStreamPosition(); + mLength = pLength; + } + + public int read() throws IOException { + if (streamPos >= mLength) { // Local EOF + return -1; + } + else { + int read = mStream.read(); + + if (read >= 0) { + streamPos++; + } + + return read; + } + } + + public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException { + if (streamPos >= mLength) { // 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); + + if (count >= 0) { + streamPos += count; + } + + return count; + } + + @Override + public long length() { + try { + long length = mStream.length(); + return length < 0 ? -1 : Math.min(length - mStartPos, mLength); + } + catch (IOException ignore) { + } + + return -1; + } + + @Override + public void seek(final long pPosition) throws IOException { + if (pPosition < getFlushedPosition()) { + throw new IndexOutOfBoundsException("pos < flushedPosition"); + } + + mStream.seek(mStartPos + pPosition); + streamPos = pPosition; + } + + @SuppressWarnings({"FinalizeDoesntCallSuperFinalize"}) + @Override + protected void finalize() throws Throwable { + // Empty finalizer (for improved performance; no need to call super.finalize() in this case) + } +} diff --git a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java new file mode 100644 index 00000000..665582d8 --- /dev/null +++ b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/stream/SubImageInputStreamTestCase.java @@ -0,0 +1,168 @@ +package com.twelvemonkeys.imageio.stream; + +import junit.framework.TestCase; + +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageInputStreamImpl; +import javax.imageio.stream.MemoryCacheImageInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.Random; + +/** + * SubImageInputStreamTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: SubImageInputStreamTestCase.java,v 1.0 Nov 8, 2009 3:03:32 PM haraldk Exp$ + */ +public class SubImageInputStreamTestCase extends TestCase { + // TODO: Extract super test case for all stream tests + private final Random mRandom = new Random(837468l); + + private ImageInputStream createStream(final int pSize) { + byte[] bytes = new byte[pSize]; + + mRandom.nextBytes(bytes); + + return new MemoryCacheImageInputStream(new ByteArrayInputStream(bytes)) { + @Override + public long length() { + return pSize; + } + }; + } + + public void testCreateNullStream() throws IOException { + try { + new SubImageInputStream(null, 1); + fail("Expected IllegalArgumentException with null stream"); + } + catch (IllegalArgumentException e) { + } + } + + public void testCreateNegativeLength() throws IOException { + try { + new SubImageInputStream(createStream(0), -1); + fail("Expected IllegalArgumentException with negative length"); + } + catch (IllegalArgumentException e) { + } + } + + public void testCreate() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(11), 7); + + assertEquals(0, stream.getStreamPosition()); + assertEquals(7, stream.length()); + } + + public void testWraphBeyondWrappedLength() throws IOException { + SubImageInputStream stream = new SubImageInputStream(createStream(5), 6); + assertEquals(5, stream.length()); + } + + public void testWrapUnknownLength() throws IOException { + SubImageInputStream stream = new SubImageInputStream(new ImageInputStreamImpl() { + @Override + public int read() throws IOException { + throw new UnsupportedOperationException("Method read not implemented"); + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new UnsupportedOperationException("Method read not implemented"); + } + + @Override + public long length() { + return -1; + } + }, 6); + + assertEquals(-1, stream.length()); + } + + public void testRead() throws IOException { + ImageInputStream wrapped = createStream(42); + + wrapped.skipBytes(13); + + ImageInputStream stream = new SubImageInputStream(wrapped, 27); + + assertEquals(0, stream.getStreamPosition()); + assertEquals(27, stream.length()); + + stream.read(); + assertEquals(1, stream.getStreamPosition()); + assertEquals(27, stream.length()); + + stream.readFully(new byte[11]); + assertEquals(12, stream.getStreamPosition()); + assertEquals(27, stream.length()); + + assertEquals(25, wrapped.getStreamPosition()); + } + + public void testReadResetRead() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(32), 16); + stream.mark(); + + byte[] first = new byte[16]; + stream.readFully(first); + + stream.reset(); + + byte[] second = new byte[16]; + stream.readFully(second); + + assertTrue(Arrays.equals(first, second)); + } + + public void testSeekNegative() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + try { + stream.seek(-2); + fail("Expected IndexOutOfBoundsException"); + } + catch (IndexOutOfBoundsException expected) { + } + + assertEquals(0, stream.getStreamPosition()); + } + + public void testSeekBeforeFlushedPos() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + stream.seek(3); + stream.flushBefore(3); + + assertEquals(3, stream.getStreamPosition()); + + try { + stream.seek(0); + fail("Expected IndexOutOfBoundsException"); + } + catch (IndexOutOfBoundsException expected) { + } + + assertEquals(3, stream.getStreamPosition()); + } + + public void testSeekAfterEOF() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + stream.seek(6); + + assertEquals(-1, stream.read()); + } + + public void testSeek() throws IOException { + ImageInputStream stream = new SubImageInputStream(createStream(7), 5); + stream.seek(5); + assertEquals(5, stream.getStreamPosition()); + + stream.seek(1); + assertEquals(1, stream.getStreamPosition()); + } +} From e8a4cc048c884f89cf51b0c493b99f5e7e34cc2b Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Wed, 11 Nov 2009 21:45:53 +0100 Subject: [PATCH 12/21] Revert "Work in progress for PSD metadata support:" This reverts commit 0d41db32cf91ec87b5070e69ee683c49f51b0fa9. --- .../imageio/plugins/psd/PSD.java | 2 +- .../plugins/psd/PSDAlphaChannelInfo.java | 4 +- .../imageio/plugins/psd/PSDDisplayInfo.java | 25 +- .../imageio/plugins/psd/PSDEXIF1Data.java | 21 +- .../imageio/plugins/psd/PSDHeader.java | 5 +- .../imageio/plugins/psd/PSDImageReader.java | 82 ++--- .../imageio/plugins/psd/PSDImageResource.java | 23 +- .../imageio/plugins/psd/PSDLayerInfo.java | 12 +- .../imageio/plugins/psd/PSDMetadata.java | 303 ++++-------------- .../plugins/psd/PSDMetadataFormat.java | 29 +- .../imageio/plugins/psd/PSDPrintFlags.java | 16 +- .../plugins/psd/PSDPrintFlagsInformation.java | 2 +- .../imageio/plugins/psd/PSDThumbnail.java | 2 +- .../imageio/plugins/psd/PSDUtil.java | 29 +- .../imageio/plugins/psd/PSDVersionInfo.java | 57 ---- 15 files changed, 162 insertions(+), 450 deletions(-) delete mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java index 55a3ba45..30c77218 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java @@ -101,7 +101,7 @@ interface PSD { int COMPRESSION_ZIP = 2; /** ZIP compression with prediction */ - int COMPRESSION_ZIP_PREDICTION = 3; + int COMPRESSION_ZIP_PREDICTON = 3; // Color Modes /** Bitmap (monochrome) */ diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java index ed0b4edd..7d3ae079 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java @@ -48,11 +48,11 @@ class PSDAlphaChannelInfo extends PSDImageResource { } @Override - protected void readData(final ImageInputStream pInput) throws IOException { + protected void readData(ImageInputStream pInput) throws IOException { mNames = new ArrayList(); long left = mSize; while (left > 0) { - String name = PSDUtil.readPascalString(pInput); + String name = PSDUtil.readPascalStringByte(pInput); mNames.add(name); left -= name.length() + 1; } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java index f74e5a7b..d4d41347 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java @@ -40,22 +40,7 @@ import java.io.IOException; * @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$ */ class PSDDisplayInfo extends PSDImageResource { - // TODO: Size of this struct should be 14.. Does not compute... Something bogus here - - // ColorSpace definitions: - // PSD_CS_RGB = 0, /* RGB */ - // PSD_CS_HSB = 1, /* Hue, Saturation, Brightness */ - // PSD_CS_CMYK = 2, /* CMYK */ - // PSD_CS_PANTONE = 3, /* Pantone matching system (Lab)*/ - // PSD_CS_FOCOLTONE = 4, /* Focoltone colour system (CMYK)*/ - // PSD_CS_TRUMATCH = 5, /* Trumatch color (CMYK)*/ - // PSD_CS_TOYO = 6, /* Toyo 88 colorfinder 1050 (Lab)*/ - // PSD_CS_LAB = 7, /* L*a*b*/ - // PSD_CS_GRAYSCALE = 8, /* Grey scale */ - // PSD_CS_HKS = 10, /* HKS colors (CMYK)*/ - // PSD_CS_DIC = 11, /* DIC color guide (Lab)*/ - // PSD_CS_ANPA = 3000, /* Anpa color (Lab)*/ - + // TODO: Size of this struct should be 14.. Does not compute... Something bogus here //typedef _DisplayInfo //{ // WORD ColorSpace; @@ -65,10 +50,10 @@ class PSDDisplayInfo extends PSDImageResource { // BYTE Padding; /* Always zero */ //} DISPLAYINFO; - int mColorSpace; - short[] mColors; - short mOpacity; - byte mKind; + private int mColorSpace; + private short[] mColors; + private short mOpacity; + private byte mKind; PSDDisplayInfo(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java index 17f181aa..a2f6f486 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java @@ -10,7 +10,6 @@ import java.io.IOException; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; /** @@ -68,8 +67,8 @@ final class PSDEXIF1Data extends PSDImageResource { } // TIFF Image file directory (IFD) - static class Directory implements Iterable { - private List mEntries = new ArrayList(); + private static class Directory { + List mEntries = new ArrayList(); private Directory() {} @@ -91,20 +90,6 @@ final class PSDEXIF1Data extends PSDImageResource { return directory; } - public Entry get(int pTag) { - for (Entry entry : mEntries) { - if (entry.mTag == pTag) { - return entry; - } - } - - return null; - } - - public Iterator iterator() { - return mEntries.iterator(); - } - @Override public String toString() { return String.format("Directory%s", mEntries); @@ -112,7 +97,7 @@ final class PSDEXIF1Data extends PSDImageResource { } // TIFF IFD Entry - static class Entry { + private static class Entry { private static final int EXIF_IFD = 0x8769; private final static String[] TYPE_NAMES = { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java index 77e6659a..6f26d147 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDHeader.java @@ -28,9 +28,6 @@ package com.twelvemonkeys.imageio.plugins.psd; -import org.w3c.dom.Node; - -import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; import javax.imageio.IIOException; import java.io.IOException; @@ -64,7 +61,7 @@ class PSDHeader { final short mBits; final short mMode; - PSDHeader(final ImageInputStream pInput) throws IOException { + PSDHeader(ImageInputStream pInput) throws IOException { int signature = pInput.readInt(); if (signature != PSD.SIGNATURE_8BPS) { throw new IIOException("Not a PSD document, expected signature \"8BPS\": \"" + PSDUtil.intToStr(signature) + "\" (0x" + Integer.toHexString(signature) + ")"); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 5eb7112e..d2f05792 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -69,12 +69,11 @@ import java.util.List; // See http://www.adobeforums.com/webx?14@@.3bc381dc/0 public class PSDImageReader extends ImageReaderBase { private PSDHeader mHeader; -// private PSDColorData mColorData; -// private List mImageResources; -// private PSDGlobalLayerMask mGlobalLayerMask; -// private List mLayerInfo; + private PSDColorData mColorData; + private List mImageResources; + private PSDGlobalLayerMask mGlobalLayerMask; + private List mLayerInfo; private ICC_ColorSpace mColorSpace; - protected PSDMetadata mMetadata; protected PSDImageReader(final ImageReaderSpi pOriginatingProvider) { super(pOriginatingProvider); @@ -82,9 +81,8 @@ public class PSDImageReader extends ImageReaderBase { protected void resetMembers() { mHeader = null; -// mColorData = null; -// mImageResources = null; - mMetadata = null; + mColorData = null; + mImageResources = null; mColorSpace = null; } @@ -124,7 +122,7 @@ public class PSDImageReader extends ImageReaderBase { case PSD.COLOR_MODE_INDEXED: // TODO: 16 bit indexed?! Does it exist? if (mHeader.mChannels == 1 && mHeader.mBits == 8) { - return IndexedImageTypeSpecifier.createFromIndexColorModel(mMetadata.mColorData.getIndexColorModel()); + return IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel()); } throw new IIOException( @@ -266,7 +264,7 @@ public class PSDImageReader extends ImageReaderBase { if (mColorSpace == null) { ICC_Profile profile = null; - for (PSDImageResource resource : mMetadata.mImageResources) { + for (PSDImageResource resource : mImageResources) { if (resource instanceof ICCProfile) { profile = ((ICCProfile) resource).getProfile(); break; @@ -335,8 +333,6 @@ public class PSDImageReader extends ImageReaderBase { int[] byteCounts = null; int compression = mImageInput.readShort(); - // TODO: Need to make sure compression is set in metadata, even without reading the image data! - mMetadata.mCompression = compression; switch (compression) { case PSD.COMPRESSION_NONE: @@ -350,7 +346,7 @@ public class PSDImageReader extends ImageReaderBase { break; case PSD.COMPRESSION_ZIP: // TODO: Could probably use the ZIPDecoder (DeflateDecoder) here.. - case PSD.COMPRESSION_ZIP_PREDICTION: + case PSD.COMPRESSION_ZIP_PREDICTON: // TODO: Need to find out if the normal java.util.zip can handle this... // Could be same as PNG prediction? Read up... throw new IIOException("ZIP compression not supported yet"); @@ -697,9 +693,6 @@ public class PSDImageReader extends ImageReaderBase { if (mHeader == null) { mHeader = new PSDHeader(mImageInput); - mMetadata = new PSDMetadata(); - mMetadata.mHeader = mHeader; - /* Contains the required data to define the color mode. @@ -712,7 +705,7 @@ 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); + mColorData = new PSDColorData(mImageInput); } else { // TODO: We need to store the duotone spec if we decide to create a writer... @@ -736,14 +729,14 @@ public class PSDImageReader extends ImageReaderBase { long length = mImageInput.readUnsignedInt(); if (pParseData && length > 0) { - if (mMetadata.mImageResources == null) { - mMetadata.mImageResources = new ArrayList(); + if (mImageResources == null) { + mImageResources = new ArrayList(); long expectedEnd = mImageInput.getStreamPosition() + length; while (mImageInput.getStreamPosition() < expectedEnd) { // TODO: Have PSDImageResources defer actual parsing? (Just store stream offsets) PSDImageResource resource = PSDImageResource.read(mImageInput); - mMetadata.mImageResources.add(resource); + mImageResources.add(resource); } if (mImageInput.getStreamPosition() != expectedEnd) { @@ -777,7 +770,7 @@ public class PSDImageReader extends ImageReaderBase { for (int i = 0; i < layerInfos.length; i++) { layerInfos[i] = new PSDLayerInfo(mImageInput); } - mMetadata.mLayerInfo = Arrays.asList(layerInfos); + mLayerInfo = Arrays.asList(layerInfos); // TODO: Clean-up mImageInput.mark(); @@ -790,9 +783,9 @@ public class PSDImageReader extends ImageReaderBase { BufferedImage layer = readLayerData(layerInfo, raw, imageType); // TODO: Don't show! Store in meta data somehow... -// if (layer != null) { -// showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString()); -// } + if (layer != null) { + showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString()); + } } long read = mImageInput.getStreamPosition() - pos; @@ -806,7 +799,7 @@ public class PSDImageReader extends ImageReaderBase { long layerMaskInfoLength = mImageInput.readUnsignedInt(); // System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength); if (layerMaskInfoLength > 0) { - mMetadata.mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput); + mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput); // System.out.println("mGlobalLayerMask: " + mGlobalLayerMask); } @@ -884,7 +877,7 @@ public class PSDImageReader extends ImageReaderBase { break; case PSD.COMPRESSION_ZIP: - case PSD.COMPRESSION_ZIP_PREDICTION: + case PSD.COMPRESSION_ZIP_PREDICTON: default: // Explicitly skipped above throw new AssertionError(String.format("Unsupported layer data. Compression: %d", compression)); @@ -992,19 +985,16 @@ public class PSDImageReader extends ImageReaderBase { readImageResources(true); readLayerAndMaskInfo(true); - // TODO: Need to make sure compression is set in metadata, even without reading the image data! - mMetadata.mCompression = mImageInput.readShort(); - -// mMetadata.mHeader = mHeader; -// mMetadata.mColorData = mColorData; -// mMetadata.mImageResources = mImageResources; - - return mMetadata; // TODO: clone if we change to mutable metadata + PSDMetadata metadata = new PSDMetadata(); + metadata.mHeader = mHeader; + metadata.mColorData = mColorData; + metadata.mImageResources = mImageResources; + return metadata; } @Override public IIOMetadata getImageMetadata(final int imageIndex, final String formatName, final Set nodeNames) throws IOException { - // TODO: It might make sense to overload this, as there's loads of meta data in the file + // TODO: This might make sense, as there's loads of meta data in the file return super.getImageMetadata(imageIndex, formatName, nodeNames); } @@ -1021,14 +1011,14 @@ public class PSDImageReader extends ImageReaderBase { List thumbnails = null; - if (mMetadata.mImageResources == null) { + if (mImageResources == null) { // TODO: Need flag here, to specify what resources to read... readImageResources(true); // TODO: Skip this, requires storing some stream offsets readLayerAndMaskInfo(false); } - for (PSDImageResource resource : mMetadata.mImageResources) { + for (PSDImageResource resource : mImageResources) { if (resource instanceof PSDThumbnail) { if (thumbnails == null) { thumbnails = new ArrayList(); @@ -1130,25 +1120,15 @@ public class PSDImageReader extends ImageReaderBase { // System.out.println("imageReader.mHeader: " + imageReader.mHeader); imageReader.readImageResources(true); - System.out.println("imageReader.mImageResources: " + imageReader.mMetadata.mImageResources); - System.out.println(); + System.out.println("imageReader.mImageResources: " + imageReader.mImageResources); imageReader.readLayerAndMaskInfo(true); - System.out.println("imageReader.mLayerInfo: " + imageReader.mMetadata.mLayerInfo); + System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo); // System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask); - System.out.println(); IIOMetadata metadata = imageReader.getImageMetadata(0); - Node node; - XMLSerializer serializer; - - node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); - serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); - serializer.serialize(node, true); - System.out.println(); - - node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME); - serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); + Node node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); + XMLSerializer serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); serializer.serialize(node, true); if (imageReader.hasThumbnails(0)) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java index 8dd3770a..5aa95625 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java @@ -28,8 +28,6 @@ package com.twelvemonkeys.imageio.plugins.psd; -import com.twelvemonkeys.lang.StringUtil; - import javax.imageio.stream.ImageInputStream; import javax.imageio.IIOException; import java.io.IOException; @@ -52,12 +50,6 @@ class PSDImageResource { mName = PSDUtil.readPascalString(pInput); - // Skip pad - int nameSize = mName.length() + 1; - if (nameSize % 2 != 0) { - pInput.readByte(); - } - mSize = pInput.readUnsignedInt(); readData(pInput); @@ -92,10 +84,7 @@ class PSDImageResource { protected StringBuilder toStringBuilder() { StringBuilder builder = new StringBuilder(getClass().getSimpleName()); - String fakeType = resourceTypeForId(mId); - if (fakeType != null) { - builder.append("(").append(fakeType).append(")"); - } + builder.append(resourceTypeForId(mId)); builder.append("[ID: 0x"); builder.append(Integer.toHexString(mId)); @@ -117,25 +106,23 @@ class PSDImageResource { case PSD.RES_THUMBNAIL_PS4: case PSD.RES_THUMBNAIL: case PSD.RES_ICC_PROFILE: - case PSD.RES_VERSION_INFO: case PSD.RES_EXIF_DATA_1: // case PSD.RES_EXIF_DATA_3: case PSD.RES_XMP_DATA: case PSD.RES_PRINT_FLAGS_INFORMATION: - return null; + return ""; default: try { for (Field field : PSD.class.getDeclaredFields()) { if (field.getName().startsWith("RES_") && field.getInt(null) == pId) { - String name = field.getName().substring(4); - return StringUtil.lispToCamel(name.replace("_", "-").toLowerCase(), true); + return "(" + field.getName().substring(4) + ")"; } } } catch (IllegalAccessException ignore) { } - return "unknown resource"; + return "(unknown resource)"; } } @@ -162,8 +149,6 @@ class PSDImageResource { return new PSDThumbnail(id, pInput); case PSD.RES_ICC_PROFILE: return new ICCProfile(id, pInput); - case PSD.RES_VERSION_INFO: - return new PSDVersionInfo(id, pInput); case PSD.RES_EXIF_DATA_1: return new PSDEXIF1Data(id, pInput); case PSD.RES_XMP_DATA: diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java index 56a62f26..da10552c 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java @@ -98,12 +98,14 @@ class PSDLayerInfo { mLayerName = PSDUtil.readPascalString(pInput); int layerNameSize = mLayerName.length() + 1; - - // Skip pad bytes for long word alignment + // readPascalString has already read pad byte for word alignment + if (layerNameSize % 2 != 0) { + layerNameSize++; + } + // Skip two more pad bytes if needed if (layerNameSize % 4 != 0) { - int skip = layerNameSize % 4; - pInput.skipBytes(skip); - layerNameSize += skip; + pInput.skipBytes(2); + layerNameSize += 2; } // TODO: There's some data skipped here... diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 69188525..a9755fad 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -1,20 +1,15 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.lang.StringUtil; -import com.twelvemonkeys.util.FilterIterator; -import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.xml.sax.InputSource; import javax.imageio.metadata.IIOInvalidTreeException; import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import java.awt.image.IndexColorModel; +import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; /** @@ -26,28 +21,16 @@ import java.util.List; */ public final class PSDMetadata extends IIOMetadata implements Cloneable { - // TODO: Decide on image/stream metadata... - static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0"; + static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_1.0"; static final String NATIVE_METADATA_FORMAT_CLASS_NAME = "com.twelvemonkeys.imageio.plugins.psd.PSDMetadataFormat"; + // TODO: Move fields from PSDImageReader (header, color map, resources, etc) here PSDHeader mHeader; PSDColorData mColorData; - int mCompression = -1; List mImageResources; PSDGlobalLayerMask mGlobalLayerMask; List mLayerInfo; - static final String[] COLOR_MODES = { - "MONOCHROME", "GRAYSCALE", "INDEXED", "RGB", "CMYK", null, null, "MULTICHANNEL", "DUOTONE", "LAB" - }; - - static final String[] DISPLAY_INFO_CS = { - "RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC", - null, // ... (until index 2999), - "ANPA" - }; - static final String[] DISPLAY_INFO_KINDS = {"selected", "protected"}; - protected PSDMetadata() { // TODO: Allow XMP, EXIF and IPTC as extra formats? super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); @@ -136,112 +119,8 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { } } - /// Native format support - private Node getNativeTree() { - IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME); - - root.appendChild(createHeaderNode()); - - if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { - root.appendChild(createPaletteNode()); - } - - if (mImageResources != null && !mImageResources.isEmpty()) { - root.appendChild(createImageResourcesNode()); - } - - return root; - } - - private Node createHeaderNode() { - IIOMetadataNode header = new IIOMetadataNode("PSDHeader"); - - header.setAttribute("version", "1"); - header.setAttribute("channels", Integer.toString(mHeader.mChannels)); - header.setAttribute("height", Integer.toString(mHeader.mHeight)); - header.setAttribute("width", Integer.toString(mHeader.mWidth)); - header.setAttribute("bits", Integer.toString(mHeader.mBits)); - header.setAttribute("mode", COLOR_MODES[mHeader.mMode]); - - return header; - } - - private Node createImageResourcesNode() { - IIOMetadataNode resource = new IIOMetadataNode("ImageResources"); - IIOMetadataNode node; - - for (PSDImageResource imageResource : mImageResources) { - // TODO: Always add name (if set) and id (as resourceId) to all nodes? - // Resource Id is useful for people with access to the PSD spec.. - - if (imageResource instanceof PSDAlphaChannelInfo) { - PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource; - - node = new IIOMetadataNode("AlphaChannelInfo"); - - for (String name : alphaChannelInfo.mNames) { - IIOMetadataNode nameNode = new IIOMetadataNode("Name"); - nameNode.setAttribute("value", name); - node.appendChild(nameNode); - } - - resource.appendChild(node); - } - else if (imageResource instanceof PSDDisplayInfo) { - PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource; - - node = new IIOMetadataNode("DisplayInfo"); - node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]); - - StringBuilder builder = new StringBuilder(); - for (short color : displayInfo.mColors) { - if (builder.length() > 0) { - builder.append(" "); - } - builder.append(Integer.toString(color)); - } - - node.setAttribute("colors", builder.toString()); - node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity)); - node.setAttribute("kind", DISPLAY_INFO_KINDS[displayInfo.mKind]); - - resource.appendChild(node); - } - else if (imageResource instanceof PSDXMPData) { - // TODO: Revise/rethink this... - PSDXMPData xmp = (PSDXMPData) imageResource; - - node = new IIOMetadataNode("XMPData"); - - try { - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document document = builder.parse(new InputSource(xmp.getData())); - - // Set the entire XMP document as user data - node.setUserObject(document); - } - catch (Exception e) { - e.printStackTrace(); - } - - resource.appendChild(node); - } - else { - // Generic resource.. - node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId)); - - resource.appendChild(node); - } - - - // TODO: More resources - - node.setAttribute("resourceId", Integer.toHexString(imageResource.mId)); - } - - return resource; + throw new UnsupportedOperationException("getNativeTree"); } /// Standard format support @@ -256,7 +135,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { switch (mHeader.mMode) { case PSD.COLOR_MODE_MONOCHROME: case PSD.COLOR_MODE_GRAYSCALE: - case PSD.COLOR_MODE_DUOTONE: // Rationale: Spec says treat as gray... + case PSD.COLOR_MODE_DUOTONE: // Rationale is spec says treat as gray... cs = "GRAY"; break; case PSD.COLOR_MODE_RGB: @@ -267,7 +146,8 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { cs = "CMYK"; break; case PSD.COLOR_MODE_MULTICHANNEL: - cs = getMultiChannelCS(mHeader.mChannels); + // TODO: FixMe + cs = "???"; break; case PSD.COLOR_MODE_LAB: cs = "Lab"; @@ -278,22 +158,42 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { node.setAttribute("name", cs); chroma_node.appendChild(node); - // TODO: Channels might be 5 for RGB + A + Mask... Probably not correct + // TODO: Channels might be 5 for RGB + A + Mask... node = new IIOMetadataNode("NumChannels"); node.setAttribute("value", Integer.toString(mHeader.mChannels)); chroma_node.appendChild(node); +// if (gAMA_present) { +// node = new IIOMetadataNode("Gamma"); +// node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); +// chroma_node.appendChild(node); +// } + // TODO: Check if this is correct with bitmap (monchrome) node = new IIOMetadataNode("BlackIsZero"); node.setAttribute("value", "true"); chroma_node.appendChild(node); if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { - node = createPaletteNode(); + node = new IIOMetadataNode("Palette"); + + IndexColorModel cm = mColorData.getIndexColorModel(); + for (int i = 0; i < cm.getMapSize(); i++) { + IIOMetadataNode entry = + new IIOMetadataNode("PaletteEntry"); + entry.setAttribute("index", Integer.toString(i)); + entry.setAttribute("red", + Integer.toString(cm.getRed(i))); + entry.setAttribute("green", + Integer.toString(cm.getGreen(i))); + entry.setAttribute("blue", + Integer.toString(cm.getBlue(i))); + + node.appendChild(entry); + } chroma_node.appendChild(node); } - // TODO: Hardcode background color to white? // if (bKGD_present) { // if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { // node = new IIOMetadataNode("BackgroundIndex"); @@ -319,59 +219,22 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { return chroma_node; } - private IIOMetadataNode createPaletteNode() { - IIOMetadataNode node = new IIOMetadataNode("Palette"); - IndexColorModel cm = mColorData.getIndexColorModel(); - - for (int i = 0; i < cm.getMapSize(); i++) { - IIOMetadataNode entry = new IIOMetadataNode("PaletteEntry"); - entry.setAttribute("index", Integer.toString(i)); - entry.setAttribute("red", Integer.toString(cm.getRed(i))); - entry.setAttribute("green", Integer.toString(cm.getGreen(i))); - entry.setAttribute("blue", Integer.toString(cm.getBlue(i))); - - node.appendChild(entry); - } - - return node; - } - - private String getMultiChannelCS(short pChannels) { - if (pChannels < 16) { - return Integer.toHexString(pChannels) + "CLR"; - } - - throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels"); - } - @Override protected IIOMetadataNode getStandardCompressionNode() { IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("CompressionTypeName"); - String compression; - switch (mCompression) { - case PSD.COMPRESSION_NONE: - compression = "none"; - break; - case PSD.COMPRESSION_RLE: - compression = "packbits"; - break; - case PSD.COMPRESSION_ZIP: - case PSD.COMPRESSION_ZIP_PREDICTION: - compression = "zip"; - break; - default: - throw new AssertionError("Unreachable"); - } - node.setAttribute("value", compression); + // TODO: Only if set... + node.setAttribute("value", "PackBits"); compression_node.appendChild(node); node = new IIOMetadataNode("Lossless"); node.setAttribute("value", "true"); compression_node.appendChild(node); +// compression_node.appendChild(node); + return compression_node; } @@ -417,9 +280,9 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { node.setAttribute("value", "Normal"); dimension_node.appendChild(node); - Iterator resolutionInfos = getResources(PSDResolutionInfo.class); - if (!resolutionInfos.hasNext()) { - PSDResolutionInfo resolutionInfo = resolutionInfos.next(); + List resolutionInfos = getResources(PSDResolutionInfo.class); + if (!resolutionInfos.isEmpty()) { + PSDResolutionInfo resolutionInfo = resolutionInfos.get(0); node = new IIOMetadataNode("HorizontalPixelSize"); node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes))); @@ -467,49 +330,31 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { @Override protected IIOMetadataNode getStandardDocumentNode() { - IIOMetadataNode document_node = new IIOMetadataNode("Document"); - IIOMetadataNode node; // scratch node + // TODO: PSDVersionInfo - node = new IIOMetadataNode("FormatVersion"); - node.setAttribute("value", "1"); // PSD format version is always 1 - document_node.appendChild(node); - - // Get EXIF data if present - Iterator exif = getResources(PSDEXIF1Data.class); - if (exif.hasNext()) { - PSDEXIF1Data data = exif.next(); - - // Get the EXIF DateTime (aka ModifyDate) tag if present - PSDEXIF1Data.Entry dateTime = data.mDirectory.get(0x0132); // TODO: Constant - if (dateTime != null) { - node = new IIOMetadataNode("ImageModificationTime"); - // Format: "YYYY:MM:DD hh:mm:ss" (with quotes! :-P) - String value = dateTime.getValueAsString(); - - node.setAttribute("year", value.substring(1, 5)); - node.setAttribute("month", value.substring(6, 8)); - node.setAttribute("day", value.substring(9, 11)); - node.setAttribute("hour", value.substring(12, 14)); - node.setAttribute("minute", value.substring(15, 17)); - node.setAttribute("second", value.substring(18, 20)); - - document_node.appendChild(node); - } - } - - return document_node; +// if (!tIME_present) { +// return null; +// } +// +// IIOMetadataNode document_node = new IIOMetadataNode("Document"); +// IIOMetadataNode node = null; // scratch node +// +// node = new IIOMetadataNode("ImageModificationTime"); +// node.setAttribute("year", Integer.toString(tIME_year)); +// node.setAttribute("month", Integer.toString(tIME_month)); +// node.setAttribute("day", Integer.toString(tIME_day)); +// node.setAttribute("hour", Integer.toString(tIME_hour)); +// node.setAttribute("minute", Integer.toString(tIME_minute)); +// node.setAttribute("second", Integer.toString(tIME_second)); +// document_node.appendChild(node); +// +// return document_node; + return null; } @Override protected IIOMetadataNode getStandardTextNode() { // TODO: CaptionDigest?, EXIF, XMP - - Iterator textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class); - - while (textResources.hasNext()) { - PSDImageResource textResource = textResources.next(); - - } // int numEntries = tEXt_keyword.size() + // iTXt_keyword.size() + zTXt_keyword.size(); @@ -566,7 +411,8 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { @Override protected IIOMetadataNode getStandardTransparencyNode() { - IIOMetadataNode transparency_node = new IIOMetadataNode("Transparency"); + IIOMetadataNode transparency_node = + new IIOMetadataNode("Transparency"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("Alpha"); @@ -581,31 +427,20 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { mHeader.mMode == PSD.COLOR_MODE_CMYK & mHeader.mChannels >= 5; } - Iterator getResources(final Class pResourceType) { - // NOTE: The cast here is wrong, strictly speaking, but it does not matter... - @SuppressWarnings({"unchecked"}) - Iterator iterator = (Iterator) mImageResources.iterator(); + // TODO: Replace with filter iterator? + List getResources(final Class pResourceType) { + List filtered = null; - return new FilterIterator(iterator, new FilterIterator.Filter() { - public boolean accept(final T pElement) { - return pResourceType.isInstance(pElement); - } - }); - } - - Iterator getResources(final Class... pResourceTypes) { - Iterator iterator = mImageResources.iterator(); - - return new FilterIterator(iterator, new FilterIterator.Filter() { - public boolean accept(final PSDImageResource pElement) { - for (Class type : pResourceTypes) { - if (type.isInstance(pElement)) { - return true; - } + for (PSDImageResource resource : mImageResources) { + if (pResourceType.isInstance(resource)) { + if (filtered == null) { + filtered = new ArrayList(); } - return false; + filtered.add(pResourceType.cast(resource)); } - }); + } + + return filtered; } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java index 6d2a765d..072748ce 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -1,7 +1,5 @@ package com.twelvemonkeys.imageio.plugins.psd; -import org.w3c.dom.Document; - import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadataFormatImpl; import java.util.Arrays; @@ -35,7 +33,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { addElement("PSDHeader", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); // TODO: Do the first two make sense? -// addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); + addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1")); addAttribute("PSDHeader", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); @@ -44,8 +42,16 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // columns? addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); - // TODO: Consider using more readable names?! - addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.COLOR_MODES)); + addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList( + String.valueOf(PSD.COLOR_MODE_MONOCHROME), + String.valueOf(PSD.COLOR_MODE_GRAYSCALE), + String.valueOf(PSD.COLOR_MODE_INDEXED), + String.valueOf(PSD.COLOR_MODE_RGB), + String.valueOf(PSD.COLOR_MODE_CMYK), + String.valueOf(PSD.COLOR_MODE_MULTICHANNEL), + String.valueOf(PSD.COLOR_MODE_DUOTONE), + String.valueOf(PSD.COLOR_MODE_LAB) + )); /* Contains the required data to define the color mode. @@ -63,7 +69,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // NOTE: Palette, PaletteEntry naming taken from the standard format, native PSD naming is ColorModeData // NOTE: PSD stores these as 256 Red, 256 Green, 256 Blue.. Should we do the same in the meta data? addElement("Palette", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, 256, 256); // 768 = 256 * 3 - addElement("PaletteEntry", "Palette", CHILD_POLICY_EMPTY); + addElement("PaletteEntry", "PSDColorData", CHILD_POLICY_EMPTY); addAttribute("PaletteEntry", "index", DATATYPE_INTEGER, true, null, "0", "255", true, true); addAttribute("PaletteEntry", "red", DATATYPE_INTEGER, true, null, "0", "255", true, true); addAttribute("PaletteEntry", "green", DATATYPE_INTEGER, true, null, "0", "255", true, true); @@ -83,19 +89,15 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // TODO: Allow arbitrary values to be added as a generic resource... // root -> ImageResources -> AlphaChannelInfo - addElement("AlphaChannelInfo", "ImageResources", 0, Integer.MAX_VALUE); // The format probably does not support that many layers.. - addElement("Name", "AlphaChannelInfo", CHILD_POLICY_EMPTY); - addAttribute("Name", "value", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); + addElement("AlphaChannelInfo", "ImageResources", CHILD_POLICY_EMPTY); + addAttribute("AlphaChannelInfo", "names", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); // root -> ImageResources -> DisplayInfo addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY); - // TODO: Consider using human readable strings - // TODO: Limit values (0-8, 10, 11, 3000) addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null); addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4); addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true); - // TODO: Consider using human readable strings - addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS)); + addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList("0", "1")); // root -> ImageResources -> EXIF1Data addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL); @@ -136,7 +138,6 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> ImageResources -> XMPData addElement("XMPData", "ImageResources", CHILD_POLICY_CHOICE); // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) - addObjectValue("XMPData", Document.class, true, null); // TODO: Layers //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java index bb802354..c64380b9 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlags.java @@ -26,14 +26,14 @@ final class PSDPrintFlags extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { - mLabels = pInput.readBoolean(); - mCropMasks = pInput.readBoolean(); - mColorBars = pInput.readBoolean(); - mRegistrationMarks = pInput.readBoolean(); - mNegative = pInput.readBoolean(); - mFlip = pInput.readBoolean(); - mInterpolate = pInput.readBoolean(); - mCaption = pInput.readBoolean(); + mLabels = pInput.readUnsignedByte() != 0; + mCropMasks = pInput.readUnsignedByte() != 0; + mColorBars = pInput.readUnsignedByte() != 0; + mRegistrationMarks = pInput.readUnsignedByte() != 0; + mNegative = pInput.readUnsignedByte() != 0; + mFlip = pInput.readUnsignedByte() != 0; + mInterpolate = pInput.readUnsignedByte() != 0; + mCaption = pInput.readUnsignedByte() != 0; pInput.skipBytes(mSize - 8); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java index c3aa31d3..d7c879d0 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDPrintFlagsInformation.java @@ -24,7 +24,7 @@ final class PSDPrintFlagsInformation extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { mVersion = pInput.readUnsignedShort(); - mCropMasks = pInput.readBoolean(); + mCropMasks = pInput.readUnsignedByte() != 0; mField = pInput.readUnsignedByte(); mBleedWidth = pInput.readUnsignedInt(); mBleedScale = pInput.readUnsignedShort(); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java index be49a19f..4d989865 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDThumbnail.java @@ -37,7 +37,7 @@ class PSDThumbnail extends PSDImageResource { */ @Override protected void readData(final ImageInputStream pInput) throws IOException { - // TODO: Support for RAW RGB (format == 0): Extract RAW reader from PICT RAW QuickTime decompressor + // TODO: Support for RAW RGB (format == 0) int format = pInput.readInt(); switch (format) { case 0: diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java index f3e1ab41..58a5fd4c 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java @@ -31,10 +31,8 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.imageio.util.IIOUtil; import com.twelvemonkeys.io.enc.DecoderStream; import com.twelvemonkeys.io.enc.PackBitsDecoder; -import com.twelvemonkeys.lang.StringUtil; import javax.imageio.stream.ImageInputStream; -import java.io.DataInput; import java.io.DataInputStream; import java.io.IOException; import java.util.zip.ZipInputStream; @@ -59,22 +57,23 @@ final class PSDUtil { ); } - // TODO: Proably also useful for PICT reader, move to some common util? - // TODO: Is this REALLY different from the previous method? Maybe the pad should not be read.. - static String readPascalString(final DataInput pInput) throws IOException { + // TODO: Proably also useful for PICT reader, move to some common util? + static String readPascalString(ImageInputStream pInput) throws IOException { + int length = pInput.readUnsignedByte(); +// int length = pInput.readUnsignedShort(); + byte[] bytes = new byte[length]; + pInput.readFully(bytes); + if (length % 2 == 0) { + pInput.readByte(); // Pad + } + return new String(bytes); + } + + static String readPascalStringByte(ImageInputStream pInput) throws IOException { int length = pInput.readUnsignedByte(); byte[] bytes = new byte[length]; pInput.readFully(bytes); - - return StringUtil.decode(bytes, 0, bytes.length, "ASCII"); - } - - static String readUTF16String(final DataInput pInput) throws IOException { - int length = pInput.readInt(); - byte[] bytes = new byte[length * 2]; - pInput.readFully(bytes); - - return StringUtil.decode(bytes, 0, bytes.length, "UTF-16"); + return new String(bytes); } static DataInputStream createPackBitsStream(final ImageInputStream pInput, long pLength) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java deleted file mode 100644 index 683fa3a3..00000000 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.twelvemonkeys.imageio.plugins.psd; - -import javax.imageio.stream.ImageInputStream; -import java.io.IOException; - -/** - * PSDVersionInfo - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: PSDVersionInfo.java,v 1.0 Nov 6, 2009 1:02:19 PM haraldk Exp$ - */ -final class PSDVersionInfo extends PSDImageResource { - - int mVersion; - boolean mHasRealMergedData; - String mWriter; - String mReader; - int mFileVersion; - - PSDVersionInfo(final short pId, final ImageInputStream pInput) throws IOException { - super(pId, pInput); - } - - @Override - protected void readData(final ImageInputStream pInput) throws IOException { - /* - 4 bytes version - 1 byte hasRealMergedData - Unicode string: writer name - Unicode string: reader name - 4 bytes file version. - */ - - mVersion = pInput.readInt(); - mHasRealMergedData = pInput.readBoolean(); - - mWriter = PSDUtil.readUTF16String(pInput); - mReader = PSDUtil.readUTF16String(pInput); - - mFileVersion = pInput.readInt(); - } - - @Override - public String toString() { - StringBuilder builder = toStringBuilder(); - - builder.append(", version: ").append(mVersion); - builder.append(", hasRealMergedData: ").append(mHasRealMergedData); - builder.append(", writer: ").append(mWriter); - builder.append(", reader: ").append(mReader); - builder.append(", file version: ").append(mFileVersion); - builder.append("]"); - - return builder.toString(); - } -} From 27553dc47af2e646104654abd26c6887191f3f3e Mon Sep 17 00:00:00 2001 From: Erlend Hamnaberg Date: Wed, 11 Nov 2009 21:47:31 +0100 Subject: [PATCH 13/21] Revert "Work in progress for PSD metadata support:" This reverts commit b5f6c96583a486896b5532a5785d488d56762d4b. --- .../util/ImageReaderAbstractTestCase.java | 27 +- .../imageio/plugins/psd/PSDColorData.java | 4 +- .../imageio/plugins/psd/PSDDisplayInfo.java | 20 +- .../imageio/plugins/psd/PSDEXIF1Data.java | 2 +- .../imageio/plugins/psd/PSDImageReader.java | 107 +---- .../plugins/psd/PSDImageReaderSpi.java | 8 +- .../imageio/plugins/psd/PSDMetadata.java | 446 ------------------ .../plugins/psd/PSDMetadataFormat.java | 165 ------- .../plugins/psd/PSDResolutionInfo.java | 13 +- 9 files changed, 44 insertions(+), 748 deletions(-) delete mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java delete mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java diff --git a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java index 601ff974..11be9e0d 100644 --- a/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java +++ b/twelvemonkeys-imageio/core/src/test/java/com/twelvemonkeys/imageio/util/ImageReaderAbstractTestCase.java @@ -36,7 +36,6 @@ import org.jmock.core.Stub; import javax.imageio.*; import javax.imageio.event.IIOReadProgressListener; -import javax.imageio.metadata.IIOMetadata; import javax.imageio.spi.IIORegistry; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; @@ -1321,9 +1320,7 @@ public abstract class ImageReaderAbstractTestCase extends assertEquals(type.getColorModel(), result.getColorModel()); - // The following logically tests - // assertEquals(type.getSampleModel(), result.getSampleModel()); - // but SampleModel does not have a proper equals method. +// assertEquals(type.getSampleModel(), result.getSampleModel()); SampleModel expectedModel = type.getSampleModel(); SampleModel resultModel = result.getSampleModel(); @@ -1338,6 +1335,10 @@ public abstract class ImageReaderAbstractTestCase extends } } +// public void testSetDestinationTypeIllegal() throws IOException { +// throw new UnsupportedOperationException("Method testSetDestinationTypeIllegal not implemented"); // TODO: Implement +// } +// // public void testSetDestinationBands() throws IOException { // throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement // } @@ -1346,24 +1347,6 @@ public abstract class ImageReaderAbstractTestCase extends // throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement // } - public void testProviderAndMetadataFormatNamesMatch() throws IOException { - ImageReaderSpi provider = createProvider(); - - ImageReader reader = createReader(); - reader.setInput(getTestData().get(0).getInputStream()); - - IIOMetadata imageMetadata = reader.getImageMetadata(0); - if (imageMetadata != null) { - assertEquals(provider.getNativeImageMetadataFormatName(), imageMetadata.getNativeMetadataFormatName()); - } - - IIOMetadata streamMetadata = reader.getStreamMetadata(); - if (streamMetadata != null) { - assertEquals(provider.getNativeStreamMetadataFormatName(), streamMetadata.getNativeMetadataFormatName()); - } - } - - protected URL getClassLoaderResource(final String pName) { return getClass().getResource(pName); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java index d8e67cde..2d64c5a8 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDColorData.java @@ -47,7 +47,7 @@ class PSDColorData { final byte[] mColors; private IndexColorModel mColorModel; - PSDColorData(final ImageInputStream pInput) throws IOException { + PSDColorData(ImageInputStream pInput) throws IOException { int length = pInput.readInt(); if (length == 0) { throw new IIOException("No palette information in PSD"); @@ -72,7 +72,7 @@ class PSDColorData { return mColorModel; } - private static int[] toInterleavedRGB(final byte[] pColors) { + private int[] toInterleavedRGB(byte[] pColors) { int[] rgb = new int[pColors.length / 3]; for (int i = 0; i < rgb.length; i++) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java index d4d41347..6b04cf12 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDDisplayInfo.java @@ -40,7 +40,7 @@ import java.io.IOException; * @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$ */ class PSDDisplayInfo extends PSDImageResource { - // TODO: Size of this struct should be 14.. Does not compute... Something bogus here + // TODO: Size of this struct should be 14.. Does not compute... //typedef _DisplayInfo //{ // WORD ColorSpace; @@ -67,20 +67,20 @@ class PSDDisplayInfo extends PSDImageResource { // long left = mSize; // while (left > 0) { - mColorSpace = pInput.readShort(); + mColorSpace = pInput.readShort(); - // Color[4]...? + // Color[4]...? mColors = new short[4]; - mColors[0] = pInput.readShort(); - mColors[1] = pInput.readShort(); - mColors[2] = pInput.readShort(); - mColors[3] = pInput.readShort(); + mColors[0] = pInput.readShort(); + mColors[1] = pInput.readShort(); + mColors[2] = pInput.readShort(); + mColors[3] = pInput.readShort(); - mOpacity = pInput.readShort(); + mOpacity = pInput.readShort(); - mKind = pInput.readByte(); + mKind = pInput.readByte(); - pInput.readByte(); // Pad + pInput.readByte(); // Pad // left -= 14; // } pInput.skipBytes(mSize - 14); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java index a2f6f486..a54bb6a8 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java @@ -35,7 +35,7 @@ final class PSDEXIF1Data extends PSDImageResource { protected void readData(final ImageInputStream pInput) throws IOException { // This is in essence an embedded TIFF file. // TODO: Extract TIFF parsing to more general purpose package - // TODO: Instead, read the byte data, store for later parsing (or store offset, and read on request) + // TODO: Instead, read the byte data, store for later parsing MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize)); byte[] bom = new byte[2]; diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index d2f05792..52680c9c 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -31,15 +31,8 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.image.ImageUtil; import com.twelvemonkeys.imageio.ImageReaderBase; import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier; -import com.twelvemonkeys.xml.XMLSerializer; -import org.w3c.dom.Node; -import javax.imageio.IIOException; -import javax.imageio.ImageIO; -import javax.imageio.ImageReadParam; -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; +import javax.imageio.*; import javax.imageio.spi.ImageReaderSpi; import javax.imageio.stream.ImageInputStream; import java.awt.*; @@ -50,11 +43,13 @@ import java.awt.image.*; import java.io.DataInputStream; import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; /** - * ImageReader for Adobe Photoshop Document (PSD) format. + * ImageReader for Adobe Photoshop Document format. * * @see Adobe Photoshop File Format Summary * @author Harald Kuhr @@ -62,7 +57,7 @@ import java.util.List; * @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$ */ // TODO: Implement ImageIO meta data interface -// TODO: API for reading separate layers +// TODO: Allow reading separate (or some?) layers // TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers // http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ // See http://www.codeproject.com/KB/graphics/PSDParser.aspx @@ -120,7 +115,7 @@ public class PSDImageReader extends ImageReaderBase { ); case PSD.COLOR_MODE_INDEXED: - // TODO: 16 bit indexed?! Does it exist? + // TODO: 16 bit indexed?! if (mHeader.mChannels == 1 && mHeader.mBits == 8) { return IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel()); } @@ -189,11 +184,6 @@ public class PSDImageReader extends ImageReaderBase { throw new IIOException( String.format("Unsupported channel count/bit depth for CMYK PSD: %d channels/%d bits", mHeader.mChannels, mHeader.mBits) ); - - case PSD.COLOR_MODE_MULTICHANNEL: - // TODO: Implement - case PSD.COLOR_MODE_LAB: - // TODO: Implement default: throw new IIOException( String.format("Unsupported PSD MODE: %s (%d channels/%d bits)", mHeader.mMode, mHeader.mChannels, mHeader.mBits) @@ -414,7 +404,7 @@ public class PSDImageReader extends ImageReaderBase { 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); break; default: - throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits)); + throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits); } if (abortRequested()) { @@ -546,7 +536,6 @@ public class PSDImageReader extends ImageReaderBase { } } - @SuppressWarnings({"UnusedDeclaration"}) private void read1bitChannel(final int pChannel, final int pChannelCount, final byte[] pData, final int pBands, final int pBandOffset, final ColorModel pSourceColorModel, @@ -708,7 +697,6 @@ public class PSDImageReader extends ImageReaderBase { mColorData = new PSDColorData(mImageInput); } 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); @@ -720,7 +708,6 @@ public class PSDImageReader extends ImageReaderBase { } // TODO: Flags or list of interesting resources to parse - // TODO: Obey ignoreMetadata private void readImageResources(final boolean pParseData) throws IOException { // TODO: Avoid unnecessary stream repositioning long pos = mImageInput.getFlushedPosition(); @@ -748,8 +735,6 @@ public class PSDImageReader extends ImageReaderBase { mImageInput.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(); @@ -782,7 +767,7 @@ public class PSDImageReader extends ImageReaderBase { // TODO: If not explicitly needed, skip layers... BufferedImage layer = readLayerData(layerInfo, raw, imageType); - // TODO: Don't show! Store in meta data somehow... + // TODO: Don't show! Store in metadata somehow... if (layer != null) { showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString()); } @@ -810,7 +795,6 @@ public class PSDImageReader extends ImageReaderBase { mImageInput.skipBytes(toSkip); } else { - // Skip entire layer and mask section mImageInput.skipBytes(length); } } @@ -854,7 +838,7 @@ public class PSDImageReader extends ImageReaderBase { } else { // 0 = red, 1 = green, etc - // -1 = transparency mask; -2 = user supplied layer mask + // ?1 = transparency mask; ?2 = user supplied layer mask int c = channelInfo.mChannelId == -1 ? pLayerInfo.mChannelInfo.length - 1 : channelInfo.mChannelId; // NOTE: For layers, byte counts are written per channel, while for the composite data @@ -908,7 +892,7 @@ public class PSDImageReader extends ImageReaderBase { read16bitChannel(c, imageType.getNumBands(), data16, 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)); + throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits); } if (abortRequested()) { @@ -947,57 +931,6 @@ public class PSDImageReader extends ImageReaderBase { return pOriginal; } - /// Layer support - // TODO: For now, leave as Metadata - - /* - int getNumLayers(int pImageIndex) throws IOException; - - boolean hasLayers(int pImageIndex) throws IOException; - - BufferedImage readLayer(int pImageIndex, int pLayerIndex, ImageReadParam pParam) throws IOException; - - int getLayerWidth(int pImageIndex, int pLayerIndex) throws IOException; - - int getLayerHeight(int pImageIndex, int pLayerIndex) throws IOException; - - // ? - Point getLayerOffset(int pImageIndex, int pLayerIndex) throws IOException; - - */ - - /// Metadata support - // TODO - - @Override - public IIOMetadata getStreamMetadata() throws IOException { - // null might be appropriate here - // "For image formats that contain a single image, only image metadata is used." - return super.getStreamMetadata(); - } - - @Override - public IIOMetadata getImageMetadata(final int pImageIndex) throws IOException { - // TODO: Implement - checkBounds(pImageIndex); - - readHeader(); - readImageResources(true); - readLayerAndMaskInfo(true); - - PSDMetadata metadata = new PSDMetadata(); - metadata.mHeader = mHeader; - metadata.mColorData = mColorData; - metadata.mImageResources = mImageResources; - return metadata; - } - - @Override - public IIOMetadata getImageMetadata(final int imageIndex, final String formatName, final Set nodeNames) throws IOException { - // TODO: This might make sense, as there's loads of meta data in the file - return super.getImageMetadata(imageIndex, formatName, nodeNames); - } - /// Thumbnail support @Override public boolean readerSupportsThumbnails() { @@ -1032,13 +965,13 @@ public class PSDImageReader extends ImageReaderBase { } @Override - public int getNumThumbnails(final int pIndex) throws IOException { + public int getNumThumbnails(int pIndex) throws IOException { List thumbnails = getThumbnailResources(pIndex); return thumbnails == null ? 0 : thumbnails.size(); } - private PSDThumbnail getThumbnailResource(final int pImageIndex, final int pThumbnailIndex) throws IOException { + private PSDThumbnail getThumbnailResource(int pImageIndex, int pThumbnailIndex) throws IOException { List thumbnails = getThumbnailResources(pImageIndex); if (thumbnails == null) { @@ -1049,17 +982,17 @@ public class PSDImageReader extends ImageReaderBase { } @Override - public int getThumbnailWidth(final int pImageIndex, final int pThumbnailIndex) throws IOException { + public int getThumbnailWidth(int pImageIndex, int pThumbnailIndex) throws IOException { return getThumbnailResource(pImageIndex, pThumbnailIndex).getWidth(); } @Override - public int getThumbnailHeight(final int pImageIndex, final int pThumbnailIndex) throws IOException { + public int getThumbnailHeight(int pImageIndex, int pThumbnailIndex) throws IOException { return getThumbnailResource(pImageIndex, pThumbnailIndex).getHeight(); } @Override - public BufferedImage readThumbnail(final int pImageIndex, final int pThumbnailIndex) throws IOException { + public BufferedImage readThumbnail(int pImageIndex, int pThumbnailIndex) throws IOException { // TODO: Thumbnail progress listeners... PSDThumbnail thumbnail = getThumbnailResource(pImageIndex, pThumbnailIndex); @@ -1068,7 +1001,6 @@ public class PSDImageReader extends ImageReaderBase { processThumbnailStarted(pImageIndex, pThumbnailIndex); processThumbnailComplete(); - // TODO: Returning a cached mutable thumbnail is not really safe... return thumbnail.getThumbnail(); } @@ -1120,17 +1052,12 @@ public class PSDImageReader extends ImageReaderBase { // System.out.println("imageReader.mHeader: " + imageReader.mHeader); imageReader.readImageResources(true); - System.out.println("imageReader.mImageResources: " + imageReader.mImageResources); +// System.out.println("imageReader.mImageResources: " + imageReader.mImageResources); imageReader.readLayerAndMaskInfo(true); System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo); // System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask); - IIOMetadata metadata = imageReader.getImageMetadata(0); - Node node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); - XMLSerializer serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); - serializer.serialize(node, true); - if (imageReader.hasThumbnails(0)) { int thumbnails = imageReader.getNumThumbnails(0); for (int i = 0; i < thumbnails; i++) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java index 0b63af8d..5a27b1c5 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReaderSpi.java @@ -67,12 +67,8 @@ public class PSDImageReaderSpi extends ImageReaderSpi { STANDARD_INPUT_TYPE, // new String[]{"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"}, null, - true, // supports standard stream metadata - null, null, // native stream format name and class - null, null, // extra stream formats - true, // supports standard image metadata - PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME, - null, null // extra image metadata formats + true, null, null, null, null, + true, null, null, null, null ); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java deleted file mode 100644 index a9755fad..00000000 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ /dev/null @@ -1,446 +0,0 @@ -package com.twelvemonkeys.imageio.plugins.psd; - -import com.twelvemonkeys.lang.StringUtil; -import org.w3c.dom.Node; - -import javax.imageio.metadata.IIOInvalidTreeException; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; -import javax.imageio.metadata.IIOMetadataNode; -import java.awt.image.IndexColorModel; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * PSDMetadata - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: PSDMetadata.java,v 1.0 Nov 4, 2009 5:28:12 PM haraldk Exp$ - */ -public final class PSDMetadata extends IIOMetadata implements Cloneable { - - static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_1.0"; - static final String NATIVE_METADATA_FORMAT_CLASS_NAME = "com.twelvemonkeys.imageio.plugins.psd.PSDMetadataFormat"; - - // TODO: Move fields from PSDImageReader (header, color map, resources, etc) here - PSDHeader mHeader; - PSDColorData mColorData; - List mImageResources; - PSDGlobalLayerMask mGlobalLayerMask; - List mLayerInfo; - - protected PSDMetadata() { - // TODO: Allow XMP, EXIF and IPTC as extra formats? - super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); - } - - @Override - public boolean isReadOnly() { - // TODO: Extract to abstract metadata impl class? - return true; - } - - @Override - public Node getAsTree(final String pFormatName) { - validateFormatName(pFormatName); - - if (pFormatName.equals(nativeMetadataFormatName)) { - return getNativeTree(); - } - else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { - return getStandardTree(); - } - - throw new AssertionError("Unreachable"); - } - - @Override - public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException { - // TODO: Extract to abstract metadata impl class? - assertMutable(); - - validateFormatName(pFormatName); - - if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) { - throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot); - } - - Node node = pRoot.getFirstChild(); - while (node != null) { - // TODO: Merge values from node into this - - // Move to the next sibling - node = node.getNextSibling(); - } - } - - @Override - public void reset() { - // TODO: Extract to abstract metadata impl class? - assertMutable(); - - throw new UnsupportedOperationException("Method reset not implemented"); // TODO: Implement - } - - // TODO: Extract to abstract metadata impl class? - private void assertMutable() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - // TODO: Extract to abstract metadata impl class? - private void validateFormatName(final String pFormatName) { - String[] metadataFormatNames = getMetadataFormatNames(); - - if (metadataFormatNames != null) { - for (String metadataFormatName : metadataFormatNames) { - if (metadataFormatName.equals(pFormatName)) { - return; // Found, we're ok! - } - } - } - - throw new IllegalArgumentException( - String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames)) - ); - } - - @Override - public Object clone() { - // TODO: Make it a deep clone - try { - return super.clone(); - } - catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - } - - private Node getNativeTree() { - throw new UnsupportedOperationException("getNativeTree"); - } - - /// Standard format support - - @Override - protected IIOMetadataNode getStandardChromaNode() { - IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma"); - IIOMetadataNode node; // scratch node - - node = new IIOMetadataNode("ColorSpaceType"); - String cs; - switch (mHeader.mMode) { - case PSD.COLOR_MODE_MONOCHROME: - case PSD.COLOR_MODE_GRAYSCALE: - case PSD.COLOR_MODE_DUOTONE: // Rationale is spec says treat as gray... - cs = "GRAY"; - break; - case PSD.COLOR_MODE_RGB: - case PSD.COLOR_MODE_INDEXED: - cs = "RGB"; - break; - case PSD.COLOR_MODE_CMYK: - cs = "CMYK"; - break; - case PSD.COLOR_MODE_MULTICHANNEL: - // TODO: FixMe - cs = "???"; - break; - case PSD.COLOR_MODE_LAB: - cs = "Lab"; - break; - default: - throw new AssertionError("Unreachable"); - } - node.setAttribute("name", cs); - chroma_node.appendChild(node); - - // TODO: Channels might be 5 for RGB + A + Mask... - node = new IIOMetadataNode("NumChannels"); - node.setAttribute("value", Integer.toString(mHeader.mChannels)); - chroma_node.appendChild(node); - -// if (gAMA_present) { -// node = new IIOMetadataNode("Gamma"); -// node.setAttribute("value", Float.toString(gAMA_gamma*1.0e-5F)); -// chroma_node.appendChild(node); -// } - - // TODO: Check if this is correct with bitmap (monchrome) - node = new IIOMetadataNode("BlackIsZero"); - node.setAttribute("value", "true"); - chroma_node.appendChild(node); - - if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) { - node = new IIOMetadataNode("Palette"); - - IndexColorModel cm = mColorData.getIndexColorModel(); - for (int i = 0; i < cm.getMapSize(); i++) { - IIOMetadataNode entry = - new IIOMetadataNode("PaletteEntry"); - entry.setAttribute("index", Integer.toString(i)); - entry.setAttribute("red", - Integer.toString(cm.getRed(i))); - entry.setAttribute("green", - Integer.toString(cm.getGreen(i))); - entry.setAttribute("blue", - Integer.toString(cm.getBlue(i))); - - node.appendChild(entry); - } - chroma_node.appendChild(node); - } - -// if (bKGD_present) { -// if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) { -// node = new IIOMetadataNode("BackgroundIndex"); -// node.setAttribute("value", Integer.toString(bKGD_index)); -// } else { -// node = new IIOMetadataNode("BackgroundColor"); -// int r, g, b; -// -// if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) { -// r = g = b = bKGD_gray; -// } else { -// r = bKGD_red; -// g = bKGD_green; -// b = bKGD_blue; -// } -// node.setAttribute("red", Integer.toString(r)); -// node.setAttribute("green", Integer.toString(g)); -// node.setAttribute("blue", Integer.toString(b)); -// } -// chroma_node.appendChild(node); -// } - - return chroma_node; - } - - @Override - protected IIOMetadataNode getStandardCompressionNode() { - IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); - IIOMetadataNode node; // scratch node - - node = new IIOMetadataNode("CompressionTypeName"); - // TODO: Only if set... - node.setAttribute("value", "PackBits"); - compression_node.appendChild(node); - - node = new IIOMetadataNode("Lossless"); - node.setAttribute("value", "true"); - compression_node.appendChild(node); - -// compression_node.appendChild(node); - - return compression_node; - } - - @Override - protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode data_node = new IIOMetadataNode("Data"); - IIOMetadataNode node; // scratch node - - node = new IIOMetadataNode("PlanarConfiguration"); - node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec - data_node.appendChild(node); - - node = new IIOMetadataNode("SampleFormat"); - node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral"); - data_node.appendChild(node); - - String bitDepth = Integer.toString(mHeader.mBits); // bits per plane - // TODO: Channels might be 5 for RGB + A + Mask... - String[] bps = new String[mHeader.mChannels]; - Arrays.fill(bps, bitDepth); - - node = new IIOMetadataNode("BitsPerSample"); - node.setAttribute("value", StringUtil.toCSVString(bps, " ")); - data_node.appendChild(node); - - // TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed? - - return data_node; - } - - @Override - protected IIOMetadataNode getStandardDimensionNode() { - IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); - IIOMetadataNode node; // scratch node - - node = new IIOMetadataNode("PixelAspectRatio"); - // TODO: This is not incorrect wrt resolution info - float ratio = 1f; - node.setAttribute("value", Float.toString(ratio)); - dimension_node.appendChild(node); - - node = new IIOMetadataNode("ImageOrientation"); - node.setAttribute("value", "Normal"); - dimension_node.appendChild(node); - - List resolutionInfos = getResources(PSDResolutionInfo.class); - if (!resolutionInfos.isEmpty()) { - PSDResolutionInfo resolutionInfo = resolutionInfos.get(0); - - node = new IIOMetadataNode("HorizontalPixelSize"); - node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes))); - dimension_node.appendChild(node); - - node = new IIOMetadataNode("VerticalPixelSize"); - node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes))); - dimension_node.appendChild(node); - } - - // TODO: - /* - - - - - - - - - - - - - - - - - - - - - */ - return dimension_node; - } - - private static float asMM(final short pUnit, final float pResolution) { - // Unit: 1 -> pixels per inch, 2 -> pixels pr cm - return (pUnit == 1 ? 25.4f : 10) / pResolution; - } - - @Override - protected IIOMetadataNode getStandardDocumentNode() { - // TODO: PSDVersionInfo - -// if (!tIME_present) { -// return null; -// } -// -// IIOMetadataNode document_node = new IIOMetadataNode("Document"); -// IIOMetadataNode node = null; // scratch node -// -// node = new IIOMetadataNode("ImageModificationTime"); -// node.setAttribute("year", Integer.toString(tIME_year)); -// node.setAttribute("month", Integer.toString(tIME_month)); -// node.setAttribute("day", Integer.toString(tIME_day)); -// node.setAttribute("hour", Integer.toString(tIME_hour)); -// node.setAttribute("minute", Integer.toString(tIME_minute)); -// node.setAttribute("second", Integer.toString(tIME_second)); -// document_node.appendChild(node); -// -// return document_node; - return null; - } - - @Override - protected IIOMetadataNode getStandardTextNode() { - // TODO: CaptionDigest?, EXIF, XMP - -// int numEntries = tEXt_keyword.size() + -// iTXt_keyword.size() + zTXt_keyword.size(); -// if (numEntries == 0) { -// return null; -// } -// -// IIOMetadataNode text_node = new IIOMetadataNode("Text"); -// IIOMetadataNode node = null; // scratch node -// -// for (int i = 0; i < tEXt_keyword.size(); i++) { -// node = new IIOMetadataNode("TextEntry"); -// node.setAttribute("keyword", (String)tEXt_keyword.get(i)); -// node.setAttribute("value", (String)tEXt_text.get(i)); -// node.setAttribute("encoding", "ISO-8859-1"); -// node.setAttribute("compression", "none"); -// -// text_node.appendChild(node); -// } -// -// for (int i = 0; i < iTXt_keyword.size(); i++) { -// node = new IIOMetadataNode("TextEntry"); -// node.setAttribute("keyword", iTXt_keyword.get(i)); -// node.setAttribute("value", iTXt_text.get(i)); -// node.setAttribute("language", -// iTXt_languageTag.get(i)); -// if (iTXt_compressionFlag.get(i)) { -// node.setAttribute("compression", "deflate"); -// } else { -// node.setAttribute("compression", "none"); -// } -// -// text_node.appendChild(node); -// } -// -// for (int i = 0; i < zTXt_keyword.size(); i++) { -// node = new IIOMetadataNode("TextEntry"); -// node.setAttribute("keyword", (String)zTXt_keyword.get(i)); -// node.setAttribute("value", (String)zTXt_text.get(i)); -// node.setAttribute("compression", "deflate"); -// -// text_node.appendChild(node); -// } -// -// return text_node; - return null; - - } - - @Override - protected IIOMetadataNode getStandardTileNode() { - return super.getStandardTileNode(); - } - - @Override - protected IIOMetadataNode getStandardTransparencyNode() { - IIOMetadataNode transparency_node = - new IIOMetadataNode("Transparency"); - IIOMetadataNode node; // scratch node - - node = new IIOMetadataNode("Alpha"); - node.setAttribute("value", hasAlpha() ? "nonpremultipled" : "none"); // TODO: Check spec - transparency_node.appendChild(node); - - return transparency_node; - } - - private boolean hasAlpha() { - return mHeader.mMode == PSD.COLOR_MODE_RGB && mHeader.mChannels >= 4 || - mHeader.mMode == PSD.COLOR_MODE_CMYK & mHeader.mChannels >= 5; - } - - // TODO: Replace with filter iterator? - List getResources(final Class pResourceType) { - List filtered = null; - - for (PSDImageResource resource : mImageResources) { - if (pResourceType.isInstance(resource)) { - if (filtered == null) { - filtered = new ArrayList(); - } - - filtered.add(pResourceType.cast(resource)); - } - } - - return filtered; - } -} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java deleted file mode 100644 index 072748ce..00000000 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.twelvemonkeys.imageio.plugins.psd; - -import javax.imageio.ImageTypeSpecifier; -import javax.imageio.metadata.IIOMetadataFormatImpl; -import java.util.Arrays; - -/** - * PSDMetadataFormat - * - * @author Harald Kuhr - * @author last modified by $Author: haraldk$ - * @version $Id: PSDMetadataFormat.java,v 1.0 Nov 4, 2009 5:27:53 PM haraldk Exp$ - */ -public final class PSDMetadataFormat extends IIOMetadataFormatImpl { - - private final static PSDMetadataFormat sInstance = new PSDMetadataFormat(); - - /** - * Private constructor. - *

- * The {@link javax.imageio.metadata.IIOMetadata} class will instantiate this class - * by reflection, invoking the static {@code getInstance()} method. - * - * @see javax.imageio.metadata.IIOMetadata#getMetadataFormat - * @see #getInstance() - */ - private PSDMetadataFormat() { - // Defines the root element - super(PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME); - - // root -> PSDHeader - // TODO: How do I specify that the header is required? - addElement("PSDHeader", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); - - // TODO: Do the first two make sense? - addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); - addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1")); - - addAttribute("PSDHeader", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); - // rows? - addAttribute("PSDHeader", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true); - // columns? - addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); - addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); - addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList( - String.valueOf(PSD.COLOR_MODE_MONOCHROME), - String.valueOf(PSD.COLOR_MODE_GRAYSCALE), - String.valueOf(PSD.COLOR_MODE_INDEXED), - String.valueOf(PSD.COLOR_MODE_RGB), - String.valueOf(PSD.COLOR_MODE_CMYK), - String.valueOf(PSD.COLOR_MODE_MULTICHANNEL), - String.valueOf(PSD.COLOR_MODE_DUOTONE), - String.valueOf(PSD.COLOR_MODE_LAB) - )); - - /* - Contains the required data to define the color mode. - - For indexed color images, the count will be equal to 768, and the mode data - will contain the color table for the image, in non-interleaved order. - - For duotone images, the mode data will contain the duotone specification, - the format of which is not documented. Non-Photoshop readers can treat - the duotone image as a grayscale image, and keep the duotone specification - around as a black box for use when saving the file. - */ - // root -> Palette - // Color map for indexed, optional - // NOTE: Palette, PaletteEntry naming taken from the standard format, native PSD naming is ColorModeData - // NOTE: PSD stores these as 256 Red, 256 Green, 256 Blue.. Should we do the same in the meta data? - addElement("Palette", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, 256, 256); // 768 = 256 * 3 - addElement("PaletteEntry", "PSDColorData", CHILD_POLICY_EMPTY); - addAttribute("PaletteEntry", "index", DATATYPE_INTEGER, true, null, "0", "255", true, true); - addAttribute("PaletteEntry", "red", DATATYPE_INTEGER, true, null, "0", "255", true, true); - addAttribute("PaletteEntry", "green", DATATYPE_INTEGER, true, null, "0", "255", true, true); - addAttribute("PaletteEntry", "blue", DATATYPE_INTEGER, true, null, "0", "255", true, true); - // No alpha allowed in indexed color PSD - - // TODO: Duotone spec, optional (use same element as palette?) - // Or use object or raw bytes.. - - // root -> ImageResources - // Image resources, optional - addElement("ImageResources", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_SEQUENCE); // SOME? - - // root -> ImageResources -> ImageResource - // Generic resource - addElement("ImageResource", "ImageResources", CHILD_POLICY_ALL); - // TODO: Allow arbitrary values to be added as a generic resource... - - // root -> ImageResources -> AlphaChannelInfo - addElement("AlphaChannelInfo", "ImageResources", CHILD_POLICY_EMPTY); - addAttribute("AlphaChannelInfo", "names", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); - - // root -> ImageResources -> DisplayInfo - addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY); - addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null); - addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4); - addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true); - addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList("0", "1")); - - // root -> ImageResources -> EXIF1Data - addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL); - // TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?) - - // root -> ImageResources -> PrintFlags - addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY); - addBooleanAttribute("PrintFlags", "labels", false, false); - addBooleanAttribute("PrintFlags", "cropMasks", false, false); - addBooleanAttribute("PrintFlags", "colorBars", false, false); - addBooleanAttribute("PrintFlags", "registrationMarks", false, false); - addBooleanAttribute("PrintFlags", "negative", false, false); - addBooleanAttribute("PrintFlags", "flip", false, false); - addBooleanAttribute("PrintFlags", "interpolate", false, false); - addBooleanAttribute("PrintFlags", "caption", false, false); - - // root -> ImageResources -> PrintFlagsInformation - addElement("PrintFlagsInformation", "ImageResources", CHILD_POLICY_EMPTY); - addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, true, null); - addBooleanAttribute("PrintFlagsInformation", "cropMarks", false, false); - addAttribute("PrintFlagsInformation", "field", DATATYPE_INTEGER, true, null); - addAttribute("PrintFlagsInformation", "bleedWidth", DATATYPE_INTEGER, true, null, "0", String.valueOf(Long.MAX_VALUE), true, true); // TODO: LONG??! - addAttribute("PrintFlagsInformation", "bleedScale", DATATYPE_INTEGER, true, null, "0", String.valueOf(Integer.MAX_VALUE), true, true); - - // root -> ImageResources -> ResolutionInfo - addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY); - addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null); - // TODO: Or use string and more friendly names? "pixels/inch"/"pixels/cm" and "inch"/"cm"/"pt"/"pica"/"column" - addAttribute("ResolutionInfo", "hResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); - addAttribute("ResolutionInfo", "widthUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5")); - addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null); - // TODO: Or use more friendly names? - addAttribute("ResolutionInfo", "vResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); - addAttribute("ResolutionInfo", "heightUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5")); - - // ??? addElement("Thumbnail", "ImageResources", CHILD_POLICY_CHOICE); - - // root -> ImageResources -> XMPData - addElement("XMPData", "ImageResources", CHILD_POLICY_CHOICE); - // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) - - // TODO: Layers - //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); - - // TODO: Global layer mask info - } - - - - @Override - public boolean canNodeAppear(final String pElementName, final ImageTypeSpecifier pImageType) { - // TODO: PSDColorData and PaletteEntry only for indexed color model - throw new UnsupportedOperationException("Method canNodeAppear not implemented"); // TODO: Implement - } - - /** - * Returns the shared instance of the {@code PSDMetadataFormat}. - * - * @return the shared instance. - * @see javax.imageio.metadata.IIOMetadata#getMetadataFormat - */ - public static PSDMetadataFormat getInstance() { - return sInstance; - } -} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java index 629f3a9c..43102599 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDResolutionInfo.java @@ -50,12 +50,13 @@ class PSDResolutionInfo extends PSDImageResource { // WORD HeightUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */ // } RESOLUTIONINFO; - float mHRes; - short mHResUnit; - short mWidthUnit; - float mVRes; - short mVResUnit; - short mHeightUnit; + private float mHRes; + private short mHResUnit; + private short mWidthUnit; + private float mVRes; + private short mVResUnit; + private short mHeightUnit; + PSDResolutionInfo(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); From aad80d043f261bace6914bb218318d5a70b8b413 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sat, 14 Nov 2009 22:42:21 +0100 Subject: [PATCH 14/21] Work in progress for PSD metadata support: - Refactored metadata support - Moved standard metadata support (EXIF, IPTC & XMP) to separate module - Changes to PSD metadata implementation --- twelvemonkeys-imageio/metadata/pom.xml | 31 ++ .../imageio/metadata/AbstractDirectory.java | 116 ++++++ .../imageio/metadata/AbstractEntry.java | 99 +++++ .../imageio/metadata/Directory.java | 33 ++ .../twelvemonkeys/imageio/metadata/Entry.java | 39 ++ .../imageio/metadata/MetadataReader.java | 15 + .../imageio/metadata/exif/EXIF.java | 47 +++ .../imageio/metadata/exif/EXIFDirectory.java | 20 + .../imageio/metadata/exif/EXIFEntry.java | 30 ++ .../imageio/metadata/exif/EXIFReader.java | 216 ++++++++++ .../imageio/metadata/iptc/IPTC.java | 130 ++++++ .../imageio/metadata/iptc/IPTCDirectory.java | 19 + .../imageio/metadata/iptc/IPTCEntry.java | 16 + .../imageio/metadata/iptc/IPTCReader.java | 149 +++++++ .../metadata/xmp/XMPScannerTestCase.java | 127 ++++++ twelvemonkeys-imageio/pom.xml | 8 + twelvemonkeys-imageio/psd/pom.xml | 66 +-- .../imageio/plugins/psd/AbstractMetadata.java | 103 +++++ .../plugins/psd/PSDAlphaChannelInfo.java | 5 +- .../imageio/plugins/psd/PSDEXIF1Data.java | 83 ++-- .../plugins/psd/PSDGlobalLayerMask.java | 10 +- .../imageio/plugins/psd/PSDIPTCData.java | 159 ++++---- .../imageio/plugins/psd/PSDImageReader.java | 7 +- .../imageio/plugins/psd/PSDImageResource.java | 12 +- .../imageio/plugins/psd/PSDMetadata.java | 379 ++++++++++-------- .../plugins/psd/PSDMetadataFormat.java | 1 - .../plugins/psd/PSDUnicodeAlphaNames.java | 2 +- .../imageio/plugins/psd/PSDUtil.java | 14 +- .../imageio/plugins/psd/PSDVersionInfo.java | 4 +- 29 files changed, 1603 insertions(+), 337 deletions(-) create mode 100644 twelvemonkeys-imageio/metadata/pom.xml create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java create mode 100644 twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java create mode 100644 twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java diff --git a/twelvemonkeys-imageio/metadata/pom.xml b/twelvemonkeys-imageio/metadata/pom.xml new file mode 100644 index 00000000..5ab79418 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + com.twelvemonkeys.imageio + twelvemonkeys-imageio-metadata + 2.3-SNAPSHOT + TwelveMonkeys ImageIO Metadata + + ImageIO metadata module. + + + + twelvemonkeys-imageio + com.twelvemonkeys + 2.3-SNAPSHOT + + + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + tests + + + diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java new file mode 100644 index 00000000..dbad3f8c --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java @@ -0,0 +1,116 @@ +package com.twelvemonkeys.imageio.metadata; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * AbstractDirectory + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: AbstractDirectory.java,v 1.0 Nov 11, 2009 5:31:04 PM haraldk Exp$ + */ +public abstract class AbstractDirectory implements Directory { + // A linked hashmap or a stable bag structure might also work.. + private final List mEntries = new ArrayList(); + + protected AbstractDirectory(final Collection pEntries) { + if (pEntries != null) { + mEntries.addAll(pEntries); + } + } + + public Entry getEntryById(final Object pIdentifier) { + for (Entry entry : this) { + if (entry.getIdentifier().equals(pIdentifier)) { + return entry; + } + } + + return null; + } + + public Entry getEntryByName(final String pName) { + for (Entry entry : this) { + if (entry.getFieldName().equals(pName)) { + return entry; + } + } + + return null; + } + + public Iterator iterator() { + return mEntries.iterator(); + } + + /** + * Throws {@code UnsupportedOperationException} if this directory is read-only. + * + * @throws UnsupportedOperationException if this directory is read-only. + * @see #isReadOnly() + */ + protected final void assertMutable() { + if (isReadOnly()) { + throw new UnsupportedOperationException("Directory is read-only"); + } + } + + public boolean add(final Entry pEntry) { + assertMutable(); + + // TODO: Replace if entry is already present? + // Some directories may need special ordering, or may/may not support multiple entries for certain ids... + return mEntries.add(pEntry); + } + + public boolean remove(final Object pEntry) { + assertMutable(); + + return mEntries.remove(pEntry); + } + + public int size() { + return mEntries.size(); + } + + /** + * This implementation returns {@code true}. + * Subclasses should override this method, if the directory is mutable. + * + * @return {@code true} + */ + public boolean isReadOnly() { + return true; + } + + /// Standard object support + + @Override + public int hashCode() { + return mEntries.hashCode(); + } + + @Override + public boolean equals(final Object pOther) { + if (this == pOther) { + return true; + } + + if (getClass() != pOther.getClass()) { + return false; + } + + // Safe cast, as it must be a subclass for the classes to be equal + AbstractDirectory other = (AbstractDirectory) pOther; + + return mEntries.equals(other.mEntries); + } + + @Override + public String toString() { + return String.format("%s%s", getClass().getSimpleName(), mEntries.toString()); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java new file mode 100644 index 00000000..e03a917d --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java @@ -0,0 +1,99 @@ +package com.twelvemonkeys.imageio.metadata; + +import com.twelvemonkeys.lang.Validate; + +import java.lang.reflect.Array; + +/** + * AbstractEntry + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: AbstractEntry.java,v 1.0 Nov 12, 2009 12:43:13 AM haraldk Exp$ + */ +public abstract class AbstractEntry implements Entry { + + private final Object mIdentifier; + private final Object mValue; // TODO: Might need to be mutable.. + + protected AbstractEntry(final Object pIdentifier, final Object pValue) { + Validate.notNull(pIdentifier, "identifier"); + + mIdentifier = pIdentifier; + mValue = pValue; + } + + public Object getIdentifier() { + return mIdentifier; + } + + /** + * Returns {@code null}, meaning unknown or undefined. + * + * @return {@code null}. + */ + public String getFieldName() { + return null; + } + + public Object getValue() { + return mValue; + } + + public String getValueAsString() { + return String.valueOf(mValue); + } + + public String getTypeName() { + if (mValue == null) { + return null; + } + + return mValue.getClass().getSimpleName(); + } + + public int valueCount() { + // TODO: Collection support? + if (mValue != null && mValue.getClass().isArray()) { + return Array.getLength(mValue); + } + + return 1; + } + + + /// Object + + + @Override + public int hashCode() { + return mIdentifier.hashCode() + 31 * mValue.hashCode(); + } + + @Override + public boolean equals(final Object pOther) { + if (this == pOther) { + return true; + } + if (!(pOther instanceof AbstractEntry)) { + return false; + } + + AbstractEntry other = (AbstractEntry) pOther; + + return mIdentifier.equals(other.mIdentifier) && ( + mValue == null && other.mValue == null || mValue != null && mValue.equals(other.mValue) + ); + } + + @Override + public String toString() { + String name = getFieldName(); + String nameStr = name != null ? "/" + name + "" : ""; + + String type = getTypeName(); + String typeStr = type != null ? " (" + type + ")" : ""; + + return String.format("%s%s: %s%s", getIdentifier(), nameStr, getValueAsString(), typeStr); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java new file mode 100644 index 00000000..66fa5011 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java @@ -0,0 +1,33 @@ +package com.twelvemonkeys.imageio.metadata; + +/** + * Directory + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: Directory.java,v 1.0 Nov 11, 2009 4:20:58 PM haraldk Exp$ + */ +public interface Directory extends Iterable { + // TODO: Spec when more entries exist? Or make Entry support multi-values!? + // For multiple entries with same id in directory, the first entry (using the order from the stream) will be returned + Entry getEntryById(Object pIdentifier); + + Entry getEntryByName(String pName); + + // Iterator containing the entries in + //Iterator getBestEntries(Object pIdentifier, Object pQualifier, String pLanguage); + + + /// Collection-like API + // TODO: addOrReplaceIfPresent... (trouble for multi-values) Or mutable entries? + // boolean replace(Entry pEntry)?? + // boolean contains(Object pIdentifier)? + + boolean add(Entry pEntry); + + boolean remove(Object pEntry); // Object in case we retro-fit Collection/Map.. + + int size(); + + boolean isReadOnly(); +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java new file mode 100644 index 00000000..3b8f7da2 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java @@ -0,0 +1,39 @@ +package com.twelvemonkeys.imageio.metadata; + +/** + * Entry + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: Entry.java,v 1.0 Nov 11, 2009 4:21:08 PM haraldk Exp$ + */ +public interface Entry { + // "tag" identifier from spec + Object getIdentifier(); + + // Human readable "tag" (field) name from sepc + String getFieldName(); + + // The internal "tag" value as stored in the stream, may be a Directory + Object getValue(); + + // Human readable "tag" value + String getValueAsString(); + + //void setValue(Object pValue); // TODO: qualifiers... + + // Optional, implementation/spec specific type, describing the object returned from getValue + String getTypeName(); + + // TODO: Or something like getValue(qualifierType, qualifierValue) + getQualifiers()/getQualifierValues + // TODO: The problem with current model is getEntry() which only has single value support + + // Optional, xml:lang-support + //String getLanguage(); + + // Optional, XMP alt-support. TODO: Do we need both? + //Object getQualifier(); + + // For arrays only + int valueCount(); +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java new file mode 100644 index 00000000..d67aff25 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java @@ -0,0 +1,15 @@ +package com.twelvemonkeys.imageio.metadata; + +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; + +/** + * MetadataReader + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: MetadataReader.java,v 1.0 Nov 13, 2009 8:38:11 PM haraldk Exp$ + */ +public abstract class MetadataReader { + public abstract Directory read(ImageInputStream pInput) throws IOException; +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java new file mode 100644 index 00000000..4fc8c22f --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java @@ -0,0 +1,47 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +/** + * EXIF + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$ + */ +interface EXIF { + /* + 1 = BYTE 8-bit unsigned integer. + 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte + must be NUL (binary zero). + 3 = SHORT 16-bit (2-byte) unsigned integer. + 4 = LONG 32-bit (4-byte) unsigned integer. + 5 = RATIONAL Two LONGs: the first represents the numerator of a + fraction; the second, the denominator. + + 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. + 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer. + 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer. + 10 = SRATIONAL Two SLONGs: the first represents the numerator of a + fraction, the second the denominator. + 11 = FLOAT Single precision (4-byte) IEEE format. + 12 = DOUBLE Double precision (8-byte) IEEE format. + */ + + static int EXIF_IFD = 0x8769; + + static String[] TYPE_NAMES = { + "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", + + "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", + }; + + static int[] TYPE_LENGTHS = { + 1, 1, 2, 4, 8, + + 1, 1, 2, 4, 8, 4, 8, + }; + + int TIFF_MAGIC = 42; +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java new file mode 100644 index 00000000..98d426de --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java @@ -0,0 +1,20 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +import com.twelvemonkeys.imageio.metadata.AbstractDirectory; +import com.twelvemonkeys.imageio.metadata.Entry; + +import java.util.Collection; +import java.util.List; + +/** + * EXIFDirectory + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: EXIFDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$ + */ +final class EXIFDirectory extends AbstractDirectory { + EXIFDirectory(final Collection pEntries) { + super(pEntries); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java new file mode 100644 index 00000000..7ed7cbb5 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -0,0 +1,30 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +import com.twelvemonkeys.imageio.metadata.AbstractEntry; + +/** + * EXIFEntry + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$ + */ +final class EXIFEntry extends AbstractEntry { + final private short mType; + + EXIFEntry(final Object pIdentifier, final Object pValue, final short pType) { + super(pIdentifier, pValue); + mType = pType; + } + + @Override + public String getFieldName() { + // TODO: Need tons of constants... ;-) + return super.getFieldName(); + } + + @Override + public String getTypeName() { + return EXIF.TYPE_NAMES[mType]; + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java new file mode 100644 index 00000000..d5a406ca --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java @@ -0,0 +1,216 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.MetadataReader; +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +/** + * EXIFReader + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$ + */ +public final class EXIFReader extends MetadataReader { + + @Override + public Directory read(final ImageInputStream pInput) throws IOException { + byte[] bom = new byte[2]; + pInput.readFully(bom); + if (bom[0] == 'I' && bom[1] == 'I') { + pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); + } + else if (!(bom[0] == 'M' && bom[1] == 'M')) { + throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); + } + + int magic = pInput.readUnsignedShort(); + if (magic != EXIF.TIFF_MAGIC) { + throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, EXIF.TIFF_MAGIC)); + } + + long directoryOffset = pInput.readUnsignedInt(); + + return readDirectory(pInput, directoryOffset); + } + + private EXIFDirectory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException { + List entries = new ArrayList(); + + pInput.seek(pOffset); + int entryCount = pInput.readUnsignedShort(); + + for (int i = 0; i < entryCount; i++) { + entries.add(readEntry(pInput)); + } + + long nextOffset = pInput.readUnsignedInt(); + + if (nextOffset != 0) { + EXIFDirectory next = readDirectory(pInput, nextOffset); + + for (Entry entry : next) { + entries.add(entry); + } + } + + return new EXIFDirectory(entries); + } + + private EXIFEntry readEntry(final ImageInputStream pInput) throws IOException { + int tagId = pInput.readUnsignedShort(); + + short type = pInput.readShort(); + int count = pInput.readInt(); // Number of values + + Object value; + + // TODO: Handle other sub-IFDs + // GPS IFD: 0x8825, Interoperability IFD: 0xA005 + if (tagId == EXIF.EXIF_IFD) { + long offset = pInput.readUnsignedInt(); + pInput.mark(); + + try { + value = readDirectory(pInput, offset); + } + 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); + } + } + + return new EXIFEntry(tagId, value, type); + } + + private Object readValue(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); + } + finally { + pInput.seek(pos); + } + } + + private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { + return readValueDirect(pInput, pType, pCount); + } + + private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { + switch (pType) { + case 2: + // TODO: This might be UTF-8 or ISO-8859-1, even 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: + if (pCount == 1) { + return pInput.readUnsignedByte(); + } + case 6: + if (pCount == 1) { + return pInput.readByte(); + } + case 7: + byte[] bytes = new byte[pCount]; + pInput.readFully(bytes); + return bytes; + case 3: + if (pCount == 1) { + return pInput.readUnsignedShort(); + } + case 8: + if (pCount == 1) { + return pInput.readShort(); + } + + short[] shorts = new short[pCount]; + pInput.readFully(shorts, 0, shorts.length); + return shorts; + case 4: + if (pCount == 1) { + return pInput.readUnsignedInt(); + } + case 9: + if (pCount == 1) { + return pInput.readInt(); + } + + int[] ints = new int[pCount]; + pInput.readFully(ints, 0, ints.length); + return ints; + case 11: + if (pCount == 1) { + return pInput.readFloat(); + } + + float[] floats = new float[pCount]; + pInput.readFully(floats, 0, floats.length); + return floats; + case 12: + if (pCount == 1) { + return pInput.readDouble(); + } + + double[] doubles = new double[pCount]; + pInput.readFully(doubles, 0, doubles.length); + return doubles; + + // TODO: Consider using a Rational class + case 5: + if (pCount == 1) { + return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt(); + } + + double[] rationals = new double[pCount]; + for (int i = 0; i < rationals.length; i++) { + rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt(); + } + + return rationals; + case 10: + if (pCount == 1) { + return pInput.readInt() / (double) pInput.readInt(); + } + + double[] srationals = new double[pCount]; + for (int i = 0; i < srationals.length; i++) { + srationals[i] = pInput.readInt() / (double) pInput.readInt(); + } + + return srationals; + + default: + throw new IIOException(String.format("Unknown EXIF type '%s'", pType)); + } + } + + private int getValueLength(final int pType, final int pCount) { + if (pType > 0 && pType <= EXIF.TYPE_LENGTHS.length) { + return EXIF.TYPE_LENGTHS[pType - 1] * pCount; + } + + return -1; + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java new file mode 100644 index 00000000..a762f6bb --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java @@ -0,0 +1,130 @@ +package com.twelvemonkeys.imageio.metadata.iptc; + +/** + * IPTC + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$ + */ +public interface IPTC { + static final int ENVELOPE_RECORD = 1 << 8; + static final int APPLICATION_RECORD = 2 << 8; + + static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90; + + /** 2:00 Record Version (mandatory) */ + public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200 + + /** 2:03 Object Type Reference */ + public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3; + /** 2:04 Object Attribute Reference (repeatable) */ + public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4; + /** 2:05 Object Name */ + public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205 + /** 2:07 Edit Status */ + public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7; + /** 2:08 Editorial Update */ + public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8; + /** 2:10 Urgency */ + public static final int TAG_URGENCY = APPLICATION_RECORD | 10; + /** 2:12 Subect Reference (repeatable) */ + public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12; + /** 2:15 Category */ + public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f + /** 2:20 Supplemental Category (repeatable) */ + public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20; + /** 2:22 Fixture Identifier */ + public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22; + /** 2:25 Keywords (repeatable) */ + public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25; + /** 2:26 Content Locataion Code (repeatable) */ + public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26; + /** 2:27 Content Locataion Name (repeatable) */ + public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27; + /** 2:30 Release Date */ + public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30; + /** 2:35 Release Time */ + public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35; + /** 2:37 Expiration Date */ + public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37; + /** 2:38 Expiration Time */ + public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38; + /** 2:40 Special Instructions */ + public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228 + /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */ + public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42; + /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */ + public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45; + /** 2:47 Reference Date (mandatory if 2:45 present) */ + public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47; + /** 2:50 Reference Number (mandatory if 2:45 present) */ + public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50; + /** 2:55 Date Created */ + public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237 + /** 2:60 Time Created */ + public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60; + /** 2:62 Digital Creation Date */ + public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62; + /** 2:63 Digital Creation Date */ + public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63; + /** 2:65 Originating Program */ + public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65; + /** 2:70 Program Version (only valid if 2:65 present) */ + public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70; + /** 2:75 Object Cycle (a: morning, p: evening, b: both) */ + public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75; + /** 2:80 By-line (repeatable) */ + public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250 + /** 2:85 By-line Title (repeatable) */ + public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255 + /** 2:90 City */ + public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a + /** 2:92 Sub-location */ + public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92; + /** 2:95 Province/State */ + public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f + /** 2:100 Country/Primary Location Code */ + public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100; + /** 2:101 Country/Primary Location Name */ + public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265 + /** 2:103 Original Transmission Reference */ + public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267 + /** 2:105 Headline */ + public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269 + /** 2:110 Credit */ + public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e + /** 2:115 Source */ + public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273 + /** 2:116 Copyright Notice */ + public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274 + /** 2:118 Contact */ + public static final int TAG_CONTACT = APPLICATION_RECORD | 118; + /** 2:120 Catption/Abstract */ + public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278 + /** 2:122 Writer/Editor (repeatable) */ + public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a + /** 2:125 Rasterized Caption (binary data) */ + public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125; + /** 2:130 Image Type */ + public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130; + /** 2:131 Image Orientation */ + public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131; + /** 2:135 Language Identifier */ + public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135; + + // TODO: 2:150-2:154 Audio + + // TODO: Should we expose this field? + /** + * 2:199 JobMinder Assignment Data (Custom IPTC field). + * A common custom IPTC field used by a now discontinued application called JobMinder. + * + * @see JobMinder Homepage + */ + static final int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199; + + // TODO: Other custom fields in 155-200 range? + + // TODO: 2:200-2:202 Object Preview Data +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java new file mode 100644 index 00000000..e3bb2745 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java @@ -0,0 +1,19 @@ +package com.twelvemonkeys.imageio.metadata.iptc; + +import com.twelvemonkeys.imageio.metadata.AbstractDirectory; +import com.twelvemonkeys.imageio.metadata.Entry; + +import java.util.Collection; + +/** + * IPTCDirectory + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: IPTCDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$ + */ +final class IPTCDirectory extends AbstractDirectory { + IPTCDirectory(final Collection pEntries) { + super(pEntries); + } +} \ No newline at end of file diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java new file mode 100644 index 00000000..8773fc48 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java @@ -0,0 +1,16 @@ +package com.twelvemonkeys.imageio.metadata.iptc; + +import com.twelvemonkeys.imageio.metadata.AbstractEntry; + +/** +* IPTCEntry +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: IPTCEntry.java,v 1.0 Nov 13, 2009 8:57:04 PM haraldk Exp$ +*/ +class IPTCEntry extends AbstractEntry { + public IPTCEntry(int pTagId, Object pValue) { + super(pTagId, pValue); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java new file mode 100644 index 00000000..863f039b --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java @@ -0,0 +1,149 @@ +package com.twelvemonkeys.imageio.metadata.iptc; + +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.MetadataReader; +import com.twelvemonkeys.lang.StringUtil; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.ArrayList; +import java.util.List; + +/** + * IPTCReader + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: IPTCReader.java,v 1.0 Nov 13, 2009 8:37:23 PM haraldk Exp$ + */ +public class IPTCReader extends MetadataReader { + private static final int ENCODING_UNKNOWN = -1; + private static final int ENCODING_UNSPECIFIED = 0; + private static final int ENCODING_UTF_8 = 0x1b2547; + + private int mEncoding = ENCODING_UNSPECIFIED; + + + @Override + public Directory read(final ImageInputStream pInput) throws IOException { + final List entries = new ArrayList(); + + // 0x1c identifies start of a tag + while (pInput.read() == 0x1c) { + int tagId = pInput.readShort(); + int tagByteCount = pInput.readUnsignedShort(); + + Entry entry = readEntry(pInput, tagId, tagByteCount); + if (entry != null) { + entries.add(entry); + } + } + + return new IPTCDirectory(entries); + } + + private Entry readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException { + Object value = null; + + switch (pTagId) { + case IPTC.TAG_CODED_CHARACTER_SET: + // TODO: Mapping from ISO 646 to Java supported character sets? + // TODO: Move somewhere else? + mEncoding = parseEncoding(pInput, pLength); + return null; + case IPTC.TAG_RECORD_VERSION: + // A single unsigned short value + value = pInput.readUnsignedShort(); + break; +// case IPTC.TAG_RELEASE_DATE: +// case IPTC.TAG_EXPIRATION_DATE: +// case IPTC.TAG_REFERENCE_DATE: +// case IPTC.TAG_DATE_CREATED: +// case IPTC.TAG_DIGITAL_CREATION_DATE: +// // Date object +// Date date = parseISO8601DatePart(pInput, tagByteCount); +// if (date != null) { +// directory.setDate(tagIdentifier, date); +// return; +// } +// case IPTC.TAG_RELEASE_TIME: +// case IPTC.TAG_EXPIRATION_TIME: +// case IPTC.TAG_TIME_CREATED: +// case IPTC.TAG_DIGITAL_CREATION_TIME: +// // NOTE: Spec says fields should be sent in order, so this is okay +// date = getDateForTime(directory, tagIdentifier); +// +// Date time = parseISO8601TimePart(pInput, tagByteCount, date); +// if (time != null) { +// directory.setDate(tagIdentifier, time); +// return; +// } +// + default: + // Skip non-Application fields, as they are typically not human readable + if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { + pInput.skipBytes(pLength); + return null; + } + + // fall through + } + + // If we don't have a value, treat it as a string + if (value == null) { + if (pLength < 1) { + value = "(No value)"; + } + else { + value = parseString(pInput, pLength); + } + } + + return new IPTCEntry(pTagId, value); + } + + private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException { + return tagByteCount == 3 + && (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8 + ? ENCODING_UTF_8 : ENCODING_UNKNOWN; + } + + // TODO: Pass encoding as parameter? Use if specified + private String parseString(final ImageInputStream pInput, final int pLength) throws IOException { + byte[] data = new byte[pLength]; + pInput.readFully(data); + + // NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding. + // UTF-8 contains all 646 characters, but not 2022. + // This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html + Charset charset = Charset.forName("UTF-8"); + CharsetDecoder decoder = charset.newDecoder(); + + try { + // First try to decode using UTF-8 (which seems to be the de-facto standard) + // Will fail fast on illegal UTF-8-sequences + CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT) + .decode(ByteBuffer.wrap(data)); + return chars.toString(); + } + catch (CharacterCodingException notUTF8) { + if (mEncoding == ENCODING_UTF_8) { + throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8); + } + + // Fall back to use ISO-8859-1 + // This will not fail, but may may create wrong fallback-characters + return StringUtil.decode(data, 0, data.length, "ISO8859_1"); + } + } + +} diff --git a/twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java b/twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java new file mode 100644 index 00000000..9d4e9a96 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScannerTestCase.java @@ -0,0 +1,127 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import junit.framework.TestCase; + +import java.io.*; +import java.nio.charset.UnsupportedCharsetException; +import java.util.Arrays; +import java.util.Collections; +import java.util.Random; + +/** + * XMPScannerTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPScannerTestCase.java,v 1.0 Nov 13, 2009 3:59:43 PM haraldk Exp$ + */ +public class XMPScannerTestCase extends TestCase { + + static final String XMP = + "" + + "\n"+ + " \n"+ + " \n"+ + " twelvemonkeys.com\n"+ + " \n"+ + " \n"+ + " application/vnd.adobe.photoshop\n"+ + " \n"+ + " \n"+ + "" + + ""; + + final Random mRandom = new Random(4934638567l); + + private InputStream createRandomStream(final int pLength) { + byte[] bytes = new byte[pLength]; + mRandom.nextBytes(bytes); + return new ByteArrayInputStream(bytes); + } + + private InputStream createXMPStream(final String pXMP, final String pCharsetName) { + try { + return new SequenceInputStream( + Collections.enumeration( + Arrays.asList( + createRandomStream(79), + new ByteArrayInputStream(pXMP.getBytes(pCharsetName)), + createRandomStream(31) + ) + ) + ); + } + catch (UnsupportedEncodingException e) { + UnsupportedCharsetException uce = new UnsupportedCharsetException(pCharsetName); + uce.initCause(e); + throw uce; + } + } + + public void testScanForUTF8() throws IOException { + InputStream stream = createXMPStream(XMP, "UTF-8"); + + Reader reader = XMPScanner.scanForXMPPacket(stream); + + assertNotNull(reader); + } + + public void testScanForUTF8singleQuote() throws IOException { + InputStream stream = createXMPStream(XMP, "UTF-8".replace("\"", "'")); + + Reader reader = XMPScanner.scanForXMPPacket(stream); + + assertNotNull(reader); + } + + public void testScanForUTF16BE() throws IOException { + InputStream stream = createXMPStream(XMP, "UTF-16BE"); + + Reader reader = XMPScanner.scanForXMPPacket(stream); + + assertNotNull(reader); + } + + public void testScanForUTF16BEsingleQuote() throws IOException { + InputStream stream = createXMPStream(XMP, "UTF-16BE".replace("\"", "'")); + + Reader reader = XMPScanner.scanForXMPPacket(stream); + + assertNotNull(reader); + } + + public void testScanForUTF16LE() throws IOException { + InputStream stream = createXMPStream(XMP, "UTF-16LE"); + + Reader reader = XMPScanner.scanForXMPPacket(stream); + + assertNotNull(reader); + } + + public void testScanForUTF16LEsingleQuote() throws IOException { + InputStream stream = createXMPStream(XMP, "UTF-16LE".replace("\"", "'")); + + Reader reader = XMPScanner.scanForXMPPacket(stream); + + assertNotNull(reader); + } + + // TODO: Default Java installation on OS X don't seem to have UTF-32 installed. Hmmm.. +// public void testUTF32BE() throws IOException { +// InputStream stream = createXMPStream("UTF-32BE"); +// +// Reader reader = XMPScanner.scanForXMPPacket(stream); +// +// assertNotNull(reader); +// } +// +// public void testUTF32LE() throws IOException { +// InputStream stream = createXMPStream("UTF-32LE"); +// +// Reader reader = XMPScanner.scanForXMPPacket(stream); +// +// assertNotNull(reader); +// } +} diff --git a/twelvemonkeys-imageio/pom.xml b/twelvemonkeys-imageio/pom.xml index cd33e3fc..97bd4e9e 100644 --- a/twelvemonkeys-imageio/pom.xml +++ b/twelvemonkeys-imageio/pom.xml @@ -29,6 +29,7 @@ core + metadata ico @@ -96,6 +97,13 @@ tests test + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-metadata + ${imageio.core.version} + compile + diff --git a/twelvemonkeys-imageio/psd/pom.xml b/twelvemonkeys-imageio/psd/pom.xml index 455d245c..a19dce2e 100644 --- a/twelvemonkeys-imageio/psd/pom.xml +++ b/twelvemonkeys-imageio/psd/pom.xml @@ -1,31 +1,35 @@ - - - 4.0.0 - com.twelvemonkeys.imageio - twelvemonkeys-imageio-psd - 2.3-SNAPSHOT - TwelveMonkeys ImageIO PSD plugin - - ImageIO plugin for Adobe Photoshop Document (PSD). - - - - twelvemonkeys-imageio - com.twelvemonkeys - 2.3-SNAPSHOT - - - - - com.twelvemonkeys.imageio - twelvemonkeys-imageio-core - - - com.twelvemonkeys.imageio - twelvemonkeys-imageio-core - tests - - - \ No newline at end of file + + + 4.0.0 + com.twelvemonkeys.imageio + twelvemonkeys-imageio-psd + 2.3-SNAPSHOT + TwelveMonkeys ImageIO PSD plugin + + ImageIO plugin for Adobe Photoshop Document (PSD). + + + + twelvemonkeys-imageio + com.twelvemonkeys + 2.3-SNAPSHOT + + + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-core + tests + + + com.twelvemonkeys.imageio + twelvemonkeys-imageio-metadata + + + diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java new file mode 100644 index 00000000..5ba2d84a --- /dev/null +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java @@ -0,0 +1,103 @@ +package com.twelvemonkeys.imageio.plugins.psd; + +import org.w3c.dom.Node; + +import javax.imageio.metadata.IIOInvalidTreeException; +import javax.imageio.metadata.IIOMetadata; +import javax.imageio.metadata.IIOMetadataFormatImpl; +import java.util.Arrays; + +/** + * AbstractMetadata + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$ + */ +abstract class AbstractMetadata extends IIOMetadata implements Cloneable { + + protected AbstractMetadata(final boolean pStandardFormatSupported, + final String pNativeFormatName, final String pNativeFormatClassName, + final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) { + super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames); + } + + /** + * Default implementation returns {@code true}. + * Mutable subclasses should override this method. + * + * @return {@code true}. + */ + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public Node getAsTree(final String pFormatName) { + validateFormatName(pFormatName); + + if (pFormatName.equals(nativeMetadataFormatName)) { + return getNativeTree(); + } + else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { + return getStandardTree(); + } + + // TODO: What about extra formats?? + throw new AssertionError("Unreachable"); + } + + @Override + public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException { + assertMutable(); + + validateFormatName(pFormatName); + + if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) { + throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot); + } + + Node node = pRoot.getFirstChild(); + while (node != null) { + // TODO: Merge values from node into this + + // Move to the next sibling + node = node.getNextSibling(); + } + } + + @Override + public void reset() { + assertMutable(); + } + + /** + * Asserts that this meta data is mutable. + * + * @throws IllegalStateException if {@link #isReadOnly()} returns {@code true}. + */ + protected final void assertMutable() { + if (isReadOnly()) { + throw new IllegalStateException("Metadata is read-only"); + } + } + + protected abstract Node getNativeTree(); + + protected final void validateFormatName(final String pFormatName) { + String[] metadataFormatNames = getMetadataFormatNames(); + + if (metadataFormatNames != null) { + for (String metadataFormatName : metadataFormatNames) { + if (metadataFormatName.equals(pFormatName)) { + return; // Found, we're ok! + } + } + } + + throw new IllegalArgumentException( + String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames)) + ); + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java index ed0b4edd..9918f429 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDAlphaChannelInfo.java @@ -34,11 +34,11 @@ import java.util.ArrayList; import java.util.List; /** - * PSDAlhpaChannelInfo + * PSDAlphaChannelInfo * * @author Harald Kuhr * @author last modified by $Author: haraldk$ - * @version $Id: PSDAlhpaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$ + * @version $Id: PSDAlphaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$ */ class PSDAlphaChannelInfo extends PSDImageResource { List mNames; @@ -50,6 +50,7 @@ class PSDAlphaChannelInfo extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { mNames = new ArrayList(); + long left = mSize; while (left > 0) { String name = PSDUtil.readPascalString(pInput); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java index 17f181aa..47a30e94 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java @@ -1,13 +1,11 @@ package com.twelvemonkeys.imageio.plugins.psd; -import com.twelvemonkeys.imageio.util.IIOUtil; +import com.twelvemonkeys.imageio.metadata.exif.EXIFReader; import com.twelvemonkeys.lang.StringUtil; import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; -import javax.imageio.stream.MemoryCacheImageInputStream; import java.io.IOException; -import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -26,7 +24,8 @@ import java.util.List; */ final class PSDEXIF1Data extends PSDImageResource { // protected byte[] mData; - protected Directory mDirectory; +// protected Directory mDirectory; + protected com.twelvemonkeys.imageio.metadata.Directory mDirectory; PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -36,24 +35,25 @@ final class PSDEXIF1Data extends PSDImageResource { protected void readData(final ImageInputStream pInput) throws IOException { // This is in essence an embedded TIFF file. // TODO: Extract TIFF parsing to more general purpose package - // TODO: Instead, read the byte data, store for later parsing (or store offset, and read on request) - MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize)); - - byte[] bom = new byte[2]; - stream.readFully(bom); - if (bom[0] == 'I' && bom[1] == 'I') { - stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); - } - else if (!(bom[0] == 'M' && bom[1] == 'M')) { - throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); - } - - if (stream.readUnsignedShort() != 42) { - throw new IIOException("Wrong TIFF magic in EXIF data."); - } - - long directoryOffset = stream.readUnsignedInt(); - mDirectory = Directory.read(stream, directoryOffset); + // TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request) + mDirectory = new EXIFReader().read(pInput); +// byte[] bom = new byte[2]; +// stream.readFully(bom); +// if (bom[0] == 'I' && bom[1] == 'I') { +// stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); +// } +// else if (!(bom[0] == 'M' && bom[1] == 'M')) { +// throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); +// } +// +// if (stream.readUnsignedShort() != 42) { +// throw new IIOException("Wrong TIFF magic in EXIF data."); +// } +// +// long directoryOffset = stream.readUnsignedInt(); +// +// // Read TIFF directory +// mDirectory = Directory.read(stream, directoryOffset); } @Override @@ -78,11 +78,13 @@ final class PSDEXIF1Data extends PSDImageResource { pInput.seek(pOffset); int entryCount = pInput.readUnsignedShort(); + for (int i = 0; i < entryCount; i++) { directory.mEntries.add(Entry.read(pInput)); } long nextOffset = pInput.readUnsignedInt(); + if (nextOffset != 0) { Directory next = Directory.read(pInput, nextOffset); directory.mEntries.addAll(next.mEntries); @@ -91,9 +93,9 @@ final class PSDEXIF1Data extends PSDImageResource { return directory; } - public Entry get(int pTag) { + public Entry get(int pTagId) { for (Entry entry : mEntries) { - if (entry.mTag == pTag) { + if (entry.mTagId == pTagId) { return entry; } } @@ -127,7 +129,7 @@ final class PSDEXIF1Data extends PSDImageResource { 1, 1, 2, 4, 8, 4, 8, }; - private int mTag; + final int mTagId; /* 1 = BYTE 8-bit unsigned integer. 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte @@ -153,19 +155,22 @@ final class PSDEXIF1Data extends PSDImageResource { private long mValueOffset; private Object mValue; - private Entry() {} + private Entry(int pTagId) { + mTagId = pTagId; + } public static Entry read(final ImageInputStream pInput) throws IOException { - Entry entry = new Entry(); + Entry entry = new Entry(pInput.readUnsignedShort()); - entry.mTag = pInput.readUnsignedShort(); entry.mType = pInput.readShort(); entry.mCount = pInput.readInt(); // Number of values // TODO: Handle other sub-IFDs - if (entry.mTag == EXIF_IFD) { + // GPS IFD: 0x8825, Interoperability IFD: 0xA005 + if (entry.mTagId == EXIF_IFD) { long offset = pInput.readUnsignedInt(); pInput.mark(); + try { entry.mValue = Directory.read(pInput, offset); } @@ -175,6 +180,7 @@ final class PSDEXIF1Data extends PSDImageResource { } else { int valueLength = entry.getValueLength(); + if (valueLength > 0 && valueLength <= 4) { entry.readValueInLine(pInput); pInput.skipBytes(4 - valueLength); @@ -299,22 +305,21 @@ final class PSDEXIF1Data extends PSDImageResource { return -1; } - private String getTypeName() { + public final String getTypeName() { if (mType > 0 && mType <= TYPE_NAMES.length) { return TYPE_NAMES[mType - 1]; } + return "Unknown type"; } - // TODO: Tag names! - @Override - public String toString() { - return String.format("0x%04x: %s (%s, %d)", mTag, getValueAsString(), getTypeName(), mCount); + public final Object getValue() { + return mValue; } - public String getValueAsString() { + public final String getValueAsString() { if (mValue instanceof String) { - return String.format("\"%s\"", mValue); + return String.format("%s", mValue); } if (mValue != null && mValue.getClass().isArray()) { @@ -338,5 +343,11 @@ final class PSDEXIF1Data extends PSDImageResource { return String.valueOf(mValue); } + + // TODO: Tag names! + @Override + public String toString() { + return String.format("0x%04x: %s (%s, %d)", mTagId, mType == 2 ? String.format("\"%s\"", mValue) : getValueAsString(), getTypeName(), mCount); + } } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java index 87e6b4b1..4f336eb1 100755 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java @@ -48,17 +48,19 @@ class PSDGlobalLayerMask { final int mKind; PSDGlobalLayerMask(final ImageInputStream pInput) throws IOException { - mColorSpace = pInput.readUnsignedShort(); + mColorSpace = pInput.readUnsignedShort(); // Undocumented mColor1 = pInput.readUnsignedShort(); mColor2 = pInput.readUnsignedShort(); mColor3 = pInput.readUnsignedShort(); mColor4 = pInput.readUnsignedShort(); - mOpacity = pInput.readUnsignedShort(); + mOpacity = pInput.readUnsignedShort(); // 0-100 + + mKind = pInput.readUnsignedByte(); // 0: Selected (ie inverted), 1: Color protected, 128: Use value stored per layer + + // TODO: Variable: Filler zeros - mKind = pInput.readUnsignedByte(); - pInput.readByte(); // Pad } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java index 000084e7..04846e07 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java @@ -1,5 +1,6 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader; import com.twelvemonkeys.lang.StringUtil; import javax.imageio.IIOException; @@ -7,8 +8,13 @@ import javax.imageio.stream.ImageInputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; -import java.nio.charset.*; -import java.util.*; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; /** * PSDIPTCData @@ -20,7 +26,7 @@ import java.util.*; final class PSDIPTCData extends PSDImageResource { // TODO: Refactor to be more like PSDEXIF1Data... // TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s) - Directory mDirectory; + com.twelvemonkeys.imageio.metadata.Directory mDirectory; PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -28,7 +34,8 @@ final class PSDIPTCData extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { - mDirectory = Directory.read(pInput, mSize); + // Read IPTC directory + mDirectory = new IPTCReader().read(pInput); } @Override @@ -40,17 +47,37 @@ final class PSDIPTCData extends PSDImageResource { } static class Entry { - private int mTagId; - private String mValue; + final int mTagId; + private Object mValue; - public Entry(int pTagId, String pValue) { + public Entry(final int pTagId, final Object pValue) { mTagId = pTagId; mValue = pValue; } @Override public String toString() { - return (mTagId >> 8) + ":" + (mTagId & 0xff) + ": " + mValue; + return String.format("%d:%d: %s", mTagId >> 8, mTagId & 0xff, mValue); + } + + public final String getTypeName() { + // TODO: Should this really look like EXIF? + if (mTagId == IPTC.TAG_RECORD_VERSION) { + return "SHORT"; + } + else if (mValue instanceof String) { + return "ASCII"; + } + + return "Unknown type"; + } + + public final String getValueAsString() { + return String.valueOf(mValue); + } + + public final Object getValue() { + return mValue; } } @@ -60,6 +87,7 @@ final class PSDIPTCData extends PSDImageResource { private static final int ENCODING_UTF_8 = 0x1b2547; private int mEncoding = ENCODING_UNSPECIFIED; + final List mEntries = new ArrayList(); private Directory() {} @@ -69,6 +97,16 @@ final class PSDIPTCData extends PSDImageResource { return "Directory" + mEntries.toString(); } + public Entry get(int pTagId) { + for (Entry entry : mEntries) { + if (entry.mTagId == pTagId) { + return entry; + } + } + + return null; + } + public Iterator iterator() { return mEntries.iterator(); } @@ -81,43 +119,33 @@ final class PSDIPTCData extends PSDImageResource { // For each tag while (pInput.getStreamPosition() < streamEnd) { // Identifies start of a tag - byte b = pInput.readByte(); - if (b != 0x1c) { - throw new IIOException("Corrupt IPTC stream segment"); + byte marker = pInput.readByte(); + + if (marker != 0x1c) { + throw new IIOException(String.format("Corrupt IPTC stream segment, found 0x%02x (expected 0x1c)", marker)); } - // We need at least four bytes left to read a tag - if (pInput.getStreamPosition() + 4 >= streamEnd) { - break; - } - - int directoryType = pInput.readUnsignedByte(); - int tagType = pInput.readUnsignedByte(); + int tagId = pInput.readShort(); int tagByteCount = pInput.readUnsignedShort(); - if (pInput.getStreamPosition() + tagByteCount > streamEnd) { - throw new IIOException("Data for tag extends beyond end of IPTC segment: " + (tagByteCount + pInput.getStreamPosition() - streamEnd)); - } - - directory.processTag(pInput, directoryType, tagType, tagByteCount); + directory.readEntry(pInput, tagId, tagByteCount); } return directory; } - private void processTag(ImageInputStream pInput, int directoryType, int tagType, int tagByteCount) throws IOException { - int tagIdentifier = (directoryType << 8) | tagType; + private void readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException { + Object value = null; - String str = null; - switch (tagIdentifier) { + switch (pTagId) { case IPTC.TAG_CODED_CHARACTER_SET: - // TODO: Use this encoding!? + // TODO: Mapping from ISO 646 to Java supported character sets? // TODO: Move somewhere else? - mEncoding = parseEncoding(pInput, tagByteCount); + mEncoding = parseEncoding(pInput, pLength); return; case IPTC.TAG_RECORD_VERSION: - // short - str = Integer.toString(pInput.readUnsignedShort()); + // A single unsigned short value + value = pInput.readUnsignedShort(); break; // case IPTC.TAG_RELEASE_DATE: // case IPTC.TAG_EXPIRATION_DATE: @@ -144,50 +172,26 @@ final class PSDIPTCData extends PSDImageResource { // } // default: + // Skip non-Application fields, as they are typically not human readable + if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { + pInput.skipBytes(pLength); + return; + } + // fall through } - // Skip non-Application fields, as they are typically not human readable - if (directoryType << 8 != IPTC.APPLICATION_RECORD) { - return; - } - // If we don't have a value, treat it as a string - if (str == null) { - if (tagByteCount < 1) { - str = "(No value)"; + if (value == null) { + if (pLength < 1) { + value = "(No value)"; } else { - str = String.format("\"%s\"", parseString(pInput, tagByteCount)); + value = parseString(pInput, pLength); } } - mEntries.add(new Entry(tagIdentifier, str)); - -// if (directory.containsTag(tagIdentifier)) { -// // TODO: Does that REALLY help for performance?! -// // this fancy string[] business avoids using an ArrayList for performance reasons -// String[] oldStrings; -// String[] newStrings; -// try { -// oldStrings = directory.getStringArray(tagIdentifier); -// } -// catch (MetadataException e) { -// oldStrings = null; -// } -// if (oldStrings == null) { -// newStrings = new String[1]; -// } -// else { -// newStrings = new String[oldStrings.length + 1]; -// System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length); -// } -// newStrings[newStrings.length - 1] = str; -// directory.setStringArray(tagIdentifier, newStrings); -// } -// else { -// directory.setString(tagIdentifier, str); -// } + mEntries.add(new Entry(pTagId, value)); } // private Date getDateForTime(final Directory directory, final int tagIdentifier) { @@ -267,22 +271,23 @@ final class PSDIPTCData extends PSDImageResource { // } // TODO: Pass encoding as parameter? Use if specified - private String parseString(final ImageInputStream pInput, int length) throws IOException { - // NOTE: The IPTC "spec" says ISO 646 or ISO 2022 encoding. UTF-8 contains all 646 characters, but not 2022. + private String parseString(final ImageInputStream pInput, final int pLength) throws IOException { + byte[] data = new byte[pLength]; + pInput.readFully(data); + + // NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding. + // UTF-8 contains all 646 characters, but not 2022. // This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html - // First try to decode using UTF-8 (which seems to be the de-facto standard) - String str; Charset charset = Charset.forName("UTF-8"); CharsetDecoder decoder = charset.newDecoder(); - CharBuffer chars; - byte[] data = new byte[length]; - pInput.readFully(data); + try { + // First try to decode using UTF-8 (which seems to be the de-facto standard) // Will fail fast on illegal UTF-8-sequences - chars = decoder.onMalformedInput(CodingErrorAction.REPORT) + CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT) .decode(ByteBuffer.wrap(data)); - str = chars.toString(); + return chars.toString(); } catch (CharacterCodingException notUTF8) { if (mEncoding == ENCODING_UTF_8) { @@ -291,10 +296,8 @@ final class PSDIPTCData extends PSDImageResource { // Fall back to use ISO-8859-1 // This will not fail, but may may create wrong fallback-characters - str = StringUtil.decode(data, 0, data.length, "ISO8859_1"); + return StringUtil.decode(data, 0, data.length, "ISO8859_1"); } - - return str; } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 5eb7112e..6adb8e65 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -62,7 +62,9 @@ import java.util.List; * @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$ */ // TODO: Implement ImageIO meta data interface -// TODO: API for reading separate layers +// TODO: Allow reading the extra alpha channels (index after composite data) +// 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 // http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ // See http://www.codeproject.com/KB/graphics/PSDParser.aspx @@ -1144,11 +1146,12 @@ public class PSDImageReader extends ImageReaderBase { node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName); serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); + serializer.setIndentation(" "); serializer.serialize(node, true); System.out.println(); node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME); - serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); +// serializer = new XMLSerializer(System.out, System.getProperty("file.encoding")); serializer.serialize(node, true); if (imageReader.hasThumbnails(0)) { diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java index 180ce4d7..7aa60dc4 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageResource.java @@ -28,6 +28,7 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.stream.SubImageInputStream; import com.twelvemonkeys.lang.StringUtil; import javax.imageio.stream.ImageInputStream; @@ -62,11 +63,16 @@ class PSDImageResource { } mSize = pInput.readUnsignedInt(); - readData(pInput); + long startPos = pInput.getStreamPosition(); - // TODO: Sanity check reading here? + readData(new SubImageInputStream(pInput, mSize)); - // Data is even-padded + // NOTE: This should never happen, however it's safer to keep it here to + if (pInput.getStreamPosition() != startPos + mSize) { + pInput.seek(startPos + mSize); + } + + // Data is even-padded (word aligned) if (mSize % 2 != 0) { pInput.read(); } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index e622bc01..276d7e07 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -1,14 +1,13 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.util.FilterIterator; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.InputSource; -import javax.imageio.metadata.IIOInvalidTreeException; -import javax.imageio.metadata.IIOMetadata; -import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataNode; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -24,7 +23,7 @@ import java.util.List; * @author last modified by $Author: haraldk$ * @version $Id: PSDMetadata.java,v 1.0 Nov 4, 2009 5:28:12 PM haraldk Exp$ */ -public final class PSDMetadata extends IIOMetadata implements Cloneable { +public final class PSDMetadata extends AbstractMetadata { // TODO: Decide on image/stream metadata... static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0"; @@ -60,98 +59,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"}; - protected PSDMetadata() { // TODO: Allow XMP, EXIF and IPTC as extra formats? super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); } - @Override - public boolean isReadOnly() { - // TODO: Extract to abstract metadata impl class? - return true; - } - - @Override - public Node getAsTree(final String pFormatName) { - validateFormatName(pFormatName); - - if (pFormatName.equals(nativeMetadataFormatName)) { - return getNativeTree(); - } - else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) { - return getStandardTree(); - } - - throw new AssertionError("Unreachable"); - } - - @Override - public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException { - // TODO: Extract to abstract metadata impl class? - assertMutable(); - - validateFormatName(pFormatName); - - if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) { - throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot); - } - - Node node = pRoot.getFirstChild(); - while (node != null) { - // TODO: Merge values from node into this - - // Move to the next sibling - node = node.getNextSibling(); - } - } - - @Override - public void reset() { - // TODO: Extract to abstract metadata impl class? - assertMutable(); - - throw new UnsupportedOperationException("Method reset not implemented"); // TODO: Implement - } - - // TODO: Extract to abstract metadata impl class? - private void assertMutable() { - if (isReadOnly()) { - throw new IllegalStateException("Metadata is read-only"); - } - } - - // TODO: Extract to abstract metadata impl class? - private void validateFormatName(final String pFormatName) { - String[] metadataFormatNames = getMetadataFormatNames(); - - if (metadataFormatNames != null) { - for (String metadataFormatName : metadataFormatNames) { - if (metadataFormatName.equals(pFormatName)) { - return; // Found, we're ok! - } - } - } - - throw new IllegalArgumentException( - String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames)) - ); - } - - @Override - public Object clone() { - // TODO: Make it a deep clone - try { - return super.clone(); - } - catch (CloneNotSupportedException e) { - throw new RuntimeException(e); - } - } - /// Native format support - private Node getNativeTree() { + @Override + protected Node getNativeTree() { IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME); root.appendChild(createHeaderNode()); @@ -195,6 +111,18 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { // TODO: Format spec node = new IIOMetadataNode("ICCProfile"); node.setAttribute("colorSpaceType", JAVA_CS[profile.getProfile().getColorSpaceType()]); +// +// FastByteArrayOutputStream data = new FastByteArrayOutputStream(0); +// EncoderStream base64 = new EncoderStream(data, new Base64Encoder(), true); +// +// try { +// base64.write(profile.getProfile().getData()); +// } +// catch (IOException ignore) { +// } +// +// byte[] bytes = data.toByteArray(); +// node.setAttribute("data", StringUtil.decode(bytes, 0, bytes.length, "ASCII")); node.setUserObject(profile.getProfile()); } else if (imageResource instanceof PSDAlphaChannelInfo) { @@ -215,10 +143,12 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]); StringBuilder builder = new StringBuilder(); + for (short color : displayInfo.mColors) { if (builder.length() > 0) { builder.append(" "); } + builder.append(Integer.toString(color)); } @@ -324,30 +254,65 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { // Transcode to XMP? ;-) PSDIPTCData iptc = (PSDIPTCData) imageResource; - node = new IIOMetadataNode("IPTC"); + node = new IIOMetadataNode("Directory"); + node.setAttribute("type", "IPTC"); node.setUserObject(iptc.mDirectory); + + for (Entry entry : iptc.mDirectory) { + IIOMetadataNode tag = new IIOMetadataNode("Entry"); + tag.setAttribute("tag", String.format("%d:%02d", (Integer) entry.getIdentifier() >> 8, (Integer) entry.getIdentifier() & 0xff)); + + String field = entry.getFieldName(); + if (field != null) { + tag.setAttribute("field", String.format("%s", field)); + } + tag.setAttribute("value", entry.getValueAsString()); + + String type = entry.getTypeName(); + if (type != null) { + tag.setAttribute("type", type); + } + node.appendChild(tag); + } } else if (imageResource instanceof PSDEXIF1Data) { // TODO: Revise/rethink this... // Transcode to XMP? ;-) PSDEXIF1Data exif = (PSDEXIF1Data) imageResource; - node = new IIOMetadataNode("EXIF"); + node = new IIOMetadataNode("Directory"); + node.setAttribute("type", "EXIF"); + // TODO: Set byte[] data instead node.setUserObject(exif.mDirectory); + + appendEntries(node, exif.mDirectory); } else if (imageResource instanceof PSDXMPData) { // TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid... + // Or maybe use the Directory approach used by IPTC and EXIF.. PSDXMPData xmp = (PSDXMPData) imageResource; node = new IIOMetadataNode("XMP"); try { +// BufferedReader reader = new BufferedReader(xmp.getData()); +// String line; +// while ((line = reader.readLine()) != null) { +// System.out.println(line); +// } +// + DocumentBuilder builder; + Document document; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - DocumentBuilder builder = factory.newDocumentBuilder(); - Document document = builder.parse(new InputSource(xmp.getData())); + factory.setNamespaceAware(true); + builder = factory.newDocumentBuilder(); + document = builder.parse(new InputSource(xmp.getData())); + // Set the entire XMP document as user data node.setUserObject(document); +// node.appendChild(document.getFirstChild()); } catch (Exception e) { e.printStackTrace(); @@ -355,7 +320,13 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { } else { // Generic resource.. - node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId)); + node = new IIOMetadataNode("ImageResource"); + String value = PSDImageResource.resourceTypeForId(imageResource.mId); + if (!"UnknownResource".equals(value)) { + node.setAttribute("name", value); + } + node.setAttribute("length", String.valueOf(imageResource.mSize)); + // TODO: Set user object: byte array } // TODO: More resources @@ -364,9 +335,36 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { resource.appendChild(node); } + // TODO: Layers and layer info + + // TODO: Global mask etc.. + return resource; } + private void appendEntries(IIOMetadataNode pNode, final Directory pDirectory) { + for (Entry entry : pDirectory) { + IIOMetadataNode tag = new IIOMetadataNode("Entry"); + tag.setAttribute("tag", String.format("%s", entry.getIdentifier())); + + String field = entry.getFieldName(); + if (field != null) { + tag.setAttribute("field", String.format("%s", field)); + } + + if (entry.getValue() instanceof Directory) { + appendEntries(tag, (Directory) entry.getValue()); + tag.setAttribute("type", "Directory"); + } + else { + tag.setAttribute("value", entry.getValueAsString()); + tag.setAttribute("type", entry.getTypeName()); + } + + pNode.appendChild(tag); + } + } + /// Standard format support @Override @@ -461,7 +459,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { private String getMultiChannelCS(short pChannels) { if (pChannels < 16) { - return Integer.toHexString(pChannels) + "CLR"; + return String.format("%xCLR", pChannels); } throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels"); @@ -469,88 +467,101 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { @Override protected IIOMetadataNode getStandardCompressionNode() { - IIOMetadataNode compression_node = new IIOMetadataNode("Compression"); + IIOMetadataNode compressionNode = new IIOMetadataNode("Compression"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("CompressionTypeName"); String compression; + switch (mCompression) { case PSD.COMPRESSION_NONE: compression = "none"; break; case PSD.COMPRESSION_RLE: - compression = "packbits"; + compression = "PackBits"; break; case PSD.COMPRESSION_ZIP: case PSD.COMPRESSION_ZIP_PREDICTION: - compression = "zip"; + compression = "Deflate"; // TODO: ZLib? (TIFF native metadata format specifies both.. :-P) break; default: throw new AssertionError("Unreachable"); } - node.setAttribute("value", compression); - compression_node.appendChild(node); + node.setAttribute("value", compression); + compressionNode.appendChild(node); + + // TODO: Does it make sense to specify lossless for compression "none"? node = new IIOMetadataNode("Lossless"); node.setAttribute("value", "true"); - compression_node.appendChild(node); + compressionNode.appendChild(node); - return compression_node; + return compressionNode; } @Override protected IIOMetadataNode getStandardDataNode() { - IIOMetadataNode data_node = new IIOMetadataNode("Data"); + IIOMetadataNode dataNode = new IIOMetadataNode("Data"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("PlanarConfiguration"); node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec - data_node.appendChild(node); + dataNode.appendChild(node); node = new IIOMetadataNode("SampleFormat"); node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral"); - data_node.appendChild(node); + dataNode.appendChild(node); String bitDepth = Integer.toString(mHeader.mBits); // bits per plane + // TODO: Channels might be 5 for RGB + A + Mask... String[] bps = new String[mHeader.mChannels]; Arrays.fill(bps, bitDepth); node = new IIOMetadataNode("BitsPerSample"); node.setAttribute("value", StringUtil.toCSVString(bps, " ")); - data_node.appendChild(node); + dataNode.appendChild(node); // TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed? - return data_node; + return dataNode; } @Override protected IIOMetadataNode getStandardDimensionNode() { - IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension"); + IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension"); IIOMetadataNode node; // scratch node node = new IIOMetadataNode("PixelAspectRatio"); - // TODO: This is not incorrect wrt resolution info - float ratio = 1f; - node.setAttribute("value", Float.toString(ratio)); - dimension_node.appendChild(node); + + // TODO: This is not correct wrt resolution info + float aspect = 1f; + + Iterator ratios = getResources(PSDPixelAspectRatio.class); + if (ratios.hasNext()) { + PSDPixelAspectRatio ratio = ratios.next(); + aspect = (float) ratio.mAspect; + } + + node.setAttribute("value", Float.toString(aspect)); + dimensionNode.appendChild(node); node = new IIOMetadataNode("ImageOrientation"); node.setAttribute("value", "Normal"); - dimension_node.appendChild(node); + dimensionNode.appendChild(node); + // TODO: If no PSDResolutionInfo, this might still be available in the EXIF data... Iterator resolutionInfos = getResources(PSDResolutionInfo.class); if (!resolutionInfos.hasNext()) { PSDResolutionInfo resolutionInfo = resolutionInfos.next(); node = new IIOMetadataNode("HorizontalPixelSize"); node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes))); - dimension_node.appendChild(node); + dimensionNode.appendChild(node); node = new IIOMetadataNode("VerticalPixelSize"); node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes))); - dimension_node.appendChild(node); + dimensionNode.appendChild(node); } // TODO: @@ -580,7 +591,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { */ - return dimension_node; + return dimensionNode; } private static float asMM(final short pUnit, final float pResolution) { @@ -603,18 +614,18 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { PSDEXIF1Data data = exif.next(); // Get the EXIF DateTime (aka ModifyDate) tag if present - PSDEXIF1Data.Entry dateTime = data.mDirectory.get(0x0132); // TODO: Constant + Entry dateTime = data.mDirectory.getEntryById(0x0132); // TODO: Constant if (dateTime != null) { - node = new IIOMetadataNode("ImageModificationTime"); - // Format: "YYYY:MM:DD hh:mm:ss" (with quotes! :-P) + node = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime + // Format: "YYYY:MM:DD hh:mm:ss" String value = dateTime.getValueAsString(); - node.setAttribute("year", value.substring(1, 5)); - node.setAttribute("month", value.substring(6, 8)); - node.setAttribute("day", value.substring(9, 11)); - node.setAttribute("hour", value.substring(12, 14)); - node.setAttribute("minute", value.substring(15, 17)); - node.setAttribute("second", value.substring(18, 20)); + node.setAttribute("year", value.substring(0, 4)); + node.setAttribute("month", value.substring(5, 7)); + node.setAttribute("day", value.substring(8, 10)); + node.setAttribute("hour", value.substring(11, 13)); + node.setAttribute("minute", value.substring(14, 16)); + node.setAttribute("second", value.substring(17, 19)); document_node.appendChild(node); } @@ -625,61 +636,68 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { @Override protected IIOMetadataNode getStandardTextNode() { - // TODO: CaptionDigest?, EXIF, XMP + // TODO: TIFF uses + // DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright: + // /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value. + // Example: TIFF Software field => /Text/TextEntry@keyword = "Software", + // /Text/TextEntry@value = Name and version number of the software package(s) used to create the image. - Iterator textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class); + Iterator textResources = getResources(PSDEXIF1Data.class, PSDIPTCData.class, PSDXMPData.class); + + if (!textResources.hasNext()) { + return null; + } + + IIOMetadataNode text = new IIOMetadataNode("Text"); + IIOMetadataNode node; + + // TODO: Alpha channel names? (PSDAlphaChannelInfo/PSDUnicodeAlphaNames) + // TODO: Reader/writer (PSDVersionInfo) while (textResources.hasNext()) { PSDImageResource textResource = textResources.next(); - } - -// int numEntries = tEXt_keyword.size() + -// iTXt_keyword.size() + zTXt_keyword.size(); -// if (numEntries == 0) { -// return null; -// } -// -// IIOMetadataNode text_node = new IIOMetadataNode("Text"); -// IIOMetadataNode node = null; // scratch node -// -// for (int i = 0; i < tEXt_keyword.size(); i++) { -// node = new IIOMetadataNode("TextEntry"); -// node.setAttribute("keyword", (String)tEXt_keyword.get(i)); -// node.setAttribute("value", (String)tEXt_text.get(i)); -// node.setAttribute("encoding", "ISO-8859-1"); -// node.setAttribute("compression", "none"); -// -// text_node.appendChild(node); -// } -// -// for (int i = 0; i < iTXt_keyword.size(); i++) { -// node = new IIOMetadataNode("TextEntry"); -// node.setAttribute("keyword", iTXt_keyword.get(i)); -// node.setAttribute("value", iTXt_text.get(i)); -// node.setAttribute("language", -// iTXt_languageTag.get(i)); -// if (iTXt_compressionFlag.get(i)) { -// node.setAttribute("compression", "deflate"); -// } else { -// node.setAttribute("compression", "none"); -// } -// -// text_node.appendChild(node); -// } -// -// for (int i = 0; i < zTXt_keyword.size(); i++) { -// node = new IIOMetadataNode("TextEntry"); -// node.setAttribute("keyword", (String)zTXt_keyword.get(i)); -// node.setAttribute("value", (String)zTXt_text.get(i)); -// node.setAttribute("compression", "deflate"); -// -// text_node.appendChild(node); -// } -// -// return text_node; - return null; + if (textResource instanceof PSDIPTCData) { + PSDIPTCData iptc = (PSDIPTCData) textResource; + for (Entry entry : iptc.mDirectory) { + node = new IIOMetadataNode("TextEntry"); + + if (entry.getValue() instanceof String) { + node.setAttribute("keyword", String.format("%s", entry.getFieldName())); + node.setAttribute("value", entry.getValueAsString()); + text.appendChild(node); + } + } + } + else if (textResource instanceof PSDEXIF1Data) { + PSDEXIF1Data exif = (PSDEXIF1Data) textResource; + + // TODO: Use name? + appendTextEntriesFlat(text, exif.mDirectory); + } + else if (textResource instanceof PSDXMPData) { + // TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF? + PSDXMPData xmp = (PSDXMPData) textResource; + } + } + + return text; + } + + private void appendTextEntriesFlat(IIOMetadataNode pNode, Directory pDirectory) { + for (Entry entry : pDirectory) { + if (entry.getValue() instanceof Directory) { + appendTextEntriesFlat(pNode, (Directory) entry.getValue()); + } + else if (entry.getValue() instanceof String) { + IIOMetadataNode tag = new IIOMetadataNode("TextEntry"); + // TODO: Use name! + tag.setAttribute("keyword", String.format("%s", entry.getFieldName())); + tag.setAttribute("value", entry.getValueAsString()); + pNode.appendChild(tag); + } + } } @Override @@ -693,7 +711,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { IIOMetadataNode node; // scratch node node = new IIOMetadataNode("Alpha"); - node.setAttribute("value", hasAlpha() ? "nonpremultipled" : "none"); // TODO: Check spec + node.setAttribute("value", hasAlpha() ? "nonpremultiplied" : "none"); // TODO: Check spec transparency_node.appendChild(node); return transparency_node; @@ -731,4 +749,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable { } }); } + + @Override + public Object clone() { + // TODO: Make it a deep clone + try { + return super.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java index 38ac1276..b842ad3b 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -44,7 +44,6 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // columns? addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); - // TODO: Consider using more readable names?! addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES)); /* diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java index 12c927e9..6901a8a3 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUnicodeAlphaNames.java @@ -25,7 +25,7 @@ final class PSDUnicodeAlphaNames extends PSDImageResource { long left = mSize; while (left > 0) { - String name = PSDUtil.readUTF16String(pInput); + String name = PSDUtil.readUnicodeString(pInput); mNames.add(name); left -= name.length() * 2 + 4; } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java index f3e1ab41..9f840406 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java @@ -60,17 +60,27 @@ final class PSDUtil { } // TODO: Proably also useful for PICT reader, move to some common util? - // TODO: Is this REALLY different from the previous method? Maybe the pad should not be read.. static String readPascalString(final DataInput pInput) throws IOException { int length = pInput.readUnsignedByte(); + + if (length == 0) { + return ""; + } + byte[] bytes = new byte[length]; pInput.readFully(bytes); return StringUtil.decode(bytes, 0, bytes.length, "ASCII"); } - static String readUTF16String(final DataInput pInput) throws IOException { + // TODO: Proably also useful for PICT reader, move to some common util? + static String readUnicodeString(final DataInput pInput) throws IOException { int length = pInput.readInt(); + + if (length == 0) { + return ""; + } + byte[] bytes = new byte[length * 2]; pInput.readFully(bytes); diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java index 683fa3a3..5e1e7ea2 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDVersionInfo.java @@ -35,8 +35,8 @@ final class PSDVersionInfo extends PSDImageResource { mVersion = pInput.readInt(); mHasRealMergedData = pInput.readBoolean(); - mWriter = PSDUtil.readUTF16String(pInput); - mReader = PSDUtil.readUTF16String(pInput); + mWriter = PSDUtil.readUnicodeString(pInput); + mReader = PSDUtil.readUnicodeString(pInput); mFileVersion = pInput.readInt(); } From d24c2c1b08b0cf22824ea587a80619d0ee50685d Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sat, 14 Nov 2009 22:49:45 +0100 Subject: [PATCH 15/21] Work in progress for PSD metadata support: - Cleaned up EXIF, IPTC and XMP metadata classes. --- .../imageio/plugins/psd/PSDEXIF1Data.java | 315 +------------- .../imageio/plugins/psd/PSDIPTCData.java | 390 +----------------- .../imageio/plugins/psd/PSDXMPData.java | 2 +- 3 files changed, 6 insertions(+), 701 deletions(-) diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java index 47a30e94..877bc34d 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDEXIF1Data.java @@ -1,15 +1,10 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.exif.EXIFReader; -import com.twelvemonkeys.lang.StringUtil; -import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; /** * EXIF metadata. @@ -23,9 +18,7 @@ import java.util.List; * @see Adobe TIFF developer resources */ final class PSDEXIF1Data extends PSDImageResource { -// protected byte[] mData; -// protected Directory mDirectory; - protected com.twelvemonkeys.imageio.metadata.Directory mDirectory; + protected Directory mDirectory; PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -34,320 +27,16 @@ final class PSDEXIF1Data extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { // This is in essence an embedded TIFF file. - // TODO: Extract TIFF parsing to more general purpose package // TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request) mDirectory = new EXIFReader().read(pInput); -// byte[] bom = new byte[2]; -// stream.readFully(bom); -// if (bom[0] == 'I' && bom[1] == 'I') { -// stream.setByteOrder(ByteOrder.LITTLE_ENDIAN); -// } -// else if (!(bom[0] == 'M' && bom[1] == 'M')) { -// throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); -// } -// -// if (stream.readUnsignedShort() != 42) { -// throw new IIOException("Wrong TIFF magic in EXIF data."); -// } -// -// long directoryOffset = stream.readUnsignedInt(); -// -// // Read TIFF directory -// mDirectory = Directory.read(stream, directoryOffset); } @Override public String toString() { StringBuilder builder = toStringBuilder(); - builder.append(", ").append(mDirectory); - builder.append("]"); return builder.toString(); } - - // TIFF Image file directory (IFD) - static class Directory implements Iterable { - private List mEntries = new ArrayList(); - - private Directory() {} - - public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException { - Directory directory = new Directory(); - - pInput.seek(pOffset); - int entryCount = pInput.readUnsignedShort(); - - for (int i = 0; i < entryCount; i++) { - directory.mEntries.add(Entry.read(pInput)); - } - - long nextOffset = pInput.readUnsignedInt(); - - if (nextOffset != 0) { - Directory next = Directory.read(pInput, nextOffset); - directory.mEntries.addAll(next.mEntries); - } - - return directory; - } - - public Entry get(int pTagId) { - for (Entry entry : mEntries) { - if (entry.mTagId == pTagId) { - return entry; - } - } - - return null; - } - - public Iterator iterator() { - return mEntries.iterator(); - } - - @Override - public String toString() { - return String.format("Directory%s", mEntries); - } - } - - // TIFF IFD Entry - static class Entry { - private static final int EXIF_IFD = 0x8769; - - private final static String[] TYPE_NAMES = { - "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", - - "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", - }; - - private final static int[] TYPE_LENGTHS = { - 1, 1, 2, 4, 8, - - 1, 1, 2, 4, 8, 4, 8, - }; - - final int mTagId; - /* - 1 = BYTE 8-bit unsigned integer. - 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte - must be NUL (binary zero). - 3 = SHORT 16-bit (2-byte) unsigned integer. - 4 = LONG 32-bit (4-byte) unsigned integer. - 5 = RATIONAL Two LONGs: the first represents the numerator of a - fraction; the second, the denominator. - - 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. - 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer. - 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer. - 10 = SRATIONAL Two SLONGs: the first represents the numerator of a - fraction, the second the denominator. - 11 = FLOAT Single precision (4-byte) IEEE format. - 12 = DOUBLE Double precision (8-byte) IEEE format. - */ - private short mType; - private int mCount; - private long mValueOffset; - private Object mValue; - - private Entry(int pTagId) { - mTagId = pTagId; - } - - public static Entry read(final ImageInputStream pInput) throws IOException { - Entry entry = new Entry(pInput.readUnsignedShort()); - - entry.mType = pInput.readShort(); - entry.mCount = pInput.readInt(); // Number of values - - // TODO: Handle other sub-IFDs - // GPS IFD: 0x8825, Interoperability IFD: 0xA005 - if (entry.mTagId == EXIF_IFD) { - long offset = pInput.readUnsignedInt(); - pInput.mark(); - - try { - entry.mValue = Directory.read(pInput, offset); - } - finally { - pInput.reset(); - } - } - else { - int valueLength = entry.getValueLength(); - - if (valueLength > 0 && valueLength <= 4) { - entry.readValueInLine(pInput); - pInput.skipBytes(4 - valueLength); - } - else { - entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes - entry.readValue(pInput); - } - } - - return entry; - } - - private void readValue(final ImageInputStream pInput) throws IOException { - long pos = pInput.getStreamPosition(); - try { - pInput.seek(mValueOffset); - readValueInLine(pInput); - } - finally { - pInput.seek(pos); - } - } - - private void readValueInLine(ImageInputStream pInput) throws IOException { - mValue = readValueDirect(pInput, mType, mCount); - } - - private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { - switch (pType) { - case 2: - // TODO: This might be UTF-8 or ISO-8859-1, even though against the spec - byte[] ascii = new byte[pCount]; - pInput.readFully(ascii); - return StringUtil.decode(ascii, 0, ascii.length, "ASCII"); - case 1: - if (pCount == 1) { - return pInput.readUnsignedByte(); - } - case 6: - if (pCount == 1) { - return pInput.readByte(); - } - case 7: - byte[] bytes = new byte[pCount]; - pInput.readFully(bytes); - return bytes; - case 3: - if (pCount == 1) { - return pInput.readUnsignedShort(); - } - case 8: - if (pCount == 1) { - return pInput.readShort(); - } - - short[] shorts = new short[pCount]; - pInput.readFully(shorts, 0, shorts.length); - return shorts; - case 4: - if (pCount == 1) { - return pInput.readUnsignedInt(); - } - case 9: - if (pCount == 1) { - return pInput.readInt(); - } - - int[] ints = new int[pCount]; - pInput.readFully(ints, 0, ints.length); - return ints; - case 11: - if (pCount == 1) { - return pInput.readFloat(); - } - - float[] floats = new float[pCount]; - pInput.readFully(floats, 0, floats.length); - return floats; - case 12: - if (pCount == 1) { - return pInput.readDouble(); - } - - double[] doubles = new double[pCount]; - pInput.readFully(doubles, 0, doubles.length); - return doubles; - - // TODO: Consider using a Rational class - case 5: - if (pCount == 1) { - return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt(); - } - - double[] rationals = new double[pCount]; - for (int i = 0; i < rationals.length; i++) { - rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt(); - } - - return rationals; - case 10: - if (pCount == 1) { - return pInput.readInt() / (double) pInput.readInt(); - } - - double[] srationals = new double[pCount]; - for (int i = 0; i < srationals.length; i++) { - srationals[i] = pInput.readInt() / (double) pInput.readInt(); - } - - return srationals; - - default: - throw new IIOException(String.format("Unknown EXIF type '%s'", pType)); - } - } - - private int getValueLength() { - if (mType > 0 && mType <= TYPE_LENGTHS.length) { - return TYPE_LENGTHS[mType - 1] * mCount; - } - return -1; - } - - public final String getTypeName() { - if (mType > 0 && mType <= TYPE_NAMES.length) { - return TYPE_NAMES[mType - 1]; - } - - return "Unknown type"; - } - - public final Object getValue() { - return mValue; - } - - public final String getValueAsString() { - if (mValue instanceof String) { - return String.format("%s", mValue); - } - - if (mValue != null && mValue.getClass().isArray()) { - Class type = mValue.getClass().getComponentType(); - if (byte.class == type) { - return Arrays.toString((byte[]) mValue); - } - if (short.class == type) { - return Arrays.toString((short[]) mValue); - } - if (int.class == type) { - return Arrays.toString((int[]) mValue); - } - if (float.class == type) { - return Arrays.toString((float[]) mValue); - } - if (double.class == type) { - return Arrays.toString((double[]) mValue); - } - } - - return String.valueOf(mValue); - } - - // TODO: Tag names! - @Override - public String toString() { - return String.format("0x%04x: %s (%s, %d)", mTagId, mType == 2 ? String.format("\"%s\"", mValue) : getValueAsString(), getTypeName(), mCount); - } - } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java index 04846e07..59a076e9 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDIPTCData.java @@ -1,20 +1,10 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader; -import com.twelvemonkeys.lang.StringUtil; -import javax.imageio.IIOException; import javax.imageio.stream.ImageInputStream; import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CodingErrorAction; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; /** * PSDIPTCData @@ -24,9 +14,7 @@ import java.util.List; * @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$ */ final class PSDIPTCData extends PSDImageResource { - // TODO: Refactor to be more like PSDEXIF1Data... - // TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s) - com.twelvemonkeys.imageio.metadata.Directory mDirectory; + Directory mDirectory; PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -43,379 +31,7 @@ final class PSDIPTCData extends PSDImageResource { StringBuilder builder = toStringBuilder(); builder.append(", ").append(mDirectory); builder.append("]"); + return builder.toString(); } - - static class Entry { - final int mTagId; - private Object mValue; - - public Entry(final int pTagId, final Object pValue) { - mTagId = pTagId; - mValue = pValue; - } - - @Override - public String toString() { - return String.format("%d:%d: %s", mTagId >> 8, mTagId & 0xff, mValue); - } - - public final String getTypeName() { - // TODO: Should this really look like EXIF? - if (mTagId == IPTC.TAG_RECORD_VERSION) { - return "SHORT"; - } - else if (mValue instanceof String) { - return "ASCII"; - } - - return "Unknown type"; - } - - public final String getValueAsString() { - return String.valueOf(mValue); - } - - public final Object getValue() { - return mValue; - } - } - - static class Directory implements Iterable { - private static final int ENCODING_UNKNOWN = -1; - private static final int ENCODING_UNSPECIFIED = 0; - private static final int ENCODING_UTF_8 = 0x1b2547; - - private int mEncoding = ENCODING_UNSPECIFIED; - - final List mEntries = new ArrayList(); - - private Directory() {} - - @Override - public String toString() { - return "Directory" + mEntries.toString(); - } - - public Entry get(int pTagId) { - for (Entry entry : mEntries) { - if (entry.mTagId == pTagId) { - return entry; - } - } - - return null; - } - - public Iterator iterator() { - return mEntries.iterator(); - } - - public static Directory read(final ImageInputStream pInput, final long pSize) throws IOException { - Directory directory = new Directory(); - - final long streamEnd = pInput.getStreamPosition() + pSize; - - // For each tag - while (pInput.getStreamPosition() < streamEnd) { - // Identifies start of a tag - byte marker = pInput.readByte(); - - if (marker != 0x1c) { - throw new IIOException(String.format("Corrupt IPTC stream segment, found 0x%02x (expected 0x1c)", marker)); - } - - int tagId = pInput.readShort(); - int tagByteCount = pInput.readUnsignedShort(); - - directory.readEntry(pInput, tagId, tagByteCount); - } - - return directory; - } - - private void readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException { - Object value = null; - - switch (pTagId) { - case IPTC.TAG_CODED_CHARACTER_SET: - // TODO: Mapping from ISO 646 to Java supported character sets? - // TODO: Move somewhere else? - mEncoding = parseEncoding(pInput, pLength); - return; - case IPTC.TAG_RECORD_VERSION: - // A single unsigned short value - value = pInput.readUnsignedShort(); - break; -// case IPTC.TAG_RELEASE_DATE: -// case IPTC.TAG_EXPIRATION_DATE: -// case IPTC.TAG_REFERENCE_DATE: -// case IPTC.TAG_DATE_CREATED: -// case IPTC.TAG_DIGITAL_CREATION_DATE: -// // Date object -// Date date = parseISO8601DatePart(pInput, tagByteCount); -// if (date != null) { -// directory.setDate(tagIdentifier, date); -// return; -// } -// case IPTC.TAG_RELEASE_TIME: -// case IPTC.TAG_EXPIRATION_TIME: -// case IPTC.TAG_TIME_CREATED: -// case IPTC.TAG_DIGITAL_CREATION_TIME: -// // NOTE: Spec says fields should be sent in order, so this is okay -// date = getDateForTime(directory, tagIdentifier); -// -// Date time = parseISO8601TimePart(pInput, tagByteCount, date); -// if (time != null) { -// directory.setDate(tagIdentifier, time); -// return; -// } -// - default: - // Skip non-Application fields, as they are typically not human readable - if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { - pInput.skipBytes(pLength); - return; - } - - // fall through - } - - // If we don't have a value, treat it as a string - if (value == null) { - if (pLength < 1) { - value = "(No value)"; - } - else { - value = parseString(pInput, pLength); - } - } - - mEntries.add(new Entry(pTagId, value)); - } - -// private Date getDateForTime(final Directory directory, final int tagIdentifier) { -// int dateTag; -// -// switch (tagIdentifier) { -// case IPTC.TAG_RELEASE_TIME: -// dateTag = IPTC.TAG_RELEASE_DATE; -// break; -// case IPTC.TAG_EXPIRATION_TIME: -// dateTag = IPTC.TAG_EXPIRATION_DATE; -// break; -// case IPTC.TAG_TIME_CREATED: -// dateTag = IPTC.TAG_DATE_CREATED; -// break; -// case IPTC.TAG_DIGITAL_CREATION_TIME: -// dateTag = IPTC.TAG_DIGITAL_CREATION_DATE; -// break; -// default: -// return new Date(0l); -// } -// -// return directory.containsTag(dateTag) ? directory.getDate(dateTag) : new Date(0l); -// } - - - private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException { - return tagByteCount == 3 - && (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8 - ? ENCODING_UTF_8 : ENCODING_UNKNOWN; - } - -// private Date parseISO8601TimePart(final ImageInputStream pInputStream, int tagByteCount, final Date date) throws IOException { -// // ISO 8601: HHMMSS±HHMM -// if (tagByteCount >= 11) { -// String timeStr = parseString(pInputStream, tagByteCount); -// try { -// int hour = Integer.parseInt(timeStr.substring(0, 2)); -// int minute = Integer.parseInt(timeStr.substring(2, 4)); -// int second = Integer.parseInt(timeStr.substring(4, 6)); -// String tzOffset = timeStr.substring(6, 11); -// -// TimeZone zone = new SimpleTimeZone(Integer.parseInt(tzOffset.charAt(0) == '+' ? tzOffset.substring(1) : tzOffset), tzOffset); -// -// GregorianCalendar calendar = new GregorianCalendar(zone); -// calendar.setTime(date); -// -// calendar.add(Calendar.HOUR_OF_DAY, hour); -// calendar.add(Calendar.MINUTE, minute); -// calendar.add(Calendar.SECOND, second); -// -// return calendar.getTime(); -// } -// catch (NumberFormatException e) { -// // fall through and we'll store whatever was there as a String -// } -// } -// return null; -// } -// -// private Date parseISO8601DatePart(final ImageInputStream pInputStream, int tagByteCount) throws IOException { -// // ISO 8601: CCYYMMDD -// if (tagByteCount >= 8) { -// String dateStr = parseString(pInputStream, tagByteCount); -// try { -// int year = Integer.parseInt(dateStr.substring(0, 4)); -// int month = Integer.parseInt(dateStr.substring(4, 6)) - 1; -// int day = Integer.parseInt(dateStr.substring(6, 8)); -// GregorianCalendar calendar = new GregorianCalendar(year, month, day); -// return calendar.getTime(); -// } -// catch (NumberFormatException e) { -// // fall through and we'll store whatever was there as a String -// } -// } -// return null; -// } - - // TODO: Pass encoding as parameter? Use if specified - private String parseString(final ImageInputStream pInput, final int pLength) throws IOException { - byte[] data = new byte[pLength]; - pInput.readFully(data); - - // NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding. - // UTF-8 contains all 646 characters, but not 2022. - // This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html - Charset charset = Charset.forName("UTF-8"); - CharsetDecoder decoder = charset.newDecoder(); - - try { - // First try to decode using UTF-8 (which seems to be the de-facto standard) - // Will fail fast on illegal UTF-8-sequences - CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT) - .onUnmappableCharacter(CodingErrorAction.REPORT) - .decode(ByteBuffer.wrap(data)); - return chars.toString(); - } - catch (CharacterCodingException notUTF8) { - if (mEncoding == ENCODING_UTF_8) { - throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8); - } - - // Fall back to use ISO-8859-1 - // This will not fail, but may may create wrong fallback-characters - return StringUtil.decode(data, 0, data.length, "ISO8859_1"); - } - } - } - - static interface IPTC { - static final int ENVELOPE_RECORD = 1 << 8; - static final int APPLICATION_RECORD = 2 << 8; - - static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90; - - /** 2:00 Record Version (mandatory) */ - public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200 -// /** 2:03 Object Type Reference */ -// public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3; -// /** 2:04 Object Attribute Reference (repeatable) */ -// public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4; -// /** 2:05 Object Name */ -// public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205 -// /** 2:07 Edit Status */ -// public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7; -// /** 2:08 Editorial Update */ -// public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8; -// /** 2:10 Urgency */ -// public static final int TAG_URGENCY = APPLICATION_RECORD | 10; -// /** 2:12 Subect Reference (repeatable) */ -// public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12; -// /** 2:15 Category */ -// public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f -// /** 2:20 Supplemental Category (repeatable) */ -// public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20; -// /** 2:22 Fixture Identifier */ -// public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22; -// /** 2:25 Keywords (repeatable) */ -// public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25; -// /** 2:26 Content Locataion Code (repeatable) */ -// public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26; -// /** 2:27 Content Locataion Name (repeatable) */ -// public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27; -// /** 2:30 Release Date */ -// public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30; -// /** 2:35 Release Time */ -// public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35; -// /** 2:37 Expiration Date */ -// public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37; -// /** 2:38 Expiration Time */ -// public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38; -// /** 2:40 Special Instructions */ -// public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228 -// /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */ -// public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42; -// /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */ -// public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45; -// /** 2:47 Reference Date (mandatory if 2:45 present) */ -// public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47; -// /** 2:50 Reference Number (mandatory if 2:45 present) */ -// public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50; -// /** 2:55 Date Created */ -// public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237 -// /** 2:60 Time Created */ -// public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60; -// /** 2:62 Digital Creation Date */ -// public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62; -// /** 2:63 Digital Creation Date */ -// public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63; -// /** 2:65 Originating Program */ -// public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65; -// /** 2:70 Program Version (only valid if 2:65 present) */ -// public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70; -// /** 2:75 Object Cycle (a: morning, p: evening, b: both) */ -// public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75; -// /** 2:80 By-line (repeatable) */ -// public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250 -// /** 2:85 By-line Title (repeatable) */ -// public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255 -// /** 2:90 City */ -// public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a -// /** 2:92 Sub-location */ -// public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92; -// /** 2:95 Province/State */ -// public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f -// /** 2:100 Country/Primary Location Code */ -// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100; -// /** 2:101 Country/Primary Location Name */ -// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265 -// /** 2:103 Original Transmission Reference */ -// public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267 -// /** 2:105 Headline */ -// public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269 -// /** 2:110 Credit */ -// public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e -// /** 2:115 Source */ -// public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273 -// /** 2:116 Copyright Notice */ -// public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274 -// /** 2:118 Contact */ -// public static final int TAG_CONTACT = APPLICATION_RECORD | 118; -// /** 2:120 Catption/Abstract */ -// public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278 -// /** 2:122 Writer/Editor (repeatable) */ -// public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a -// /** 2:125 Rasterized Caption (binary data) */ -// public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125; -// /** 2:130 Image Type */ -// public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130; -// /** 2:131 Image Orientation */ -// public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131; -// /** 2:135 Language Identifier */ -// public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135; -// -// // TODO: Should we expose this field? -// /** -// * 2:199 JobMinder Assignment Data (Custom IPTC field). -// * A common custom IPTC field used by a now discontinued application called JobMinder. -// * -// * @see JobMinder Homepage -// */ -// static final int CUSTOM_TAG_JOBMINDER_ASSIGMENT_DATA = APPLICATION_RECORD | 199; -// -// // TODO: 2:150-2:154 Audio and 2:200-2:202 Object Preview Data - - } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java index c83dc49b..7ba689e1 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java @@ -47,7 +47,7 @@ final class PSDXMPData extends PSDImageResource { builder.append("\"]"); return builder.toString(); - } + } /** * Returns a character stream containing the XMP metadata (XML). From 64b21b83bb52888d6824463e4193fecca1b596a5 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Wed, 18 Nov 2009 00:43:18 +0100 Subject: [PATCH 16/21] Work in progress for PSD metadata support: - Implemented XMP Reader, Directory and Entry - More EXIF and IPTC changes - Cleaning up --- .../imageio/metadata/AbstractDirectory.java | 6 +- .../imageio/metadata/AbstractEntry.java | 2 +- .../imageio/metadata/Directory.java | 2 +- .../imageio/metadata/exif/EXIF.java | 41 +--- .../imageio/metadata/exif/EXIFDirectory.java | 1 - .../imageio/metadata/exif/EXIFEntry.java | 31 ++- .../imageio/metadata/exif/EXIFReader.java | 15 +- .../imageio/metadata/exif/TIFF.java | 53 +++++ .../imageio/metadata/iptc/IPTCEntry.java | 13 +- .../imageio/metadata/iptc/IPTCReader.java | 34 +-- .../imageio/metadata/xmp/XMP.java | 36 +++ .../imageio/metadata/xmp/XMPDirectory.java | 23 ++ .../imageio/metadata/xmp/XMPEntry.java | 29 +++ .../metadata/xmp/XMPNamespaceMapping.java | 23 ++ .../imageio/metadata/xmp/XMPReader.java | 195 ++++++++++++++++ .../imageio/metadata/xmp/XMPScanner.java | 215 ++++++++++++++++++ .../imageio/plugins/psd/AbstractMetadata.java | 1 + .../imageio/plugins/psd/PSDMetadata.java | 140 ++++++------ .../imageio/plugins/psd/PSDXMPData.java | 7 +- 19 files changed, 709 insertions(+), 158 deletions(-) create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java index dbad3f8c..4837a330 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java @@ -13,7 +13,6 @@ import java.util.List; * @version $Id: AbstractDirectory.java,v 1.0 Nov 11, 2009 5:31:04 PM haraldk Exp$ */ public abstract class AbstractDirectory implements Directory { - // A linked hashmap or a stable bag structure might also work.. private final List mEntries = new ArrayList(); protected AbstractDirectory(final Collection pEntries) { @@ -32,9 +31,9 @@ public abstract class AbstractDirectory implements Directory { return null; } - public Entry getEntryByName(final String pName) { + public Entry getEntryByFieldName(final String pFieldName) { for (Entry entry : this) { - if (entry.getFieldName().equals(pName)) { + if (entry.getFieldName() != null && entry.getFieldName().equals(pFieldName)) { return entry; } } @@ -66,6 +65,7 @@ public abstract class AbstractDirectory implements Directory { return mEntries.add(pEntry); } + @SuppressWarnings({"SuspiciousMethodCalls"}) public boolean remove(final Object pEntry) { assertMutable(); diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java index e03a917d..2ef60c07 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java @@ -23,7 +23,7 @@ public abstract class AbstractEntry implements Entry { mValue = pValue; } - public Object getIdentifier() { + public final Object getIdentifier() { return mIdentifier; } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java index 66fa5011..07c5fb0e 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java @@ -12,7 +12,7 @@ public interface Directory extends Iterable { // For multiple entries with same id in directory, the first entry (using the order from the stream) will be returned Entry getEntryById(Object pIdentifier); - Entry getEntryByName(String pName); + Entry getEntryByFieldName(String pName); // Iterator containing the entries in //Iterator getBestEntries(Object pIdentifier, Object pQualifier, String pLanguage); diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java index 4fc8c22f..5ff05628 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java @@ -7,41 +7,8 @@ package com.twelvemonkeys.imageio.metadata.exif; * @author last modified by $Author: haraldk$ * @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$ */ -interface EXIF { - /* - 1 = BYTE 8-bit unsigned integer. - 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte - must be NUL (binary zero). - 3 = SHORT 16-bit (2-byte) unsigned integer. - 4 = LONG 32-bit (4-byte) unsigned integer. - 5 = RATIONAL Two LONGs: the first represents the numerator of a - fraction; the second, the denominator. - - 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. - 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer. - 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer. - 10 = SRATIONAL Two SLONGs: the first represents the numerator of a - fraction, the second the denominator. - 11 = FLOAT Single precision (4-byte) IEEE format. - 12 = DOUBLE Double precision (8-byte) IEEE format. - */ - - static int EXIF_IFD = 0x8769; - - static String[] TYPE_NAMES = { - "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", - - "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", - }; - - static int[] TYPE_LENGTHS = { - 1, 1, 2, 4, 8, - - 1, 1, 2, 4, 8, 4, 8, - }; - - int TIFF_MAGIC = 42; +public interface EXIF { + int TAG_COLOR_SPACE = 40961; + int TAG_PIXEL_X_DIMENSION = 40962; + int TAG_PIXEL_Y_DIMENSION = 40963; } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java index 98d426de..77bb8e64 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java @@ -4,7 +4,6 @@ import com.twelvemonkeys.imageio.metadata.AbstractDirectory; import com.twelvemonkeys.imageio.metadata.Entry; import java.util.Collection; -import java.util.List; /** * EXIFDirectory diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index 7ed7cbb5..da2e4c6e 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -12,19 +12,42 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry; final class EXIFEntry extends AbstractEntry { final private short mType; - EXIFEntry(final Object pIdentifier, final Object pValue, final short pType) { + 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)); + } + mType = pType; } @Override public String getFieldName() { - // TODO: Need tons of constants... ;-) - return super.getFieldName(); + switch ((Integer) getIdentifier()) { + case TIFF.TAG_SOFTWARE: + return "Software"; + case TIFF.TAG_DATE_TIME: + return "DateTime"; + case TIFF.TAG_ARTIST: + return "Artist"; + case TIFF.TAG_COPYRIGHT: + return "Copyright"; + + case EXIF.TAG_COLOR_SPACE: + return "ColorSpace"; + case EXIF.TAG_PIXEL_X_DIMENSION: + return "PixelXDimension"; + case EXIF.TAG_PIXEL_Y_DIMENSION: + return "PixelYDimension"; + // TODO: More field names + } + + return null; } @Override public String getTypeName() { - return EXIF.TYPE_NAMES[mType]; + return TIFF.TYPE_NAMES[mType - 1]; } } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java index d5a406ca..f4dd56f1 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java @@ -29,12 +29,12 @@ public final class EXIFReader extends MetadataReader { pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); } else if (!(bom[0] == 'M' && bom[1] == 'M')) { - throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); + throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); } int magic = pInput.readUnsignedShort(); - if (magic != EXIF.TIFF_MAGIC) { - throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, EXIF.TIFF_MAGIC)); + if (magic != TIFF.TIFF_MAGIC) { + throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC)); } long directoryOffset = pInput.readUnsignedInt(); @@ -73,9 +73,8 @@ public final class EXIFReader extends MetadataReader { Object value; - // TODO: Handle other sub-IFDs - // GPS IFD: 0x8825, Interoperability IFD: 0xA005 - if (tagId == EXIF.EXIF_IFD) { + if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) { + // Parse sub IFDs long offset = pInput.readUnsignedInt(); pInput.mark(); @@ -207,8 +206,8 @@ public final class EXIFReader extends MetadataReader { } private int getValueLength(final int pType, final int pCount) { - if (pType > 0 && pType <= EXIF.TYPE_LENGTHS.length) { - return EXIF.TYPE_LENGTHS[pType - 1] * pCount; + if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) { + return TIFF.TYPE_LENGTHS[pType - 1] * pCount; } return -1; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java new file mode 100644 index 00000000..b47ae01b --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -0,0 +1,53 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +/** + * TIFF + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$ + */ +public interface TIFF { + int TIFF_MAGIC = 42; + + /* + 1 = BYTE 8-bit unsigned integer. + 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte + must be NUL (binary zero). + 3 = SHORT 16-bit (2-byte) unsigned integer. + 4 = LONG 32-bit (4-byte) unsigned integer. + 5 = RATIONAL Two LONGs: the first represents the numerator of a + fraction; the second, the denominator. + + 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. + 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer. + 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer. + 10 = SRATIONAL Two SLONGs: the first represents the numerator of a + fraction, the second the denominator. + 11 = FLOAT Single precision (4-byte) IEEE format. + 12 = DOUBLE Double precision (8-byte) IEEE format. + */ + String[] TYPE_NAMES = { + "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", + + "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", + }; + int[] TYPE_LENGTHS = { + 1, 1, 2, 4, 8, + + 1, 1, 2, 4, 8, 4, 8, + }; + + int IFD_EXIF = 0x8769; + int IFD_GPS = 0x8825; + int IFD_INTEROP = 0xA005; + + + int TAG_SOFTWARE = 305; + int TAG_DATE_TIME = 306; + int TAG_ARTIST = 315; + int TAG_COPYRIGHT = 33432; +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java index 8773fc48..9c046911 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java @@ -10,7 +10,18 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry; * @version $Id: IPTCEntry.java,v 1.0 Nov 13, 2009 8:57:04 PM haraldk Exp$ */ class IPTCEntry extends AbstractEntry { - public IPTCEntry(int pTagId, Object pValue) { + public IPTCEntry(final int pTagId, final Object pValue) { super(pTagId, pValue); } + + @Override + public String getFieldName() { + switch ((Integer) getIdentifier()) { + case IPTC.TAG_SOURCE: + return "Source"; + // TODO: More tags... + } + + return null; + } } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java index 863f039b..eea40917 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java @@ -24,7 +24,7 @@ import java.util.List; * @author last modified by $Author: haraldk$ * @version $Id: IPTCReader.java,v 1.0 Nov 13, 2009 8:37:23 PM haraldk Exp$ */ -public class IPTCReader extends MetadataReader { +public final class IPTCReader extends MetadataReader { private static final int ENCODING_UNKNOWN = -1; private static final int ENCODING_UNSPECIFIED = 0; private static final int ENCODING_UTF_8 = 0x1b2547; @@ -38,10 +38,10 @@ public class IPTCReader extends MetadataReader { // 0x1c identifies start of a tag while (pInput.read() == 0x1c) { - int tagId = pInput.readShort(); + short tagId = pInput.readShort(); int tagByteCount = pInput.readUnsignedShort(); - Entry entry = readEntry(pInput, tagId, tagByteCount); + if (entry != null) { entries.add(entry); } @@ -50,7 +50,7 @@ public class IPTCReader extends MetadataReader { return new IPTCDirectory(entries); } - private Entry readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException { + private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException { Object value = null; switch (pTagId) { @@ -63,30 +63,6 @@ public class IPTCReader extends MetadataReader { // A single unsigned short value value = pInput.readUnsignedShort(); break; -// case IPTC.TAG_RELEASE_DATE: -// case IPTC.TAG_EXPIRATION_DATE: -// case IPTC.TAG_REFERENCE_DATE: -// case IPTC.TAG_DATE_CREATED: -// case IPTC.TAG_DIGITAL_CREATION_DATE: -// // Date object -// Date date = parseISO8601DatePart(pInput, tagByteCount); -// if (date != null) { -// directory.setDate(tagIdentifier, date); -// return; -// } -// case IPTC.TAG_RELEASE_TIME: -// case IPTC.TAG_EXPIRATION_TIME: -// case IPTC.TAG_TIME_CREATED: -// case IPTC.TAG_DIGITAL_CREATION_TIME: -// // NOTE: Spec says fields should be sent in order, so this is okay -// date = getDateForTime(directory, tagIdentifier); -// -// Date time = parseISO8601TimePart(pInput, tagByteCount, date); -// if (time != null) { -// directory.setDate(tagIdentifier, time); -// return; -// } -// default: // Skip non-Application fields, as they are typically not human readable if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { @@ -100,7 +76,7 @@ public class IPTCReader extends MetadataReader { // If we don't have a value, treat it as a string if (value == null) { if (pLength < 1) { - value = "(No value)"; + value = null; } else { value = parseString(pInput, pLength); diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java new file mode 100644 index 00000000..b67d24ab --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java @@ -0,0 +1,36 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import java.util.Collections; +import java.util.Map; + +/** + * XMP + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMP.java,v 1.0 Nov 12, 2009 12:19:32 AM haraldk Exp$ + * + * @see Extensible Metadata Platform (XMP) + */ +public interface XMP { + /** W3C Resource Description Format namespace */ + String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + /** Dublin Core Metadata Initiative namespace */ + String NS_DC = "http://purl.org/dc/elements/1.1/"; + + String NS_EXIF = "http://ns.adobe.com/exif/1.0/"; + + String NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/"; + + String NS_ST_REF = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"; + + String NS_TIFF = "http://ns.adobe.com/tiff/1.0/"; + + String NS_XAP = "http://ns.adobe.com/xap/1.0/"; + + String NS_XAP_MM = "http://ns.adobe.com/xap/1.0/mm/"; + + /** Contains the mapping from URI to default namespace prefix. */ + Map DEFAULT_NS_MAPPING = Collections.unmodifiableMap(new XMPNamespaceMapping()); +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java new file mode 100644 index 00000000..b79ad8d1 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java @@ -0,0 +1,23 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.metadata.AbstractDirectory; +import com.twelvemonkeys.imageio.metadata.Entry; + +import java.util.List; + +/** +* XMPDirectory +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: XMPDirectory.java,v 1.0 Nov 17, 2009 9:38:58 PM haraldk Exp$ +*/ +final class XMPDirectory extends AbstractDirectory { + // TODO: Store size of root directory, to allow serializing + // TODO: XMPDirectory, maybe not even an AbstractDirectory + // - Keeping the Document would allow for easier serialization + // TODO: Or use direct SAX parsing + public XMPDirectory(List pEntries) { + super(pEntries); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java new file mode 100644 index 00000000..4ef515ff --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java @@ -0,0 +1,29 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.metadata.AbstractEntry; + +/** +* XMPEntry +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: XMPEntry.java,v 1.0 Nov 17, 2009 9:38:39 PM haraldk Exp$ +*/ +final class XMPEntry extends AbstractEntry { + private final String mFieldName; + + public XMPEntry(final String pIdentifier, final Object pValue) { + this(pIdentifier, null, pValue); + } + + public XMPEntry(final String pIdentifier, final String pFieldName, final Object pValue) { + super(pIdentifier, pValue); + mFieldName = pFieldName; + } + + @SuppressWarnings({"SuspiciousMethodCalls"}) + @Override + public String getFieldName() { + return mFieldName != null ? mFieldName : XMP.DEFAULT_NS_MAPPING.get(getIdentifier()); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java new file mode 100644 index 00000000..bc0077be --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java @@ -0,0 +1,23 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import java.util.HashMap; + +/** + * XMPNamespaceMapping + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPNamespaceMapping.java,v 1.0 Nov 17, 2009 6:35:21 PM haraldk Exp$ + */ +final class XMPNamespaceMapping extends HashMap { + public XMPNamespaceMapping() { + put(XMP.NS_RDF, "rdf"); + put(XMP.NS_DC, "dc"); + put(XMP.NS_EXIF, "exif"); + put(XMP.NS_PHOTOSHOP, "photoshop"); + put(XMP.NS_ST_REF, "stRef"); + put(XMP.NS_TIFF, "tiff"); + put(XMP.NS_XAP, "xap"); + put(XMP.NS_XAP_MM, "xapMM"); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java new file mode 100644 index 00000000..fff9de38 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java @@ -0,0 +1,195 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.metadata.*; +import com.twelvemonkeys.imageio.util.IIOUtil; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.*; + +/** + * XMPReader + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPReader.java,v 1.0 Nov 14, 2009 11:04:30 PM haraldk Exp$ + */ +public final class XMPReader extends MetadataReader { + @Override + public Directory read(final ImageInputStream pInput) throws IOException { + pInput.mark(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(IIOUtil.createStreamAdapter(pInput), Charset.forName("UTF-8"))); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + + pInput.reset(); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + + try { + // TODO: Consider parsing using SAX? + // TODO: Determine encoding and parse using a Reader... + // TODO: Refactor scanner to return inputstream? + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(pInput))); + +// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding")); +// serializer.serialize(document); + + + // Each rdf:Description is a Directory (but we can't really rely on that structure.. it's only convention) + // - Each element inside the rdf:Desc is an Entry + + Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0); + NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description"); + + return parseDirectories(rdfRoot, descriptions); + } + catch (SAXException e) { + throw new IIOException(e.getMessage(), e); + } + catch (ParserConfigurationException e) { + throw new RuntimeException(e); // TODO: Or IOException? + } + } + + // TODO: Consider using namespace-prefix in tags/identifiers and qName as field only!? + + private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes) { + Map> subdirs = new LinkedHashMap>(); + + for (Node desc : asIterable(pNodes)) { + if (desc.getParentNode() != pParentNode) { + continue; + } + + for (Node node : asIterable(desc.getChildNodes())) { + if (node.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + // Lookup + List dir = subdirs.get(node.getNamespaceURI()); + if (dir == null) { + dir = new ArrayList(); + subdirs.put(node.getNamespaceURI(), dir); + } + + Object value; + + Node parseType = node.getAttributes().getNamedItemNS(XMP.NS_RDF, "parseType"); + if (parseType != null && "Resource".equals(parseType.getNodeValue())) { + List entries = new ArrayList(); + + for (Node child : asIterable(node.getChildNodes())) { + if (child.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + // TODO: Preserve the stRef namespace here.. + entries.add(new XMPEntry(child.getNamespaceURI() + child.getLocalName(), child.getLocalName(), getChildTextValue(child))); + } + value = new XMPDirectory(entries); + } + else { + // TODO: Support alternative RDF syntax (short-form), using attributes on desc +// NamedNodeMap attributes = node.getAttributes(); +// +// for (Node attr : asIterable(attributes)) { +// System.out.println("attr.getNodeName(): " + attr.getNodeName()); +// System.out.println("attr.getNodeValue(): " + attr.getNodeValue()); +// } + + value = getChildTextValue(node); + } + + // TODO: Preserve namespace (without URI?) here.. + XMPEntry entry = new XMPEntry(node.getNamespaceURI() + node.getLocalName(), node.getLocalName(), value); + dir.add(entry); + } + } + + List entries = new ArrayList(); + + for (Map.Entry> entry : subdirs.entrySet()) { + entries.add(new XMPEntry(entry.getKey(), new XMPDirectory(entry.getValue()))); + } + + return new XMPDirectory(entries); + } + + private Object getChildTextValue(Node node) { + Object value; + Node child = node.getFirstChild(); + + String strVal = null; + if (child != null) { + strVal = child.getNodeValue(); + } + + value = strVal != null ? strVal.trim() : ""; + return value; + } + + private Iterable asIterable(final NamedNodeMap pNodeList) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + private int mIndex; + + public boolean hasNext() { + return pNodeList != null && pNodeList.getLength() > mIndex; + } + + public Node next() { + return pNodeList.item(mIndex++); + } + + public void remove() { + throw new UnsupportedOperationException("Method remove not supported"); + } + }; + } + }; + } + + private Iterable asIterable(final NodeList pNodeList) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + private int mIndex; + + public boolean hasNext() { + return pNodeList != null && pNodeList.getLength() > mIndex; + } + + public Node next() { + return pNodeList.item(mIndex++); + } + + public void remove() { + throw new UnsupportedOperationException("Method remove not supported"); + } + }; + } + }; + } + +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java new file mode 100644 index 00000000..8e9f6a7c --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java @@ -0,0 +1,215 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; +import com.twelvemonkeys.imageio.util.IIOUtil; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import java.io.*; +import java.nio.charset.Charset; + +/** + * XMPScanner + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPScanner.java,v 1.0 Nov 11, 2009 4:49:00 PM haraldk Exp$ + */ +public final class XMPScanner { + /** + * {@code <?xpacket begin=} + *

+ *

    + *
  • + * 8-bit (UTF-8): + * 0x3C 0x3F 0x78 0x70 0x61 0x63 0x6B 0x65 0x74 0x20 + * 0x62 0x65 0x67 0x69 0x6E 0x3D + *
  • + *
  • 16-bit encoding (UCS-2, UTF-16): (either big- or little-endian order) + * 0x3C 0x00 0x3F 0x00 0x78 0x00 0x70 0x00 0x61 0x00 + * 0x63 0x00 0x6B 0x00 0x65 0x00 0x74 0x00 0x20 0x00 0x62 0x00 + * 0x65 0x00 0x67 0x00 0x69 0x00 0x6E 0x00 0x3D [0x00] + *
  • + *
  • 32-bit encoding (UCS-4): + * As 16 bit UCS2, with three 0x00 instead of one.
  • + *
+ */ + private static final byte[] XMP_PACKET_BEGIN = { + 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, + 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D + }; + + /** + * {@code <?xpacket end=} + */ + private static final byte[] XMP_PACKET_END = { + 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, + 0x65, 0x6E, 0x64, 0x3D + }; + + /** + * Scans the given input for an XML metadata packet. + * The scanning process involves reading every byte in the file, while searching for an XMP packet. + * This process is very inefficient, compared to reading a known file format. + *

+ * NOTE: The XMP Specification says this method of reading an XMP packet + * should be considered a last resort.
+ * This is because files may contain multiple XMP packets, some which may be related to embedded resources, + * some which may be obsolete (or even incomplete). + * + * @param pInput the input to scan. The input may be an {@link javax.imageio.stream.ImageInputStream} or + * any object that can be passed to {@link ImageIO#createImageInputStream(Object)}. + * Typically this may be a {@link File}, {@link InputStream} or {@link java.io.RandomAccessFile}. + * + * @return a character Reader + * + * @throws java.nio.charset.UnsupportedCharsetException if the encoding specified within the BOM is not supported + * by the JRE. + * @throws IOException if an I/O exception occurs reading from {@code pInput}. + * @see ImageIO#createImageInputStream(Object) + */ + static public Reader scanForXMPPacket(final Object pInput) throws IOException { + ImageInputStream stream = pInput instanceof ImageInputStream ? (ImageInputStream) pInput : ImageIO.createImageInputStream(pInput); + + // TODO: Consider if BufferedIIS is a good idea + if (!(stream instanceof BufferedImageInputStream)) { + stream = new BufferedImageInputStream(stream); + } + + // TODO: Might be more than one XMP block per file (it's possible to re-start for now).. + long pos; + pos = scanForSequence(stream, XMP_PACKET_BEGIN); + + if (pos >= 0) { + // Skip ' OR " (plus possible nulls for 16/32 bit) + byte quote = stream.readByte(); + + if (quote == '\'' || quote == '"') { + Charset cs = null; + + // Read BOM + byte[] bom = new byte[4]; + stream.readFully(bom); + + // NOTE: Empty string should be treated as UTF-8 for backwards compatibility + if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF && bom[3] == quote || + bom[0] == quote) { + // UTF-8 + cs = Charset.forName("UTF-8"); + } + else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF && bom[2] == 0x00 && bom[3] == quote) { + // UTF-16 BIG endian + cs = Charset.forName("UTF-16BE"); + } + else if (bom[0] == 0x00 && bom[1] == (byte) 0xFF && bom[2] == (byte) 0xFE && bom[3] == quote) { + stream.skipBytes(1); // Alignment + + // UTF-16 little endian + cs = Charset.forName("UTF-16LE"); + } + else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF) { + // NOTE: 32-bit character set not supported by default + // UTF 32 BIG endian + cs = Charset.forName("UTF-32BE"); + } + else if (bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { + // TODO: FixMe.. + // NOTE: 32-bit character set not supported by default + // UTF 32 little endian + cs = Charset.forName("UTF-32LE"); + } + + if (cs != null) { + // Read all bytes until + while (reader.read() != '>') { + } + + // Return reader? + // How to decide between w or r?! + return reader; + } + } + } + + return null; + } + + /** + * Scans for a given ASCII sequence. + * + * @param pStream the stream to scan + * @param pSequence the byte sequence to search for + * + * @return the start position of the given sequence. + * + * @throws IOException if an I/O exception occurs during scanning + */ + private static long scanForSequence(final ImageInputStream pStream, final byte[] pSequence) throws IOException { + long start = -1l; + + int index = 0; + int nullBytes = 0; + + for (int read; (read = pStream.read()) >= 0;) { + if (pSequence[index] == (byte) read) { + // If this is the first byte in the sequence, store position + if (start == -1) { + start = pStream.getStreamPosition() - 1; + } + + // Inside the sequence, there might be 1 or 3 null bytes, depending on 16/32 byte encoding + if (nullBytes == 1 || nullBytes == 3) { + pStream.skipBytes(nullBytes); + } + + index++; + + // If we found the entire sequence, we're done, return start position + if (index == pSequence.length) { + return start; + } + } + else if (index == 1 && read == 0 && nullBytes < 3) { + // Skip 1 or 3 null bytes for 16/32 bit encoding + nullBytes++; + } + else if (index != 0) { + // Start over + index = 0; + start = -1; + nullBytes = 0; + } + } + + return -1l; + } + + //static public XMPDirectory parse(input); + + public static void main(final String[] pArgs) throws IOException { + ImageInputStream stream = ImageIO.createImageInputStream(new File(pArgs[0])); + + Reader xmp; + while ((xmp = scanForXMPPacket(stream)) != null) { + BufferedReader reader = new BufferedReader(xmp); + String line; + + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + + stream.close(); +// else { +// System.err.println("XMP not found"); +// } + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java index 5ba2d84a..4859fa96 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java @@ -15,6 +15,7 @@ import java.util.Arrays; * @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$ */ abstract class AbstractMetadata extends IIOMetadata implements Cloneable { + // TODO: Move to core... protected AbstractMetadata(final boolean pStandardFormatSupported, final String pNativeFormatName, final String pNativeFormatClassName, diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 276d7e07..d18c80ea 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -2,15 +2,13 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.exif.TIFF; +import com.twelvemonkeys.imageio.metadata.iptc.IPTC; import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.util.FilterIterator; -import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.xml.sax.InputSource; import javax.imageio.metadata.IIOMetadataNode; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import java.awt.image.IndexColorModel; import java.util.Arrays; import java.util.Iterator; @@ -251,72 +249,36 @@ public final class PSDMetadata extends AbstractMetadata { } else if (imageResource instanceof PSDIPTCData) { // TODO: Revise/rethink this... - // Transcode to XMP? ;-) PSDIPTCData iptc = (PSDIPTCData) imageResource; - node = new IIOMetadataNode("Directory"); + node = new IIOMetadataNode("DirectoryResource"); node.setAttribute("type", "IPTC"); node.setUserObject(iptc.mDirectory); - for (Entry entry : iptc.mDirectory) { - IIOMetadataNode tag = new IIOMetadataNode("Entry"); - tag.setAttribute("tag", String.format("%d:%02d", (Integer) entry.getIdentifier() >> 8, (Integer) entry.getIdentifier() & 0xff)); - - String field = entry.getFieldName(); - if (field != null) { - tag.setAttribute("field", String.format("%s", field)); - } - tag.setAttribute("value", entry.getValueAsString()); - - String type = entry.getTypeName(); - if (type != null) { - tag.setAttribute("type", type); - } - node.appendChild(tag); - } + appendEntries(node, "IPTC", iptc.mDirectory); } else if (imageResource instanceof PSDEXIF1Data) { // TODO: Revise/rethink this... - // Transcode to XMP? ;-) PSDEXIF1Data exif = (PSDEXIF1Data) imageResource; - node = new IIOMetadataNode("Directory"); + node = new IIOMetadataNode("DirectoryResource"); node.setAttribute("type", "EXIF"); // TODO: Set byte[] data instead node.setUserObject(exif.mDirectory); - appendEntries(node, exif.mDirectory); + appendEntries(node, "EXIF", exif.mDirectory); } else if (imageResource instanceof PSDXMPData) { // TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid... // Or maybe use the Directory approach used by IPTC and EXIF.. PSDXMPData xmp = (PSDXMPData) imageResource; - node = new IIOMetadataNode("XMP"); + node = new IIOMetadataNode("DirectoryResource"); + node.setAttribute("type", "XMP"); + appendEntries(node, "XMP", xmp.mDirectory); - try { -// BufferedReader reader = new BufferedReader(xmp.getData()); -// String line; -// while ((line = reader.readLine()) != null) { -// System.out.println(line); -// } -// - DocumentBuilder builder; - Document document; - - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - builder = factory.newDocumentBuilder(); - document = builder.parse(new InputSource(xmp.getData())); - - - // Set the entire XMP document as user data - node.setUserObject(document); -// node.appendChild(document.getFirstChild()); - } - catch (Exception e) { - e.printStackTrace(); - } + // Set the entire XMP document as user data + node.setUserObject(xmp.mData); } else { // Generic resource.. @@ -342,18 +304,25 @@ public final class PSDMetadata extends AbstractMetadata { return resource; } - private void appendEntries(IIOMetadataNode pNode, final Directory pDirectory) { + private void appendEntries(final IIOMetadataNode pNode, final String pType, final Directory pDirectory) { for (Entry entry : pDirectory) { + Object tagId = entry.getIdentifier(); + IIOMetadataNode tag = new IIOMetadataNode("Entry"); - tag.setAttribute("tag", String.format("%s", entry.getIdentifier())); + tag.setAttribute("tag", String.format("%s", tagId)); String field = entry.getFieldName(); if (field != null) { tag.setAttribute("field", String.format("%s", field)); } + else { + if ("IPTC".equals(pType)) { + tag.setAttribute("field", String.format("%s:%s", (Integer) tagId >> 8, (Integer) tagId & 0xff)); + } + } if (entry.getValue() instanceof Directory) { - appendEntries(tag, (Directory) entry.getValue()); + appendEntries(tag, pType, (Directory) entry.getValue()); tag.setAttribute("type", "Directory"); } else { @@ -614,7 +583,7 @@ public final class PSDMetadata extends AbstractMetadata { PSDEXIF1Data data = exif.next(); // Get the EXIF DateTime (aka ModifyDate) tag if present - Entry dateTime = data.mDirectory.getEntryById(0x0132); // TODO: Constant + Entry dateTime = data.mDirectory.getEntryById(TIFF.TAG_DATE_TIME); if (dateTime != null) { node = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime // Format: "YYYY:MM:DD hh:mm:ss" @@ -642,7 +611,7 @@ public final class PSDMetadata extends AbstractMetadata { // Example: TIFF Software field => /Text/TextEntry@keyword = "Software", // /Text/TextEntry@value = Name and version number of the software package(s) used to create the image. - Iterator textResources = getResources(PSDEXIF1Data.class, PSDIPTCData.class, PSDXMPData.class); + Iterator textResources = getResources(PSD.RES_IPTC_NAA, PSD.RES_EXIF_DATA_1, PSD.RES_XMP_DATA); if (!textResources.hasNext()) { return null; @@ -660,24 +629,40 @@ public final class PSDMetadata extends AbstractMetadata { if (textResource instanceof PSDIPTCData) { PSDIPTCData iptc = (PSDIPTCData) textResource; - for (Entry entry : iptc.mDirectory) { - node = new IIOMetadataNode("TextEntry"); + appendTextEntriesFlat(text, iptc.mDirectory, new FilterIterator.Filter() { + public boolean accept(final Entry pEntry) { + Integer tagId = (Integer) pEntry.getIdentifier(); - if (entry.getValue() instanceof String) { - node.setAttribute("keyword", String.format("%s", entry.getFieldName())); - node.setAttribute("value", entry.getValueAsString()); - text.appendChild(node); + switch (tagId) { + case IPTC.TAG_SOURCE: + return true; + default: + return false; + } } - } + }); } else if (textResource instanceof PSDEXIF1Data) { PSDEXIF1Data exif = (PSDEXIF1Data) textResource; - // TODO: Use name? - appendTextEntriesFlat(text, exif.mDirectory); + appendTextEntriesFlat(text, exif.mDirectory, new FilterIterator.Filter() { + public boolean accept(final Entry pEntry) { + Integer tagId = (Integer) pEntry.getIdentifier(); + + switch (tagId) { + case TIFF.TAG_SOFTWARE: + case TIFF.TAG_ARTIST: + case TIFF.TAG_COPYRIGHT: + return true; + default: + return false; + } + } + }); } else if (textResource instanceof PSDXMPData) { // TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF? + // TODO: Use XMP IPTC/EXIF/TIFFF NativeDigest field to validate if the values are in sync... PSDXMPData xmp = (PSDXMPData) textResource; } } @@ -685,15 +670,26 @@ public final class PSDMetadata extends AbstractMetadata { return text; } - private void appendTextEntriesFlat(IIOMetadataNode pNode, Directory pDirectory) { - for (Entry entry : pDirectory) { + private void appendTextEntriesFlat(final IIOMetadataNode pNode, final Directory pDirectory, final FilterIterator.Filter pFilter) { + FilterIterator pEntries = new FilterIterator(pDirectory.iterator(), pFilter); + while (pEntries.hasNext()) { + Entry entry = pEntries.next(); + if (entry.getValue() instanceof Directory) { - appendTextEntriesFlat(pNode, (Directory) entry.getValue()); + appendTextEntriesFlat(pNode, (Directory) entry.getValue(), pFilter); } else if (entry.getValue() instanceof String) { IIOMetadataNode tag = new IIOMetadataNode("TextEntry"); - // TODO: Use name! - tag.setAttribute("keyword", String.format("%s", entry.getFieldName())); + String fieldName = entry.getFieldName(); + + if (fieldName != null) { + tag.setAttribute("keyword", String.format("%s", fieldName)); + } + else { + // TODO: This should never happen, as we filter out only specific nodes + tag.setAttribute("keyword", String.format("%s", entry.getIdentifier())); + } + tag.setAttribute("value", entry.getValueAsString()); pNode.appendChild(tag); } @@ -734,13 +730,13 @@ public final class PSDMetadata extends AbstractMetadata { }); } - Iterator getResources(final Class... pResourceTypes) { + Iterator getResources(final int... pResourceTypes) { Iterator iterator = mImageResources.iterator(); return new FilterIterator(iterator, new FilterIterator.Filter() { - public boolean accept(final PSDImageResource pElement) { - for (Class type : pResourceTypes) { - if (type.isInstance(pElement)) { + public boolean accept(final PSDImageResource pResource) { + for (int type : pResourceTypes) { + if (type == pResource.mId) { return true; } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java index 7ba689e1..605e78ef 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java @@ -1,5 +1,7 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.xmp.XMPReader; import com.twelvemonkeys.lang.StringUtil; import javax.imageio.stream.ImageInputStream; @@ -21,6 +23,7 @@ import java.nio.charset.Charset; */ final class PSDXMPData extends PSDImageResource { protected byte[] mData; + Directory mDirectory; PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -29,7 +32,9 @@ final class PSDXMPData extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec) - pInput.readFully(mData); + //pInput.readFully(mData); + + mDirectory = new XMPReader().read(pInput); } @Override From c7fd5b3dd9bbe25436c95e5a223ee8fe42ca6898 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 3 Dec 2009 15:29:38 +0100 Subject: [PATCH 17/21] Fixed a bug related to transcoding images with indexed color (ie. GIF) to JPEG. Thanks to Rune Bremnes for pointing it out and providing a patch and sample image! --- .../image/ImageServletResponseImpl.java | 20 +- .../ImageServletResponseImplTestCase.java | 177 ++++++++++++------ .../com/twelvemonkeys/servlet/image/tux.gif | Bin 0 -> 5822 bytes 3 files changed, 125 insertions(+), 72 deletions(-) create mode 100644 twelvemonkeys-servlet/src/test/resources/com/twelvemonkeys/servlet/image/tux.gif diff --git a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java index dc24d041..3a023428 100755 --- a/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java +++ b/twelvemonkeys-servlet/src/main/java/com/twelvemonkeys/servlet/image/ImageServletResponseImpl.java @@ -131,9 +131,9 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima * @param pMimeType the content (MIME) type */ public void setContentType(final String pMimeType) { - // Throw exception is allready set + // Throw exception is already set if (mOriginalContentType != null) { - throw new IllegalStateException("ContentType allready set."); + throw new IllegalStateException("ContentType already set."); } mOriginalContentType = pMimeType; @@ -187,17 +187,13 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima getImage(); } - // This is stupid, but don't know how to work around... - // TODO: Test what types of images that work with JPEG, consider reporting it as a bug + // For known formats that don't support transparency, convert to opaque if (("image/jpeg".equals(outputType) || "image/jpg".equals(outputType) || "image/bmp".equals(outputType) || "image/x-bmp".equals(outputType)) && - mImage instanceof BufferedImage && ((BufferedImage) mImage).getType() == BufferedImage.TYPE_INT_ARGB) { + mImage.getColorModel().getTransparency() != Transparency.OPAQUE) { mImage = ImageUtil.toBuffered(mImage, BufferedImage.TYPE_INT_RGB); } - //System.out.println("Writing image, content-type: " + getContentType(outputType)); - //System.out.println("Writing image, outputType: " + outputType); - //System.out.println("Writing image: " + mImage); if (mImage != null) { Iterator writers = ImageIO.getImageWritersByMIMEType(outputType); if (writers.hasNext()) { @@ -218,10 +214,6 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima ImageOutputStream stream = ImageIO.createImageOutputStream(out); - //System.out.println("-ISR- Image: " + mImage); - //System.out.println("-ISR- ImageWriter: " + writer); - //System.out.println("-ISR- ImageOutputStream: " + stream); - writer.setOutput(stream); try { writer.write(null, new IIOImage(mImage, null, null), param); @@ -233,12 +225,10 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima finally { writer.dispose(); out.flush(); -// out.close(); } } else { mContext.log("ERROR: No writer for content-type: " + outputType); -// sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Unable to encode image: No writer for content-type " + outputType); throw new IIOException("Unable to transcode image: No suitable image writer found (content-type: " + outputType + ")."); } } @@ -365,8 +355,6 @@ class ImageServletResponseImpl extends HttpServletResponseWrapper implements Ima // Fill bgcolor behind image, if transparent extractAndSetBackgroundColor(image); - //System.out.println("-ISR- Image: " + image); - // Set image mImage = image; } diff --git a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java index fe4fc100..231ec787 100755 --- a/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java +++ b/twelvemonkeys-servlet/src/test/java/com/twelvemonkeys/servlet/image/ImageServletResponseImplTestCase.java @@ -26,13 +26,17 @@ import java.util.Arrays; public class ImageServletResponseImplTestCase extends MockObjectTestCase { private static final String CONTENT_TYPE_BMP = "image/bmp"; private static final String CONTENT_TYPE_FOO = "foo/bar"; + private static final String CONTENT_TYPE_GIF = "image/gif"; private static final String CONTENT_TYPE_JPEG = "image/jpeg"; private static final String CONTENT_TYPE_PNG = "image/png"; private static final String CONTENT_TYPE_TEXT = "text/plain"; - private static final String IMAGE_NAME = "12monkeys-splash.png"; + private static final String IMAGE_NAME_PNG = "12monkeys-splash.png"; + private static final String IMAGE_NAME_GIF = "tux.gif"; + + private static final Dimension IMAGE_DIMENSION_PNG = new Dimension(300, 410); + private static final Dimension IMAGE_DIMENSION_GIF = new Dimension(250, 250); - private static final Dimension IMAGE_DIMENSION = new Dimension(300, 410); private HttpServletRequest mRequest; private ServletContext mContext; @@ -43,15 +47,17 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { Mock mockRequest = mock(HttpServletRequest.class); mockRequest.stubs().method("getAttribute").will(returnValue(null)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); mRequest = (HttpServletRequest) mockRequest.proxy(); Mock mockContext = mock(ServletContext.class); - mockContext.stubs().method("getResource").with(eq("/" + IMAGE_NAME)).will(returnValue(getClass().getResource(IMAGE_NAME))); - mockContext.stubs().method("log").withAnyArguments(); // Just supress the logging + mockContext.stubs().method("getResource").with(eq("/" + IMAGE_NAME_PNG)).will(returnValue(getClass().getResource(IMAGE_NAME_PNG))); + mockContext.stubs().method("getResource").with(eq("/" + IMAGE_NAME_GIF)).will(returnValue(getClass().getResource(IMAGE_NAME_GIF))); + mockContext.stubs().method("log").withAnyArguments(); // Just suppress the logging mockContext.stubs().method("getMimeType").with(eq("file.bmp")).will(returnValue(CONTENT_TYPE_BMP)); mockContext.stubs().method("getMimeType").with(eq("file.foo")).will(returnValue(CONTENT_TYPE_FOO)); + mockContext.stubs().method("getMimeType").with(eq("file.gif")).will(returnValue(CONTENT_TYPE_GIF)); mockContext.stubs().method("getMimeType").with(eq("file.jpeg")).will(returnValue(CONTENT_TYPE_JPEG)); mockContext.stubs().method("getMimeType").with(eq("file.png")).will(returnValue(CONTENT_TYPE_PNG)); mockContext.stubs().method("getMimeType").with(eq("file.txt")).will(returnValue(CONTENT_TYPE_TEXT)); @@ -98,11 +104,11 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(mRequest, response, mContext); fakeResponse(mRequest, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); - assertEquals(IMAGE_DIMENSION.width, image.getWidth()); - assertEquals(IMAGE_DIMENSION.height, image.getHeight()); + assertEquals(IMAGE_DIMENSION_PNG.width, image.getWidth()); + assertEquals(IMAGE_DIMENSION_PNG.height, image.getHeight()); // Flush image to wrapped response imageResponse.flush(); @@ -136,7 +142,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { assertTrue("Content has no data", out.size() > 0); // Test that image data is untouched - assertTrue("Data differs", Arrays.equals(FileUtil.read(getClass().getResourceAsStream(IMAGE_NAME)), out.toByteArray())); + assertTrue("Data differs", Arrays.equals(FileUtil.read(getClass().getResourceAsStream(IMAGE_NAME_PNG)), out.toByteArray())); } // Transcode original PNG to JPEG with no other changes @@ -161,8 +167,67 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { // Test that image data is still readable BufferedImage outImage = ImageIO.read(new ByteArrayInputStream(out.toByteArray())); assertNotNull(outImage); - assertEquals(IMAGE_DIMENSION.width, outImage.getWidth()); - assertEquals(IMAGE_DIMENSION.height, outImage.getHeight()); + assertEquals(IMAGE_DIMENSION_PNG.width, outImage.getWidth()); + assertEquals(IMAGE_DIMENSION_PNG.height, outImage.getHeight()); + assertSimilarImage(ImageIO.read(mContext.getResource("/" + IMAGE_NAME_PNG)), outImage, 96f); + } + + @Test + public void testTranscodeResponseIndexedCM() throws IOException { + // Custom setup + Mock mockRequest = mock(HttpServletRequest.class); + mockRequest.stubs().method("getAttribute").will(returnValue(null)); + mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_GIF)); + mockRequest.stubs().method("getParameter").will(returnValue(null)); + HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); + + Mock mockResponse = mock(HttpServletResponse.class); + mockResponse.expects(once()).method("setContentType").with(eq(CONTENT_TYPE_JPEG)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + mockResponse.expects(once()).method("getOutputStream").will(returnValue(new OutputStreamAdapter(out))); + HttpServletResponse response = (HttpServletResponse) mockResponse.proxy(); + + ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); + fakeResponse(request, imageResponse); + + // Force transcode to JPEG + imageResponse.setOutputContentType("image/jpeg"); + + // Flush image to wrapped response + imageResponse.flush(); + + assertTrue("Content has no data", out.size() > 0); + + // Test that image data is still readable + BufferedImage outImage = ImageIO.read(new ByteArrayInputStream(out.toByteArray())); + assertNotNull(outImage); + assertEquals(IMAGE_DIMENSION_GIF.width, outImage.getWidth()); + assertEquals(IMAGE_DIMENSION_GIF.height, outImage.getHeight()); + assertSimilarImage(ImageIO.read(mContext.getResource("/" + IMAGE_NAME_GIF)), outImage, 96f); + } + + /** + * Makes sure images are the same, taking JPEG artifacts into account. + * + * @param pExpected the expected image + * @param pActual the actual image + * @param pArtifactThreshold the maximum allowed difference between the expected and actual pixel value + */ + private void assertSimilarImage(final BufferedImage pExpected, final BufferedImage pActual, final float pArtifactThreshold) { + for (int y = 0; y < pExpected.getHeight(); y++) { + for (int x = 0; x < pExpected.getWidth(); x++) { + int original = pExpected.getRGB(x, y); + int actual = pActual.getRGB(x, y); + + // Multiply in the alpha component + float alpha = ((original >> 24) & 0xff) / 255f; + + assertEquals(alpha * ((original >> 16) & 0xff), (actual >> 16) & 0xff, pArtifactThreshold); + assertEquals(alpha * ((original >> 8) & 0xff), (actual >> 8) & 0xff, pArtifactThreshold); + assertEquals(alpha * ((original) & 0xff), actual & 0xff, pArtifactThreshold); + } + } } public void testReplaceResponse() throws IOException { @@ -175,7 +240,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(mRequest, response, mContext); fakeResponse(mRequest, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); @@ -298,7 +363,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -311,7 +376,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); assertEquals(sourceRegion.width, image.getWidth()); @@ -337,7 +402,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -350,7 +415,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); assertEquals(sourceRegion.width, image.getWidth()); @@ -379,7 +444,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI_UNIFORM)).will(returnValue(true)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -392,24 +457,24 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); assertEquals(sourceRegion.width, image.getWidth()); assertEquals(sourceRegion.height, image.getHeight()); - BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME)); + BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME_PNG)); // Sanity check assertNotNull(original); - assertEquals(IMAGE_DIMENSION.width, original.getWidth()); - assertEquals(IMAGE_DIMENSION.height, original.getHeight()); + assertEquals(IMAGE_DIMENSION_PNG.width, original.getWidth()); + assertEquals(IMAGE_DIMENSION_PNG.height, original.getHeight()); // Center sourceRegion.setLocation( - (int) Math.round((IMAGE_DIMENSION.width - sourceRegion.getWidth()) / 2.0), - (int) Math.round((IMAGE_DIMENSION.height - sourceRegion.getHeight()) / 2.0) + (int) Math.round((IMAGE_DIMENSION_PNG.width - sourceRegion.getWidth()) / 2.0), + (int) Math.round((IMAGE_DIMENSION_PNG.height - sourceRegion.getHeight()) / 2.0) ); // Test that we have exactly the pixels we should @@ -442,7 +507,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI_UNIFORM)).will(returnValue(true)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -455,49 +520,49 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); // Flush image to wrapped response - imageResponse.flush(); + imageResponse.flush(); - assertTrue("Image wider than bounding box", IMAGE_DIMENSION.width >= image.getWidth()); - assertTrue("Image taller than bounding box", IMAGE_DIMENSION.height >= image.getHeight()); - assertTrue("Image not maximized to bounding box", IMAGE_DIMENSION.width == image.getWidth() || IMAGE_DIMENSION.height == image.getHeight()); + assertTrue("Image wider than bounding box", IMAGE_DIMENSION_PNG.width >= image.getWidth()); + assertTrue("Image taller than bounding box", IMAGE_DIMENSION_PNG.height >= image.getHeight()); + assertTrue("Image not maximized to bounding box", IMAGE_DIMENSION_PNG.width == image.getWidth() || IMAGE_DIMENSION_PNG.height == image.getHeight()); // Above tests that one of the sides equal, we now need to test that the other follows aspect double destAspect = sourceRegion.getWidth() / sourceRegion.getHeight(); - double srcAspect = IMAGE_DIMENSION.getWidth() / IMAGE_DIMENSION.getHeight(); + double srcAspect = IMAGE_DIMENSION_PNG.getWidth() / IMAGE_DIMENSION_PNG.getHeight(); if (srcAspect >= destAspect) { // Dst is narrower than src - assertEquals(IMAGE_DIMENSION.height, image.getHeight()); + assertEquals(IMAGE_DIMENSION_PNG.height, image.getHeight()); assertEquals( "Image width does not follow aspect", - Math.round(IMAGE_DIMENSION.getHeight() * destAspect), image.getWidth() + Math.round(IMAGE_DIMENSION_PNG.getHeight() * destAspect), image.getWidth() ); } else { // Dst is wider than src - assertEquals(IMAGE_DIMENSION.width, image.getWidth()); + assertEquals(IMAGE_DIMENSION_PNG.width, image.getWidth()); assertEquals( "Image height does not follow aspect", - Math.round(IMAGE_DIMENSION.getWidth() / destAspect), image.getHeight() + Math.round(IMAGE_DIMENSION_PNG.getWidth() / destAspect), image.getHeight() ); } - BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME)); + BufferedImage original = ImageIO.read(getClass().getResource(IMAGE_NAME_PNG)); // Sanity check assertNotNull(original); - assertEquals(IMAGE_DIMENSION.width, original.getWidth()); - assertEquals(IMAGE_DIMENSION.height, original.getHeight()); + assertEquals(IMAGE_DIMENSION_PNG.width, original.getWidth()); + assertEquals(IMAGE_DIMENSION_PNG.height, original.getHeight()); // Center sourceRegion.setLocation( - (int) Math.round((IMAGE_DIMENSION.width - image.getWidth()) / 2.0), - (int) Math.round((IMAGE_DIMENSION.height - image.getHeight()) / 2.0) + (int) Math.round((IMAGE_DIMENSION_PNG.width - image.getWidth()) / 2.0), + (int) Math.round((IMAGE_DIMENSION_PNG.height - image.getHeight()) / 2.0) ); sourceRegion.setSize(image.getWidth(), image.getHeight()); @@ -526,7 +591,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").withAnyArguments().will(returnValue(null)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -539,7 +604,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); @@ -549,10 +614,10 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { // Above tests that one of the sides equal, we now need to test that the other follows aspect if (size.width == image.getWidth()) { - assertEquals(Math.round(size.getWidth() * IMAGE_DIMENSION.getWidth() / IMAGE_DIMENSION.getHeight()), image.getHeight()); + assertEquals(Math.round(size.getWidth() * IMAGE_DIMENSION_PNG.getWidth() / IMAGE_DIMENSION_PNG.getHeight()), image.getHeight()); } else { - assertEquals(Math.round(size.getHeight() * IMAGE_DIMENSION.getWidth() / IMAGE_DIMENSION.getHeight()), image.getWidth()); + assertEquals(Math.round(size.getHeight() * IMAGE_DIMENSION_PNG.getWidth() / IMAGE_DIMENSION_PNG.getHeight()), image.getWidth()); } // Flush image to wrapped response @@ -576,7 +641,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE_UNIFORM)).will(returnValue(false)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -589,7 +654,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); assertEquals(size.width, image.getWidth()); @@ -618,7 +683,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -631,7 +696,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); @@ -658,7 +723,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { assertEquals(image.getWidth(), outImage.getWidth()); assertEquals(image.getHeight(), outImage.getHeight()); } - + @Test public void testReadWithSourceRegionAndNonUniformResize() throws IOException { Rectangle sourceRegion = new Rectangle(100, 100, 200, 200); @@ -670,7 +735,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE_UNIFORM)).will(returnValue(false)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -683,7 +748,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); assertEquals(size.width, image.getWidth()); @@ -713,7 +778,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -726,7 +791,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); @@ -774,7 +839,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -787,7 +852,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); @@ -839,7 +904,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_AOI)).will(returnValue(sourceRegion)); mockRequest.stubs().method("getAttribute").with(eq(ImageServletResponse.ATTRIB_SIZE)).will(returnValue(size)); mockRequest.stubs().method("getContextPath").will(returnValue("/ape")); - mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME)); + mockRequest.stubs().method("getRequestURI").will(returnValue("/ape/" + IMAGE_NAME_PNG)); mockRequest.stubs().method("getParameter").will(returnValue(null)); HttpServletRequest request = (HttpServletRequest) mockRequest.proxy(); @@ -852,7 +917,7 @@ public class ImageServletResponseImplTestCase extends MockObjectTestCase { ImageServletResponseImpl imageResponse = new ImageServletResponseImpl(request, response, mContext); fakeResponse(request, imageResponse); - // Make sure image is correctly loaedd + // Make sure image is correctly loaded BufferedImage image = imageResponse.getImage(); assertNotNull(image); diff --git a/twelvemonkeys-servlet/src/test/resources/com/twelvemonkeys/servlet/image/tux.gif b/twelvemonkeys-servlet/src/test/resources/com/twelvemonkeys/servlet/image/tux.gif new file mode 100644 index 0000000000000000000000000000000000000000..378cd229495ce7ce91396946fff568b990465ecd GIT binary patch literal 5822 zcmeH}`8O1N!-v1KV8&9`F@s_#du8l{YK(o&R@|BxOT>f`WzS&j6xr8C*_T4rvW$Hz zk(%r`-Q1~=C6N~I^ZfSy5zlo#f5COmbEG(mdva+%|3aASJb#-;k0ie0LxibuOc6JVifk8SL92^{{gYofk z79Frs0E@+1OaY6TU~zHrVwE0qlG5V%3l>|27I0$c~Vg~AFBAu9U6|cV~H@@Q*vL@Z!N;-#0s;<>OJI2?{q75*(Ny3y>21@f^7f7m_{vJ zNNxU!Bg=2 z1&&((a=eoU~9=YX>1abAOK-N6wu2*~h);mP7sN6V>z$soozE&%<~a z@VjSEmlmCFC~&*Z|9U-Qts_B3oiLw$%pF^uxkvu z&fAXWX1BPoq-RH z!Le#IfkCaw_f5HyVYw;O9$eX#F^7ccf(xX>jk3?|G7x08tG9Q%8V>@+6Z1xi^*H7U zXAXM~=dT_v5%|!#(o@EFpk1n1K31>QO?!Y0H6h&S^XD_KY!g|LP)2fq!YA%^M8%BO zpPb(P{rEH`fc(9=+Q3MWXG9N+&+AmWAFtH0jou(GpL4)|WH4VbBrVn^-HIf3Ac9=- zO3rMEGq&Me?~PGBc=VM3(q+9fC^ktID>Xr<{o8e{Ge3~vl~n>4;J~-3sgtgMIl7~F z?0D^hy0cYp=L6=+0v;M19}bwkNy(S@mR!7++Lj)?@(eaZpImBm;7%jw!fmddM=eC# zTtNMsaJ~a?CPu%k8s~bQ{Bxu2pFdgbm-7CwW&?M!cD~3Pu(P46L+sub^^<{==`GQ6R>ZuxR#-uvSR6X;SqU?H?~;54vgf)=JR_8bYcYaPZa+*w&2tO%c z4ssxxT$~!rMJJL*&jbB?&U$Djl+N2L5}k%Xk&m-E-r)b7P-X$py3ivZWBqT-w9qj* zkRPnckGK~Lv}7L%>fV4I=Ot*~aeZWDBZz`eP?N}FhucX1yt3d-5s3$Y5YsWM!e+=RYrrGkrO)#@_jq)HKkWfCPqs4j=rlij>*esV#0n) z+Wg-9aC+ARh-p^?LXGgyYcYEyxqe+C?wdPbGLy-t^OU1}k|d`hnQd3COjiKO3Pl>w zsV(npbYA8ezaJe!4*Y;!ws-g5Or99nl`N<|jVrRJ^N9&Se}^q{JH?E6TGG?p@PD5P zn=q?)ClpSM5l9!dAlDLONsF1rQx86kh!7+crL4-)`;A8`n93~$@uUj<@B>unPI}>x zDnXsY!J?~!;EuO~{ANmuMiEQZ{BbwiMXHH*Lm1ffSvHfp5*|q^;0F+!JEB~Rp`*BlQtKMdAD+f|)A!Hs1!l1nX`Eug zat!&(ifK^-YFR5yZ{u`Q!mlU}Km~y%)m~XYufF{7#-f+s-9}M~IQI=PB>k2Q%MM(x zn-$5K7~wg4rBP9r`9tV+*;z#-qJ}53J{oyMFLPh2&ZTk^Oxw;bHvw4@!4!fFq>WIe zwgptWqqV5I*(#9BXEsnARl^p{a(!dpW6Q+mdEc9w<4uZQ-ICq!pButf_cryl`)IYt znxkF4HW>*Nk;%{?zc!N%t9Equx92xIXLi3vlt{e)4u&5YBHpyS8#wnff|!q8Hz@iO zZ~neF(A=0LMq1%=iR1CC&odvtx~?rR0uB@?U$)9D%`s7%ug@(h_XhV!LLfE4R$i%P z8fs#suZK(QU-FZveu_0v8MDef^R271W-HPh6eQ;z9@sHOV`$$%sO7kFwdrXw@#}A@ z$&l>qp>R25w_&#Til5cUOAjIjfAp8c;vzq5EJ}S2!-+n@pLJDpA#tJol^M0d>(N}V zPIs9@q=$;V#V!j8)tvyrghzAcR$G=6NTyJ7leC40j6Oz#E#cXy)30ocfY6tvEL3t% zhiBboN8JkP-2kDqPhItP6icO>q|ahsa9=_dg%9>zMC#O(x(7TveH$D4Zb4o4*^1@I z-npF3U03{~aF#8&r+UDKMj+mtbe(i)enuiAhnJ?pd#Xp@b_ko?pBvd)NJco(Hammc zNx9`OUlgswQO9ZQ=V+=Vk;F6ZLJZd3uBXTEFt_5g!#OUXqx;hd{#_69#%x|im1@Sg zU&3OI#SvFYEow_xonI@mN5=&jY=5%it7)stgICs{$SZrQ2=^L0XpROQgX_2A--VjW zdGfDTtDRRJa&}Rs7m?}MwfSZBmS=f?OE!y67rvMfY&Z7& z*`nvR#UZ0^ApG@+G4+7I`ZuYbuH*NTZ!d+xo!vbCt2f~F4*$vpU#TN)4%JEx5+dTX zJ8sEOqcW%ye(AuwJCrPy%*bVs9r#z4*qy$HRrLMYtch9Oq7|e-v(aVtX2u#9UN;Je zb@}N%wRKmfg*coFF4K72_DXtRcRgd{Q&C35^kpW=FFDZzDMT9!`xomkf_@ito3`9=i4H{5T5V=u`ra=GeLNFWLQ? zdiikb-45z_7Hs;x@NHG8mR91Ch8NmiK{A6z&yYgwqpXeGEopGqR=+)aN8an9-fB{o zfoNBjhb1GRuhmZgp_1As)@dN_oMqpe<$WKT`s(@^_Xa%js9<|b4q?J9V zh3#cev>XLRdRE*DVu#0RM(QJk_j6#p4i^`7;}7jGKgW4x4pU{?{3e@(IV6-54zxcl z->%Aq-PMc^`W{%;<~R3Uqu?O^o+c_qk0z?_bxAn*NH}z-#DD$t2~%klW#IB~#*M7~ zNS_0Zu;3d*x+yjpuzm!{@brB4o_e9p?^A_n-g``kktkLcr6{3HZi6MVQn_1c(I4Vn z(i4{x(EqfG$OopXx5fsk2XkwwggbTL`wWwkzae z;jGglM@?7czolNBrv5sJOb*t1SP>>{DEj)g4W6N8nt9{WLHce^@dn7G z*H3F@=mzHco5ouMRkFrg^beTar;UEd`bWE3^60vGLqtvJE5zj-!SA#M4rA;*KEW;*Cw0R4^ zw!k&0P!Ym|UPx99L=0F#)m34Tdf{(X*B2a$3k~z_-uOo^!)sh^HJV9xA*J$zRIw2+ z?JWw1QTndLY9$t95DT%fgL5&kH>#+|XDNSGn!U;}gFi5zgM+_XQ6YCq^h@D$tK#d! z9)`@)pzp3N`ep69m+Il5RtDX&EyT5Dn0%R7IFq|CB+d)YdmM?JtokN&JwZsF6F{l< zHgtXJazR2;fQw7jx{H}}PtjiuaBY5A_*-~OGo^sjh168J7W1eVXYP!9Ghk^q+z!e?w)tKIws4s+U@pHCVx# zEGnvmrdxHVN?{R`DF<5eX>Uz_I@aPvs?A*BpFRR#Q*-Quutjb?y;qrgu0El`?6|gu z3GOd4A4Evo!hE8vcek{A)Wn8v84xOZIS{fw_sCn;>G&MD6a~> z5YZD?_0Q87MS`#YD$7s9zR#t~k%;!|Qtzbn2Ur|D+=#!-O*SoNailB2iMQBMK{}_v@GVjUMNd-+BSU){o&RDy2D;l;t`y~8&()!3S6R1PlST)_Kj9XP z#-An`xb%ELu6mtZGAWPApUv%a1v5u;6)uJyXP4D>@;iU3VivgWTb=@IV|FejptOb` zD+_{?yI$!LGjy9evpv^p;0OuS(G`J04wNRXdum9UG!#@&V zgWx_-lmP$6SB?ljnc;$ogCrpA$|(-X|EqF(mkPyQjwqr3www7YcgE zr{Hrnw1h){GWIG$5w4U5ky3;Tne(qFLbZPBs56DPXL9XhIx*((Ef|>j&c8j`Vll5% z<>+1GeOYF%|4`J+M2sJ)TzPF?=h0Yx!Kg1s$DpuQlN0gcljL za4DS}naOZbrICpjoty-ivXV7yYbdhCpNbvLH%Fq$NH;FpxZh9-j$DoH(OxhRC@5PD}KJ8NVa%s&?)18Z{zu(X}$4g zV(?ly(i5h7VA>19BTLa{6&~(U;J`I#kynC6cs^}v{QARm6N0sZ)fK(a0XG$5780plLI2kLINO!}QWhKUf-^$nxnN>NQn?Tw>2B$i<-$`CzLeGv1^VLJ~ zXAIQSKBV6k$czDyGX@)qDC?7_&-}T>MTY7fBN{QERy-3J_b9nJ-rCAKeWu`v7CV3{ z|ADJ8TYq8}l%nABO#rh5Rr#sIfI}-H)=qM`cY6RSEBnA^s4b^3CofBDJKE{GwmMA1 znEY=!-;$p0hhr`0KP$&Qzb1HLDeCDW(GueO;+~-5(igYoPXd`=Ui3jp7X%fzJk0v) zs!t0V^SbLU3{4ep%yw)g{{u~- BF%AF# literal 0 HcmV?d00001 From d97a0cc00b568dd1890b7667eaf6b71f990a87bd Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Mon, 7 Dec 2009 13:45:41 +0100 Subject: [PATCH 18/21] Now allows 64 entries in the EHB pallette. --- .../com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java b/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java index b9280135..4c0fa687 100755 --- a/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java +++ b/twelvemonkeys-imageio/iff/src/main/java/com/twelvemonkeys/imageio/plugins/iff/CMAPChunk.java @@ -86,7 +86,7 @@ class CMAPChunk extends IFFChunk { if (numColors == 32) { paletteSize = 64; } - else { + else if (numColors != 64) { throw new IIOException("Unknown number of colors for EHB: " + numColors); } } @@ -100,7 +100,9 @@ class CMAPChunk extends IFFChunk { mGreens[i] = pInput.readByte(); mBlues[i] = pInput.readByte(); } - if (isEHB) { + + if (isEHB && numColors == 32) { + // Create the half-brite colors for (int i = 0; i < numColors; i++) { mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2); mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2); From 20d6e3564071cce69bb02fb55051354a3e323807 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 10 Dec 2009 18:48:12 +0100 Subject: [PATCH 19/21] Work in progress for PSD metadata support: - Added Rational class for EXIF datatype support - Added more EXIF fields/field names - Added/updated license in source files --- .../imageio/metadata/AbstractDirectory.java | 28 +++ .../imageio/metadata/AbstractEntry.java | 28 +++ .../imageio/metadata/Directory.java | 28 +++ .../twelvemonkeys/imageio/metadata/Entry.java | 28 +++ .../imageio/metadata/MetadataReader.java | 28 +++ .../imageio/metadata/exif/EXIF.java | 28 +++ .../imageio/metadata/exif/EXIFDirectory.java | 28 +++ .../imageio/metadata/exif/EXIFEntry.java | 42 ++++ .../imageio/metadata/exif/EXIFReader.java | 43 +++- .../imageio/metadata/exif/Rational.java | 214 ++++++++++++++++++ .../imageio/metadata/exif/TIFF.java | 66 +++++- .../imageio/metadata/iptc/IPTC.java | 28 +++ .../imageio/metadata/iptc/IPTCDirectory.java | 28 +++ .../imageio/metadata/iptc/IPTCEntry.java | 28 +++ .../imageio/metadata/iptc/IPTCReader.java | 28 +++ .../imageio/metadata/xmp/XMP.java | 28 +++ .../imageio/metadata/xmp/XMPDirectory.java | 28 +++ .../imageio/metadata/xmp/XMPEntry.java | 28 +++ .../metadata/xmp/XMPNamespaceMapping.java | 28 +++ .../imageio/metadata/xmp/XMPReader.java | 33 ++- .../imageio/metadata/xmp/XMPScanner.java | 28 +++ .../metadata/exif/RationalTestCase.java | 143 ++++++++++++ 22 files changed, 976 insertions(+), 13 deletions(-) create mode 100644 twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Rational.java create mode 100644 twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/RationalTestCase.java diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java index 4837a330..69748af5 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata; import java.util.ArrayList; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java index 2ef60c07..75b8b456 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata; import com.twelvemonkeys.lang.Validate; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java index 07c5fb0e..e1766f54 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata; /** diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java index 3b8f7da2..8681382e 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Entry.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata; /** diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java index d67aff25..01457bf4 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/MetadataReader.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata; import javax.imageio.stream.ImageInputStream; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java index 5ff05628..46e22826 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.exif; /** diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java index 77bb8e64..d1ce5930 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.exif; import com.twelvemonkeys.imageio.metadata.AbstractDirectory; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index da2e4c6e..fef71f37 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.exif; import com.twelvemonkeys.imageio.metadata.AbstractEntry; @@ -25,6 +53,20 @@ final class EXIFEntry extends AbstractEntry { @Override public String getFieldName() { switch ((Integer) getIdentifier()) { + case TIFF.TAG_COMPRESSION: + return "Compression"; + case TIFF.TAG_ORIENTATION: + return "Orientation"; + case TIFF.TAG_X_RESOLUTION: + return "XResolution"; + case TIFF.TAG_Y_RESOLUTION: + return "YResolution"; + case TIFF.TAG_RESOLUTION_UNIT: + return "ResolutionUnit"; + case TIFF.TAG_JPEG_INTERCHANGE_FORMAT: + return "JPEGInterchangeFormat"; + case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: + return "JPEGInterchangeFormatLength"; case TIFF.TAG_SOFTWARE: return "Software"; case TIFF.TAG_DATE_TIME: diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java index f4dd56f1..2118bc57 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.exif; import com.twelvemonkeys.imageio.metadata.Directory; @@ -119,7 +147,7 @@ public final class EXIFReader extends MetadataReader { private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException { switch (pType) { case 2: - // TODO: This might be UTF-8 or ISO-8859-1, even spec says ASCII + // TODO: This might be UTF-8 or ISO-8859-1, 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 @@ -176,26 +204,25 @@ public final class EXIFReader extends MetadataReader { pInput.readFully(doubles, 0, doubles.length); return doubles; - // TODO: Consider using a Rational class case 5: if (pCount == 1) { - return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt(); + return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt()); } - double[] rationals = new double[pCount]; + Rational[] rationals = new Rational[pCount]; for (int i = 0; i < rationals.length; i++) { - rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt(); + rationals[i] = new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt()); } return rationals; case 10: if (pCount == 1) { - return pInput.readInt() / (double) pInput.readInt(); + return new Rational(pInput.readInt(), pInput.readInt()); } - double[] srationals = new double[pCount]; + Rational[] srationals = new Rational[pCount]; for (int i = 0; i < srationals.length; i++) { - srationals[i] = pInput.readInt() / (double) pInput.readInt(); + srationals[i] = new Rational(pInput.readInt(), pInput.readInt()); } return srationals; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Rational.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Rational.java new file mode 100644 index 00000000..ffe69ae0 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/Rational.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2009, 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. + */ + +/* + * Adapted from sample code featured in + * "Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley) + * by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license. + */ + +package com.twelvemonkeys.imageio.metadata.exif; + +/** + * Represents a rational number with a {@code long} numerator and {@code long} denominator. + * Rational numbers are stored in reduced form with the sign stored with the numerator. + * Rationals are immutable. + *

+ * Adapted from sample code featured in + * "Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley) + * by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license. + * + * @author Harald Kuhr + * @author Robert Sedgewick and Kevin Wayne (original version) + * @author last modified by $Author: haraldk$ + * @version $Id: Rational.java,v 1.0 Nov 18, 2009 1:12:00 AM haraldk Exp$ + */ +public final class Rational extends Number implements Comparable { + // TODO: Document public API + // TODO: Move to com.tm.lang? + // Inspired by http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html and java.lang.Integer + static final Rational ZERO = new Rational(0, 1); + + private final long mNumerator; + private final long mDenominator; + + public Rational(final long pNumber) { + this(pNumber, 1); + } + + public Rational(final long pNumerator, final long pDenominator) { + if (pDenominator == 0) { + throw new IllegalArgumentException("denominator == 0"); + } + if (pNumerator == Long.MIN_VALUE || pDenominator == Long.MIN_VALUE) { + throw new IllegalArgumentException("value == Long.MIN_VALUE"); + } + + // Reduce fractions + long gcd = gcd(pNumerator, pDenominator); + long num = pNumerator / gcd; + long den = pDenominator / gcd; + + mNumerator = pDenominator >= 0 ? num : -num; + mDenominator = pDenominator >= 0 ? den : -den; + } + + private static long gcd(final long m, final long n) { + if (m < 0) { + return gcd(n, -m); + } + + return n == 0 ? m : gcd(n, m % n); + } + + private static long lcm(final long m, final long n) { + if (m < 0) { + return lcm(n, -m); + } + + return m * (n / gcd(m, n)); // parentheses important to avoid overflow + } + + public long numerator() { + return mNumerator; + } + + public long denominator() { + return mDenominator; + } + + /// Number implementation + + @Override + public int intValue() { + return (int) doubleValue(); + } + + @Override + public long longValue() { + return (long) doubleValue(); + } + + @Override + public float floatValue() { + return (float) doubleValue(); + } + + @Override + public double doubleValue() { + return mNumerator / (double) mDenominator; + } + + /// Comparable implementation + + public int compareTo(final Rational pOther) { + double thisVal = doubleValue(); + double otherVal = pOther.doubleValue(); + + return thisVal < otherVal ? -1 : thisVal == otherVal ? 0 : 1; + } + + /// Object overrides + + @Override + public int hashCode() { + return Float.floatToIntBits(floatValue()); + } + + @Override + public boolean equals(final Object pOther) { + return pOther == this || pOther instanceof Rational && compareTo((Rational) pOther) == 0; + } + + @Override + public String toString() { + return mDenominator == 1 ? Long.toString(mNumerator) : String.format("%s/%s", mNumerator, mDenominator); + } + + /// Operations (adapted from http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html) + // TODO: Naming! multiply/divide/add/subtract or times/divides/plus/minus + + // return a * b, staving off overflow as much as possible by cross-cancellation + public Rational times(final Rational pOther) { + // special cases + if (equals(ZERO) || pOther.equals(ZERO)) { + return ZERO; + } + + // reduce p1/q2 and p2/q1, then multiply, where a = p1/q1 and b = p2/q2 + Rational c = new Rational(mNumerator, pOther.mDenominator); + Rational d = new Rational(pOther.mNumerator, mDenominator); + + return new Rational(c.mNumerator * d.mNumerator, c.mDenominator * d.mDenominator); + } + + // return a + b, staving off overflow + public Rational plus(final Rational pOther) { + // special cases + if (equals(ZERO)) { + return pOther; + } + if (pOther.equals(ZERO)) { + return this; + } + + // Find gcd of numerators and denominators + long f = gcd(mNumerator, pOther.mNumerator); + long g = gcd(mDenominator, pOther.mDenominator); + + // add cross-product terms for numerator + // multiply back in + return new Rational( + ((mNumerator / f) * (pOther.mDenominator / g) + (pOther.mNumerator / f) * (mDenominator / g)) * f, + lcm(mDenominator, pOther.mDenominator) + ); + } + + // return -a + public Rational negate() { + return new Rational(-mNumerator, mDenominator); + } + + // return a - b + public Rational minus(final Rational pOther) { + return plus(pOther.negate()); + } + + public Rational reciprocal() { + return new Rational(mDenominator, mNumerator); + } + + // return a / b + public Rational divides(final Rational pOther) { + if (pOther.equals(ZERO)) { + throw new ArithmeticException("/ by zero"); + } + + return times(pOther.reciprocal()); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java index b47ae01b..c7fc6e27 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.exif; /** @@ -45,9 +73,45 @@ public interface TIFF { int IFD_GPS = 0x8825; int IFD_INTEROP = 0xA005; + /// A. Tags relating to image data structure: + + int TAG_IMAGE_WIDTH = 256; + int TAG_IMAGE_HEIGHT = 257; + int TAG_BITS_PER_SAMPLE = 258; + int TAG_COMPRESSION = 259; + int TAG_PHOTOMETRIC_INTERPRETATION = 262; + int TAG_ORIENTATION = 274; + int TAG_SAMPLES_PER_PIXELS = 277; + int TAG_PLANAR_CONFIGURATION = 284; + int TAG_YCBCR_SUB_SAMPLING = 530; + int TAG_YCBCR_POSITIONING = 531; + int TAG_X_RESOLUTION = 282; + int TAG_Y_RESOLUTION = 283; + int TAG_RESOLUTION_UNIT = 296; + + /// B. Tags relating to recording offset + + int TAG_STRIP_OFFSETS = 273; + int TAG_ROWS_PER_STRIP = 278; + int TAG_STRIP_BYTE_COUNTS = 279; + int TAG_JPEG_INTERCHANGE_FORMAT = 513; + int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514; + + /// C. Tags relating to image data characteristics + + int TAG_TRANSFER_FUNCTION = 301; + int TAG_WHITE_POINT = 318; + int TAG_PRIMARY_CHROMATICITIES = 319; + int TAG_YCBCR_COEFFICIENTS = 529; + int TAG_REFERENCE_BLACK_WHITE = 532; + + /// D. Other tags - int TAG_SOFTWARE = 305; int TAG_DATE_TIME = 306; + int TAG_IMAGE_DESCRIPTION = 270; + int TAG_MAKE = 271; + int TAG_MODEL = 272; + int TAG_SOFTWARE = 305; int TAG_ARTIST = 315; int TAG_COPYRIGHT = 33432; } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java index a762f6bb..a149aa80 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTC.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.iptc; /** diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java index e3bb2745..a3fb3325 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCDirectory.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.iptc; import com.twelvemonkeys.imageio.metadata.AbstractDirectory; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java index 9c046911..3b1175a2 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.iptc; import com.twelvemonkeys.imageio.metadata.AbstractEntry; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java index eea40917..a9f23b96 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.iptc; import com.twelvemonkeys.imageio.metadata.Directory; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java index b67d24ab..d36cc6c4 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.xmp; import java.util.Collections; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java index b79ad8d1..8f4d53a2 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.xmp; import com.twelvemonkeys.imageio.metadata.AbstractDirectory; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java index 4ef515ff..202a0ce7 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.xmp; import com.twelvemonkeys.imageio.metadata.AbstractEntry; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java index bc0077be..0170acb3 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.xmp; import java.util.HashMap; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java index fff9de38..ff4d4f51 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.xmp; import com.twelvemonkeys.imageio.metadata.*; @@ -70,8 +98,6 @@ public final class XMPReader extends MetadataReader { } } - // TODO: Consider using namespace-prefix in tags/identifiers and qName as field only!? - private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes) { Map> subdirs = new LinkedHashMap>(); @@ -103,7 +129,6 @@ public final class XMPReader extends MetadataReader { continue; } - // TODO: Preserve the stRef namespace here.. entries.add(new XMPEntry(child.getNamespaceURI() + child.getLocalName(), child.getLocalName(), getChildTextValue(child))); } value = new XMPDirectory(entries); @@ -120,12 +145,12 @@ public final class XMPReader extends MetadataReader { value = getChildTextValue(node); } - // TODO: Preserve namespace (without URI?) here.. XMPEntry entry = new XMPEntry(node.getNamespaceURI() + node.getLocalName(), node.getLocalName(), value); dir.add(entry); } } + // TODO: Consider flattening the somewhat artificial directory structure List entries = new ArrayList(); for (Map.Entry> entry : subdirs.entrySet()) { diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java index 8e9f6a7c..07f2b2fa 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java @@ -1,3 +1,31 @@ +/* + * Copyright (c) 2009, 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.metadata.xmp; import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; diff --git a/twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/RationalTestCase.java b/twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/RationalTestCase.java new file mode 100644 index 00000000..819c334e --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/test/java/com/twelvemonkeys/imageio/metadata/exif/RationalTestCase.java @@ -0,0 +1,143 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +import junit.framework.TestCase; + +/** + * RationalTestCase + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: RationalTestCase.java,v 1.0 Nov 18, 2009 3:23:17 PM haraldk Exp$ + */ +public class RationalTestCase extends TestCase { + public void testZeroDenominator() { + try { + new Rational(1, 0); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + } + } + + // TODO: Find a solution to this problem, as we should be able to work with it... + public void testLongMinValue() { + try { + new Rational(Long.MIN_VALUE, 1); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + } + + try { + new Rational(1, Long.MIN_VALUE); + fail("IllegalArgumentException expected"); + } + catch (IllegalArgumentException expected) { + } + } + + public void testEquals() { + assertEquals(new Rational(0, 1), new Rational(0, 999)); + assertEquals(new Rational(0, 1), new Rational(0, -1)); + assertEquals(new Rational(1, 2), new Rational(1000000, 2000000)); + assertEquals(new Rational(1, -2), new Rational(-1, 2)); + + Rational x = new Rational(1, -2); + Rational y = new Rational(-1000000, 2000000); + assertEquals(x, y); + assertEquals(x.numerator(), y.numerator()); + assertEquals(x.denominator(), y.denominator()); + + } + + public void testEqualsBoundaries() { + assertEquals(new Rational(Long.MAX_VALUE, Long.MAX_VALUE), new Rational(1, 1)); + + // NOTE: Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE... :-P + assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MIN_VALUE + 1), new Rational(1, 1)); + assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MAX_VALUE), new Rational(-1, 1)); + assertEquals(new Rational(Long.MAX_VALUE, Long.MIN_VALUE + 1), new Rational(-1, 1)); + } + + public void testReciprocal() { + assertEquals(new Rational(1, 99), new Rational(99, 1).reciprocal()); + assertEquals(new Rational(-1, 1234567), new Rational(-1234567, 1).reciprocal()); + } + + public void testNegate() { + assertEquals(new Rational(-1, 99), new Rational(1, 99).negate()); + assertEquals(new Rational(1, 1234567), new Rational(1, -1234567).negate()); + } + + public void testPlus() { + Rational x, y; + + // 1/2 + 1/3 = 5/6 + x = new Rational(1, 2); + y = new Rational(1, 3); + assertEquals(new Rational(5, 6), x.plus(y)); + + // 8/9 + 1/9 = 1 + x = new Rational(8, 9); + y = new Rational(1, 9); + assertEquals(new Rational(1, 1), x.plus(y)); + + // 1/200000000 + 1/300000000 = 1/120000000 + x = new Rational(1, 200000000); + y = new Rational(1, 300000000); + assertEquals(new Rational(1, 120000000), x.plus(y)); + + // 1073741789/20 + 1073741789/30 = 1073741789/12 + x = new Rational(1073741789, 20); + y = new Rational(1073741789, 30); + assertEquals(new Rational(1073741789, 12), x.plus(y)); + + // x + 0 = x + assertEquals(x, x.plus(Rational.ZERO)); + } + + public void testTimes() { + Rational x, y; + + // 4/17 * 17/4 = 1 + x = new Rational(4, 17); + y = new Rational(17, 4); + assertEquals(new Rational(1, 1), x.times(y)); + + // 3037141/3247033 * 3037547/3246599 = 841/961 + x = new Rational(3037141, 3247033); + y = new Rational(3037547, 3246599); + assertEquals(new Rational(841, 961), x.times(y)); + + // x * 0 = 0 + assertEquals(Rational.ZERO, x.times(Rational.ZERO)); + } + + public void testMinus() { + // 1/6 - -4/-8 = -1/3 + Rational x = new Rational(1, 6); + Rational y = new Rational(-4, -8); + assertEquals(new Rational(-1, 3), x.minus(y)); + + // x - 0 = x + assertEquals(x, x.minus(Rational.ZERO)); + } + + public void testDivides() { + // 3037141/3247033 / 3246599/3037547 = 841/961 + Rational x = new Rational(3037141, 3247033); + Rational y = new Rational(3246599, 3037547); + assertEquals(new Rational(841, 961), x.divides(y)); + + // 0 / x = 0 + assertEquals(Rational.ZERO, new Rational(0, 386).divides(x)); + } + + public void testDivideZero() { + try { + new Rational(3037141, 3247033).divides(new Rational(0, 1)); + } + catch (ArithmeticException expected) { + } + } +} From ef09b599f6d89f49666653161cedd7196c65e8f6 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 27 Dec 2009 16:33:09 +0100 Subject: [PATCH 20/21] Updated metadataformat to use new Metadata classes (how did IDEA's refactorings miss this? Hmmm...) --- .../twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java index b842ad3b..b6a78cb6 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -1,5 +1,6 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.Directory; import org.w3c.dom.Document; import javax.imageio.ImageTypeSpecifier; @@ -98,7 +99,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> ImageResources -> EXIF addElement("EXIF", "ImageResources", CHILD_POLICY_EMPTY); - addObjectValue("EXIF", PSDEXIF1Data.Directory.class, true, null); + addObjectValue("EXIF", Directory.class, true, null); // TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?) // root -> ImageResources -> GridAndGuideInfo @@ -116,7 +117,7 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // root -> ImageResources -> IPTC addElement("IPTC", "ImageResources", CHILD_POLICY_EMPTY); - addObjectValue("IPTC", PSDIPTCData.Directory.class, true, null); + addObjectValue("IPTC", Directory.class, true, null); // TODO: Incorporate IPTC metadata here somehow... (or treat as opaque bytes?) // root -> ImageResources -> PixelAspectRatio From 261c6f803870108b6e49f7d501f648724d0a1804 Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Sun, 27 Dec 2009 16:34:13 +0100 Subject: [PATCH 21/21] Implemented to/from CIEXYZ (via sRGB conversio) for completeness. --- .../com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java index 53f043ba..54715827 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/CMYKColorSpace.java @@ -103,10 +103,10 @@ Easier to calculate if K' is calculated first, because K' = min(C,M,Y): } public float[] toCIEXYZ(float[] colorvalue) { - throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement + return sRGB.toCIEXYZ(toRGB(colorvalue)); } public float[] fromCIEXYZ(float[] colorvalue) { - throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement + return sRGB.fromCIEXYZ(fromRGB(colorvalue)); } }