From c3cafc63d82476c1853b21195c45138edcb8e8dd Mon Sep 17 00:00:00 2001 From: Harald Kuhr Date: Thu, 26 Feb 2015 21:51:48 +0100 Subject: [PATCH] TMI-98: Fix PSDMetadata to include layer info + Cleanup and other minor fixes. --- .../imageio/metadata/iptc/IPTCEntry.java | 6 + .../imageio/metadata/psd/PSDEntry.java | 35 ++++++ .../imageio/plugins/psd/AbstractMetadata.java | 5 + .../imageio/plugins/psd/PSD.java | 64 +++++++++-- .../psd/PSDChannelSourceDestinationRange.java | 4 +- .../plugins/psd/PSDGlobalLayerMask.java | 37 +++--- .../imageio/plugins/psd/PSDImageReader.java | 17 ++- .../plugins/psd/PSDLayerBlendMode.java | 8 +- .../imageio/plugins/psd/PSDLayerInfo.java | 10 +- .../imageio/plugins/psd/PSDLayerMaskData.java | 5 +- .../imageio/plugins/psd/PSDMetadata.java | 106 ++++++++++++++++-- .../plugins/psd/PSDMetadataFormat.java | 46 +++++++- .../imageio/plugins/psd/PSDUtil.java | 13 +-- 13 files changed, 284 insertions(+), 72 deletions(-) diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java index be129c3e..6bca3f91 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java @@ -49,6 +49,12 @@ class IPTCEntry extends AbstractEntry { return "RecordVersion"; case IPTC.TAG_SOURCE: return "Source"; + case IPTC.TAG_CAPTION: + return "Caption"; + case IPTC.TAG_COPYRIGHT_NOTICE: + return "CopyrightNotice"; + case IPTC.TAG_BY_LINE: + return "ByLine"; // TODO: More tags... } diff --git a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java index bd444e39..b89d4eb4 100644 --- a/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java +++ b/imageio/imageio-metadata/src/main/java/com/twelvemonkeys/imageio/metadata/psd/PSDEntry.java @@ -31,6 +31,8 @@ package com.twelvemonkeys.imageio.metadata.psd; import com.twelvemonkeys.imageio.metadata.AbstractEntry; import com.twelvemonkeys.lang.StringUtil; +import java.lang.reflect.Field; + /** * PhotoshopEntry * @@ -53,6 +55,39 @@ class PSDEntry extends AbstractEntry { @Override public String getFieldName() { + Class[] classes = new Class[] {getPSDClass()}; + + for (Class cl : classes) { + Field[] fields = cl.getDeclaredFields(); + + for (Field field : fields) { + try { + if (field.getType() == Integer.TYPE && field.getName().startsWith("RES_")) { + field.setAccessible(true); + + if (field.get(null).equals(getIdentifier())) { + return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true); + } + } + } + catch (IllegalAccessException e) { + // Should never happen, but in case, abort + break; + } + } + } + return name; } + + private Class getPSDClass() { + // TODO: Instead, move members to metadata module PSD class! + try { + return Class.forName("com.twelvemonkeys.imageio.plugins.psd.PSD"); + } + catch (ClassNotFoundException ignore) { + } + + return PSD.class; + } } diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java index c5f3a528..091246c7 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java @@ -129,4 +129,9 @@ abstract class AbstractMetadata extends IIOMetadata implements Cloneable { String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames)) ); } + + protected static String toListString(short[] values) { + String string = Arrays.toString(values); + return string.substring(1, string.length() - 1); + } } diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java index 8c676663..b1c0539a 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSD.java @@ -36,15 +36,15 @@ package com.twelvemonkeys.imageio.plugins.psd; * @version $Id: PSD.java,v 1.0 Apr 29, 2008 4:47:47 PM haraldk Exp$ * * @see Adobe Photoshop File Formats Specification - * @see http://www.fileformat.info/format/psd/egff.htm + * @see Adobe Photoshop File Format Summary */ interface PSD { /** PSD 2+ Native format (.PSD) identifier "8BPS" */ int SIGNATURE_8BPS = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'S'; - // TODO: Is this ever used??! Spec says (and sample files uses) 8BPS + version == 2 for PSB... - /** PSD 5+ Large Document Format (.PSB) identifier "8BPB" */ - int SIGNATURE_8BPB = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'B'; + // This is never used, it seems. Spec says (and sample files uses) 8BPS + version == 2 for PSB... + //** PSD 5+ Large Document Format (.PSB) identifier "8BPB" */ + //int SIGNATURE_8BPB = ('8' << 24) + ('B' << 16) + ('P' << 8) + 'B'; int VERSION_PSD = 1; int VERSION_PSB = 2; @@ -53,6 +53,9 @@ interface PSD { int RESOURCE_TYPE = ('8' << 24) + ('B' << 16) + ('I' << 8) + 'M'; // Blending modes + /** Pass through blending mode "pass"*/ + int BLEND_PASS = ('p' << 24) + ('a' << 16) + ('s' << 8) + 's'; + /** Normal blending mode "norm"*/ int BLEND_NORM = ('n' << 24) + ('o' << 16) + ('r' << 8) + 'm'; @@ -75,7 +78,7 @@ interface PSD { int BLEND_LUM = ('l' << 24) + ('u' << 16) + ('m' << 8) + ' '; /** Multiply blending mode "mul " */ - int BELND_MUL = ('m' << 24) + ('u' << 16) + ('l' << 8) + ' '; + int BLEND_MUL = ('m' << 24) + ('u' << 16) + ('l' << 8) + ' '; /** Screen blending mode "scrn" */ int BLEND_SCRN = ('s' << 24) + ('c' << 16) + ('r' << 8) + 'n'; @@ -95,6 +98,45 @@ interface PSD { /** Difference blending mode "diff" */ int BLEND_DIFF = ('d' << 24) + ('i' << 16) + ('f' << 8) + 'f'; + /** Color burn blending mode "idiv" */ + int BLEND_IDIV = ('i' << 24) + ('d' << 16) + ('i' << 8) + 'v'; + + /** Linear burn blending mode "lbrn" */ + int BLEND_LBRN = ('l' << 24) + ('b' << 16) + ('r' << 8) + 'n'; + + /** Darker color blending mode "dkCl" */ + int BLEND_DKCL = ('d' << 24) + ('k' << 16) + ('C' << 8) + 'l'; + + /** Color dodge blending mode "div " */ + int BLEND_DIV = ('d' << 24) + ('i' << 16) + ('v' << 8) + ' '; + + /** Linear dodge blending mode "lddg" */ + int BLEND_LDDG = ('l' << 24) + ('d' << 16) + ('d' << 8) + 'g'; + + /** Lighter color blending mode "lgCl" */ + int BLEND_LGCL = ('l' << 24) + ('g' << 16) + ('C' << 8) + 'l'; + + /** Vivid light blending mode "vLit" */ + int BLEND_VLIT = ('v' << 24) + ('L' << 16) + ('i' << 8) + 't'; + + /** Linear light blending mode "lLit" */ + int BLEND_LLIT = ('l' << 24) + ('L' << 16) + ('i' << 8) + 't'; + + /** Pin light blending mode "pLit" */ + int BLEND_PLIT = ('p' << 24) + ('L' << 16) + ('i' << 8) + 't'; + + /** Hard mix blending mode "hMix" */ + int BLEND_HMIX = ('h' << 24) + ('M' << 16) + ('i' << 8) + 'x'; + + /** Exclusion blending mode "smud" */ + int BLEND_SMUD = ('s' << 24) + ('m' << 16) + ('u' << 8) + 'd'; + + /** Subtract blending mode "fsub" */ + int BLEND_FSUB = ('f' << 24) + ('s' << 16) + ('u' << 8) + 'b'; + + /** Divide blending mode "fdiv" */ + int BLEND_FDIV = ('f' << 24) + ('d' << 16) + ('i' << 8) + 'v'; + // Compression modes /** No compression */ int COMPRESSION_NONE = 0; @@ -504,12 +546,17 @@ interface PSD { /** * (Photoshop CS) Pixel Aspect Ratio * 4 bytes (version = 1), 8 bytes double, x / y of a pixel - * 0x0429 1065 (Photoshop CS) Layer Comps - * 4 bytes (descriptor version = 16), Descriptor (see ?Descriptor structure? - * on page57) */ int RES_PIXEL_ASPECT_RATIO = 0x0428; + + // 1065 + /** + * (Photoshop CS) Layer Comps + * 4 bytes (descriptor version = 16), Descriptor. + */ + int RES_LAYER_COMPS = 0x0429; + // 1066 /** * (Photoshop CS) Alternate Duotone Colors @@ -554,5 +601,4 @@ interface PSD { /** Plug-In resource(s). Resources added by a plug-in. See the plug-in API found in the SDK documentation */ int RES_PLUGIN_MAX = 0x1387; - } diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java index 3bff3bba..406d8ccb 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDChannelSourceDestinationRange.java @@ -45,7 +45,7 @@ final class PSDChannelSourceDestinationRange { private short destBlack; private short destWhite; - public PSDChannelSourceDestinationRange(ImageInputStream pInput, String pChannel) throws IOException { + public PSDChannelSourceDestinationRange(final ImageInputStream pInput, final String pChannel) throws IOException { channel = pChannel; sourceBlack = pInput.readShort(); sourceWhite = pInput.readShort(); @@ -56,7 +56,7 @@ final class PSDChannelSourceDestinationRange { @Override public String toString() { StringBuilder builder = new StringBuilder(getClass().getSimpleName()); - + builder.append("[(").append(channel); builder.append("): sourceBlack: ").append(Integer.toHexString(sourceBlack & 0xffff)); builder.append(", sourceWhite: ").append(Integer.toHexString(sourceWhite & 0xffff)); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java index 68366faf..6d26f396 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDGlobalLayerMask.java @@ -39,29 +39,23 @@ import java.io.IOException; * @version $Id: PSDGlobalLayerMask.java,v 1.0 May 8, 2008 5:33:48 PM haraldk Exp$ */ final class PSDGlobalLayerMask { - final int colorSpace; - final int color1; - final int color2; - final int color3; - final int color4; + final int colorSpace; + final short[] colors = new short[4]; final int opacity; final int kind; - PSDGlobalLayerMask(final ImageInputStream pInput) throws IOException { + PSDGlobalLayerMask(final ImageInputStream pInput, final long globalLayerMaskLength) throws IOException { colorSpace = pInput.readUnsignedShort(); // Undocumented - color1 = pInput.readUnsignedShort(); - color2 = pInput.readUnsignedShort(); - color3 = pInput.readUnsignedShort(); - color4 = pInput.readUnsignedShort(); + pInput.readFully(colors, 0, colors.length); opacity = pInput.readUnsignedShort(); // 0-100 - kind = pInput.readUnsignedByte(); // 0: Selected (ie inverted), 1: Color protected, 128: Use value stored per layer + // Kind: 0: Selected (ie inverted), 1: Color protected, 128: Use value stored per layer (actually, the value is 80, not 0x80) + kind = pInput.readUnsignedByte(); - // TODO: Variable: Filler zeros - - pInput.readByte(); // Pad + // Skip "Variable: Filler zeros" + pInput.skipBytes(globalLayerMaskLength - 17); } @Override @@ -69,13 +63,20 @@ final class PSDGlobalLayerMask { StringBuilder builder = new StringBuilder(getClass().getSimpleName()); builder.append("["); builder.append("color space: 0x").append(Integer.toHexString(colorSpace)); - builder.append(", colors: [0x").append(Integer.toHexString(color1)); - builder.append(", 0x").append(Integer.toHexString(color2)); - builder.append(", 0x").append(Integer.toHexString(color3)); - builder.append(", 0x").append(Integer.toHexString(color4)); + builder.append(", colors: ["); + + for (int i = 0; i < colors.length; i++) { + if (i > 0) { + builder.append(", "); + } + + builder.append("0x").append(Integer.toHexString(colors[i])); + } + builder.append("], opacity: ").append(opacity); builder.append(", kind: ").append(kind); builder.append("]"); + return builder.toString(); } } diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java index 7fa2b9e1..ea1d40d3 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDImageReader.java @@ -54,6 +54,7 @@ import java.util.List; /** * ImageReader for Adobe Photoshop Document (PSD) format. * + * @see Adobe Photoshop File Formats Specification * @see Adobe Photoshop File Format Summary * @author Harald Kuhr * @author last modified by $Author: haraldk$ @@ -593,6 +594,7 @@ public final class PSDImageReader extends ImageReaderBase { if (abortRequested()) { break; } + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } @@ -654,6 +656,7 @@ public final class PSDImageReader extends ImageReaderBase { if (abortRequested()) { break; } + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } @@ -712,6 +715,7 @@ public final class PSDImageReader extends ImageReaderBase { if (abortRequested()) { break; } + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } @@ -791,6 +795,7 @@ public final class PSDImageReader extends ImageReaderBase { if (abortRequested()) { break; } + processImageProgressForChannel(pChannel, pChannelCount, y, pChannelHeight); } } @@ -974,10 +979,10 @@ public final class PSDImageReader extends ImageReaderBase { // Global LayerMaskInfo (18 bytes or more..?) // 4 (length), 2 (colorSpace), 8 (4 * 2 byte color components), 2 (opacity %), 1 (kind), variable (pad) - long layerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB! + long globalLayerMaskInfoLength = imageInput.readUnsignedInt(); // NOTE: Not long for PSB! - if (layerMaskInfoLength > 0) { - metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput); + if (globalLayerMaskInfoLength > 0) { + metadata.globalLayerMask = new PSDGlobalLayerMask(imageInput, globalLayerMaskInfoLength); } // TODO: Parse "Additional layer information" @@ -986,12 +991,6 @@ public final class PSDImageReader extends ImageReaderBase { // imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4)); // imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4)); } - -// read = imageInput.getStreamPosition() - pos; -// -// long toSkip = layerAndMaskInfoLength - read; -// System.out.println("toSkip: " + toSkip); -// imageInput.skipBytes(toSkip); } metadata.imageDataStart = metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java index e6035e69..043d23d6 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerBlendMode.java @@ -28,8 +28,8 @@ package com.twelvemonkeys.imageio.plugins.psd; -import javax.imageio.stream.ImageInputStream; import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; import java.io.IOException; /** @@ -43,11 +43,11 @@ final class PSDLayerBlendMode { final int blendMode; final int opacity; // 0-255 final int clipping; // 0: base, 1: non-base - final int flags; + final byte flags; public PSDLayerBlendMode(final ImageInputStream pInput) throws IOException { int blendModeSig = pInput.readInt(); - if (blendModeSig != PSD.RESOURCE_TYPE) { // TODO: Is this really just a resource? + if (blendModeSig != PSD.RESOURCE_TYPE) { throw new IIOException("Illegal PSD Blend Mode signature, expected 8BIM: " + PSDUtil.intToStr(blendModeSig)); } @@ -55,7 +55,7 @@ final class PSDLayerBlendMode { opacity = pInput.readUnsignedByte(); clipping = pInput.readUnsignedByte(); - flags = pInput.readUnsignedByte(); + flags = pInput.readByte(); pInput.readByte(); // Pad } diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java index 5b50196b..1fc8fd82 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerInfo.java @@ -28,8 +28,8 @@ package com.twelvemonkeys.imageio.plugins.psd; -import javax.imageio.stream.ImageInputStream; import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; import java.io.IOException; import java.util.Arrays; @@ -70,10 +70,8 @@ final class PSDLayerInfo { blendMode = new PSDLayerBlendMode(pInput); - // Lenght of layer mask data + // Length of layer mask data long extraDataSize = pInput.readUnsignedInt(); - // TODO: Allow skipping the rest here? - // pInput.skipBytes(extraDataSize); // Layer mask/adjustment layer data int layerMaskDataSize = pInput.readInt(); // May be 0, 20 or 36 bytes... @@ -94,7 +92,6 @@ final class PSDLayerInfo { ranges[i] = new PSDChannelSourceDestinationRange(pInput, (i == 0 ? "Gray" : "Channel " + (i - 1))); } - layerName = PSDUtil.readPascalString(pInput); int layerNameSize = layerName.length() + 1; @@ -106,8 +103,7 @@ final class PSDLayerInfo { layerNameSize += skip; } - // TODO: There's some data skipped here... - // Adjustment layer info etc... + // TODO: Consider reading this: Adjustment layer info etc... pInput.skipBytes(extraDataSize - layerMaskDataSize - 4 - layerBlendingDataSize - 4 - layerNameSize); } diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java index 55782e8c..aa13943c 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDLayerMaskData.java @@ -55,10 +55,11 @@ final class PSDLayerMaskData { private int realBottom; private int realRight; - PSDLayerMaskData(ImageInputStream pInput, int pSize) throws IOException { + PSDLayerMaskData(final ImageInputStream pInput, final int pSize) throws IOException { if (pSize != 20 && pSize != 36) { - throw new IIOException("Illegal PSD Layer Mask data size: " + pSize + " (expeced 20 or 36)"); + throw new IIOException("Illegal PSD Layer Mask data size: " + pSize + " (expected 20 or 36)"); } + top = pInput.readInt(); left = pInput.readInt(); bottom = pInput.readInt(); diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 1f6bbffd..2ca29956 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -52,7 +52,6 @@ import java.util.List; */ 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"; static final String NATIVE_METADATA_FORMAT_CLASS_NAME = "com.twelvemonkeys.imageio.plugins.psd.PSDMetadataFormat"; // TODO: Support TIFF metadata, based on EXIF/XMP + merge in PSD specifics @@ -93,7 +92,7 @@ public final class PSDMetadata extends AbstractMetadata { static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"}; protected PSDMetadata() { - // TODO: Allow XMP, EXIF and IPTC as extra formats? + // TODO: Allow XMP, EXIF (TIFF) and IPTC as extra formats? super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); } @@ -112,7 +111,15 @@ public final class PSDMetadata extends AbstractMetadata { if (imageResources != null && !imageResources.isEmpty()) { root.appendChild(createImageResourcesNode()); } - + + if (layerInfo != null && !layerInfo.isEmpty()) { + root.appendChild(createLayerInfoNode()); + } + + if (globalLayerMask != null) { + root.appendChild(createGlobalLayerMaskNode()); + } + return root; } @@ -291,7 +298,7 @@ public final class PSDMetadata extends AbstractMetadata { PSDEXIF1Data exif = (PSDEXIF1Data) imageResource; node = new IIOMetadataNode("DirectoryResource"); - node.setAttribute("type", "EXIF"); + node.setAttribute("type", "TIFF"); // TODO: Set byte[] data instead node.setUserObject(exif.directory); @@ -326,10 +333,6 @@ public final class PSDMetadata extends AbstractMetadata { resource.appendChild(node); } - // TODO: Layers and layer info - - // TODO: Global mask etc.. - return resource; } @@ -363,6 +366,86 @@ public final class PSDMetadata extends AbstractMetadata { } } + private Node createLayerInfoNode() { + IIOMetadataNode layers = new IIOMetadataNode("Layers"); + IIOMetadataNode node; + + + for (PSDLayerInfo psdLayerInfo : layerInfo) { + // TODO: Group in layer and use sub node for blend mode? + node = new IIOMetadataNode("LayerInfo"); + node.setAttribute("name", psdLayerInfo.layerName); + node.setAttribute("top", String.valueOf(psdLayerInfo.top)); + node.setAttribute("left", String.valueOf(psdLayerInfo.left)); + node.setAttribute("bottom", String.valueOf(psdLayerInfo.bottom)); + node.setAttribute("right", String.valueOf(psdLayerInfo.right)); + + node.setAttribute("blendMode", PSDUtil.intToStr(psdLayerInfo.blendMode.blendMode)); + node.setAttribute("opacity", String.valueOf(psdLayerInfo.blendMode.opacity)); // 0-255 + node.setAttribute("clipping", getClippingValue(psdLayerInfo.blendMode.clipping)); // Enum: 0: Base, 1: Non-base, n: unknown + node.setAttribute("flags", String.valueOf(psdLayerInfo.blendMode.flags)); + + if ((psdLayerInfo.blendMode.flags & 0x01) != 0) { + node.setAttribute("transparencyProtected", "true"); + } + if ((psdLayerInfo.blendMode.flags & 0x02) != 0) { + node.setAttribute("visible", "true"); + } + if ((psdLayerInfo.blendMode.flags & 0x04) != 0) { + node.setAttribute("obsolete", "true"); + } + if ((psdLayerInfo.blendMode.flags & 0x08) != 0 && (psdLayerInfo.blendMode.flags & 0x10) != 0) { + node.setAttribute("pixelDataIrrelevant", "true"); + } + + // Skip channelInfo + // Skip layerMaskData + // TODO: Consider adding psdLayerInfo.ranges as it may be useful for composing + + layers.appendChild(node); + } + + return layers; + } + + private String getClippingValue(final int clipping) { + switch (clipping) { + case 0: + return "base"; + case 1: + return "non-base"; + default: + } + + return String.valueOf(clipping); + } + + private Node createGlobalLayerMaskNode() { + IIOMetadataNode globalLayerMaskNode = new IIOMetadataNode("GlobalLayerMask"); + + globalLayerMaskNode.setAttribute("colorSpace", String.valueOf(globalLayerMask.colorSpace)); + globalLayerMaskNode.setAttribute("colors", toListString(globalLayerMask.colors)); + globalLayerMaskNode.setAttribute("opacity", String.valueOf(globalLayerMask.opacity)); // 0-100 + globalLayerMaskNode.setAttribute("kind", getGlobalLayerMaskKind(globalLayerMask.kind)); // (selected|protected|layer) + + return globalLayerMaskNode; + } + + private String getGlobalLayerMaskKind(final int kind) { + switch (kind) { + case 0: + return "selected"; + case 1: + return "protected"; + case 80: // Spec says 128 (which is 0x80, probably a bug in the implementation...) + case 0x80: + return "layer"; + default: + } + + return String.valueOf(kind); + } + /// Standard format support @Override @@ -620,7 +703,7 @@ public final class PSDMetadata extends AbstractMetadata { // TODO: Reader/writer (PSDVersionInfo) while (textResources.hasNext()) { - PSDImageResource textResource = textResources.next(); + final PSDImageResource textResource = textResources.next(); if (textResource instanceof PSDIPTCData) { PSDIPTCData iptc = (PSDIPTCData) textResource; @@ -630,6 +713,10 @@ public final class PSDMetadata extends AbstractMetadata { Integer tagId = (Integer) pEntry.getIdentifier(); switch (tagId) { + // Older PSD versions have only these, map to TIFF counterparts + case IPTC.TAG_CAPTION: // Use if TIFF.TAG_IMAGE_DESCRIPTION is missing + case IPTC.TAG_BY_LINE: // Use if TIFF.TAG_ARTIST is missing + case IPTC.TAG_COPYRIGHT_NOTICE: // Use if TIFF.TAG_COPYRIGHT is missing case IPTC.TAG_SOURCE: return true; default: @@ -649,6 +736,7 @@ public final class PSDMetadata extends AbstractMetadata { case TIFF.TAG_SOFTWARE: case TIFF.TAG_ARTIST: case TIFF.TAG_COPYRIGHT: + case TIFF.TAG_IMAGE_DESCRIPTION: return true; default: return false; diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java index c6df5c3e..50e99677 100755 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadataFormat.java @@ -35,6 +35,7 @@ import javax.imageio.ImageTypeSpecifier; import javax.imageio.metadata.IIOMetadataFormatImpl; import java.awt.image.BufferedImage; import java.util.Arrays; +import java.util.List; /** * PSDMetadataFormat @@ -47,6 +48,19 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { private static final PSDMetadataFormat instance = new PSDMetadataFormat(); + static final List PSD_BLEND_MODES = Arrays.asList( + PSDUtil.intToStr(PSD.BLEND_PASS), PSDUtil.intToStr(PSD.BLEND_NORM), PSDUtil.intToStr(PSD.BLEND_DISS), + PSDUtil.intToStr(PSD.BLEND_DARK), PSDUtil.intToStr(PSD.BLEND_MUL), PSDUtil.intToStr(PSD.BLEND_IDIV), + PSDUtil.intToStr(PSD.BLEND_LBRN), PSDUtil.intToStr(PSD.BLEND_DKCL), PSDUtil.intToStr(PSD.BLEND_LITE), + PSDUtil.intToStr(PSD.BLEND_SCRN), PSDUtil.intToStr(PSD.BLEND_DIV), PSDUtil.intToStr(PSD.BLEND_LDDG), + PSDUtil.intToStr(PSD.BLEND_LGCL), PSDUtil.intToStr(PSD.BLEND_OVER), PSDUtil.intToStr(PSD.BLEND_SLIT), + PSDUtil.intToStr(PSD.BLEND_HLIT), PSDUtil.intToStr(PSD.BLEND_VLIT), PSDUtil.intToStr(PSD.BLEND_LLIT), + PSDUtil.intToStr(PSD.BLEND_PLIT), PSDUtil.intToStr(PSD.BLEND_HMIX), PSDUtil.intToStr(PSD.BLEND_DIFF), + PSDUtil.intToStr(PSD.BLEND_SMUD), PSDUtil.intToStr(PSD.BLEND_FSUB), PSDUtil.intToStr(PSD.BLEND_FDIV), + PSDUtil.intToStr(PSD.BLEND_HUE), PSDUtil.intToStr(PSD.BLEND_SAT), PSDUtil.intToStr(PSD.BLEND_COLR), + PSDUtil.intToStr(PSD.BLEND_LUM) + ); + /** * Private constructor. *

@@ -214,14 +228,36 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl { // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) addObjectValue("XMP", Document.class, true, null); - // TODO: Layers - //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); + // root -> Layers + addElement("Layers", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_REPEAT); - // TODO: Global layer mask info + // root -> Layers -> LayerInfo + addElement("LayerInfo", "Layers", CHILD_POLICY_REPEAT); + addAttribute("LayerInfo", "name", DATATYPE_STRING, false, ""); + addAttribute("LayerInfo", "top", DATATYPE_INTEGER, false, "0"); + addAttribute("LayerInfo", "left", DATATYPE_INTEGER, false, "0"); + addAttribute("LayerInfo", "bottom", DATATYPE_INTEGER, false, "0"); + addAttribute("LayerInfo", "right", DATATYPE_INTEGER, false, "0"); + addAttribute("LayerInfo", "blendmode", DATATYPE_STRING, false, PSDUtil.intToStr(PSD.BLEND_NORM), PSD_BLEND_MODES); + addAttribute("LayerInfo", "opacity", DATATYPE_INTEGER, false, "0"); + addAttribute("LayerInfo", "clipping", DATATYPE_STRING, false, "base", Arrays.asList("base", "non-base")); + addAttribute("LayerInfo", "flags", DATATYPE_INTEGER, false, "0"); + + // Redundant (derived from flags) + addAttribute("LayerInfo", "transparencyProtected", DATATYPE_BOOLEAN, false, "false"); + addAttribute("LayerInfo", "visible", DATATYPE_BOOLEAN, false, "false"); + addAttribute("LayerInfo", "obsolete", DATATYPE_BOOLEAN, false, "false"); // Spec doesn't say if the flag is obsolete, or if this means the layer is obsolete...? + addAttribute("LayerInfo", "pixelDataIrrelevant", DATATYPE_BOOLEAN, false, "false"); + + + // root -> GlobalLayerMask + addElement("GlobalLayerMask", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); + addAttribute("GlobalLayerMask", "colorSpace", DATATYPE_INTEGER, false, "0"); + addAttribute("GlobalLayerMask", "colors", DATATYPE_INTEGER, false, 4, 4); + addAttribute("GlobalLayerMask", "opacity", DATATYPE_INTEGER, false, "0"); + addAttribute("GlobalLayerMask", "kind", DATATYPE_STRING, false, "layer", Arrays.asList("selected", "protected", "layer")); } - - @Override public boolean canNodeAppear(final String elementName, final ImageTypeSpecifier imageType) { // TODO: PSDColorData and PaletteEntry only for indexed color model diff --git a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java index 55f0c840..7945f837 100644 --- a/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java +++ b/imageio/imageio-psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDUtil.java @@ -48,13 +48,13 @@ import java.util.zip.ZipInputStream; */ final class PSDUtil { // TODO: Duplicated code from IFF plugin, move to some common util? - static String intToStr(int pChunkId) { + static String intToStr(int value) { return new String( new byte[]{ - (byte) ((pChunkId & 0xff000000) >> 24), - (byte) ((pChunkId & 0x00ff0000) >> 16), - (byte) ((pChunkId & 0x0000ff00) >> 8), - (byte) ((pChunkId & 0x000000ff)) + (byte) ((value & 0xff000000) >> 24), + (byte) ((value & 0x00ff0000) >> 16), + (byte) ((value & 0x0000ff00) >> 8), + (byte) ((value & 0x000000ff)) } ); } @@ -73,7 +73,7 @@ final class PSDUtil { return StringUtil.decode(bytes, 0, bytes.length, "ASCII"); } - // TODO: Proably also useful for PICT reader, move to some common util? + // TODO: Probably also useful for PICT reader, move to some common util? static String readUnicodeString(final DataInput pInput) throws IOException { int length = pInput.readInt(); @@ -92,7 +92,6 @@ final class PSDUtil { } static DataInputStream createZipStream(final ImageInputStream pInput, long pLength) { - //return new DataInputStream(new DecoderStream(IIOUtil.createStreamAdapter(pInput, pLength), new InflateDecoder())); return new DataInputStream(new ZipInputStream(IIOUtil.createStreamAdapter(pInput, pLength))); }