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);