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
This commit is contained in:
Harald Kuhr 2009-11-07 06:54:48 +08:00 committed by Erlend Hamnaberg
parent b5f6c96583
commit 0d41db32cf
15 changed files with 447 additions and 159 deletions

View File

@ -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) */

View File

@ -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<String>();
long left = mSize;
while (left > 0) {
String name = PSDUtil.readPascalStringByte(pInput);
String name = PSDUtil.readPascalString(pInput);
mNames.add(name);
left -= name.length() + 1;
}

View File

@ -41,6 +41,21 @@ import java.io.IOException;
*/
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)*/
//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);

View File

@ -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<Entry> mEntries = new ArrayList<Entry>();
static class Directory implements Iterable<Entry> {
private List<Entry> mEntries = new ArrayList<Entry>();
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<Entry> 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 = {

View File

@ -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) + ")");

View File

@ -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<PSDImageResource> mImageResources;
private PSDGlobalLayerMask mGlobalLayerMask;
private List<PSDLayerInfo> mLayerInfo;
// private PSDColorData mColorData;
// private List<PSDImageResource> mImageResources;
// private PSDGlobalLayerMask mGlobalLayerMask;
// private List<PSDLayerInfo> 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<PSDImageResource>();
if (mMetadata.mImageResources == null) {
mMetadata.mImageResources = new ArrayList<PSDImageResource>();
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<String> 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<PSDThumbnail> 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<PSDThumbnail>();
@ -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)) {

View File

@ -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:

View File

@ -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...

View File

@ -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<PSDImageResource> mImageResources;
PSDGlobalLayerMask mGlobalLayerMask;
List<PSDLayerInfo> 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<PSDResolutionInfo> resolutionInfos = getResources(PSDResolutionInfo.class);
if (!resolutionInfos.isEmpty()) {
PSDResolutionInfo resolutionInfo = resolutionInfos.get(0);
Iterator<PSDResolutionInfo> 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,32 +467,50 @@ 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<PSDEXIF1Data> 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<PSDImageResource> textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class);
while (textResources.hasNext()) {
PSDImageResource textResource = textResources.next();
}
// int numEntries = tEXt_keyword.size() +
// iTXt_keyword.size() + zTXt_keyword.size();
// if (numEntries == 0) {
@ -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?
<T extends PSDImageResource> List<T> getResources(final Class<T> pResourceType) {
List<T> filtered = null;
<T extends PSDImageResource> Iterator<T> getResources(final Class<T> pResourceType) {
// NOTE: The cast here is wrong, strictly speaking, but it does not matter...
@SuppressWarnings({"unchecked"})
Iterator<T> iterator = (Iterator<T>) mImageResources.iterator();
for (PSDImageResource resource : mImageResources) {
if (pResourceType.isInstance(resource)) {
if (filtered == null) {
filtered = new ArrayList<T>();
return new FilterIterator<T>(iterator, new FilterIterator.Filter<T>() {
public boolean accept(final T pElement) {
return pResourceType.isInstance(pElement);
}
});
}
Iterator<PSDImageResource> getResources(final Class<? extends PSDImageResource>... pResourceTypes) {
Iterator<PSDImageResource> iterator = mImageResources.iterator();
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
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;
});
}
}

View File

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

View File

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

View File

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

View File

@ -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:

View File

@ -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;
@ -58,22 +60,21 @@ final class PSDUtil {
}
// TODO: Proably also useful for PICT reader, move to some common util?
static String readPascalString(ImageInputStream pInput) throws IOException {
// 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) {

View File

@ -0,0 +1,57 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDVersionInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @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();
}
}