mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-27 00:00:02 -04:00
Work in progress for PSD metadata support:
- Refactored metadata support - Moved standard metadata support (EXIF, IPTC & XMP) to separate module - Changes to PSD metadata implementation
This commit is contained in:
+103
@@ -0,0 +1,103 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AbstractMetadata
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
|
||||
*/
|
||||
abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
|
||||
protected AbstractMetadata(final boolean pStandardFormatSupported,
|
||||
final String pNativeFormatName, final String pNativeFormatClassName,
|
||||
final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
|
||||
super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation returns {@code true}.
|
||||
* Mutable subclasses should override this method.
|
||||
*
|
||||
* @return {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getAsTree(final String pFormatName) {
|
||||
validateFormatName(pFormatName);
|
||||
|
||||
if (pFormatName.equals(nativeMetadataFormatName)) {
|
||||
return getNativeTree();
|
||||
}
|
||||
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
|
||||
// TODO: What about extra formats??
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
||||
assertMutable();
|
||||
|
||||
validateFormatName(pFormatName);
|
||||
|
||||
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
||||
}
|
||||
|
||||
Node node = pRoot.getFirstChild();
|
||||
while (node != null) {
|
||||
// TODO: Merge values from node into this
|
||||
|
||||
// Move to the next sibling
|
||||
node = node.getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
assertMutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that this meta data is mutable.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #isReadOnly()} returns {@code true}.
|
||||
*/
|
||||
protected final void assertMutable() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Node getNativeTree();
|
||||
|
||||
protected final void validateFormatName(final String pFormatName) {
|
||||
String[] metadataFormatNames = getMetadataFormatNames();
|
||||
|
||||
if (metadataFormatNames != null) {
|
||||
for (String metadataFormatName : metadataFormatNames) {
|
||||
if (metadataFormatName.equals(pFormatName)) {
|
||||
return; // Found, we're ok!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
||||
);
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -34,11 +34,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PSDAlhpaChannelInfo
|
||||
* PSDAlphaChannelInfo
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDAlhpaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
|
||||
* @version $Id: PSDAlphaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
|
||||
*/
|
||||
class PSDAlphaChannelInfo extends PSDImageResource {
|
||||
List<String> mNames;
|
||||
@@ -50,6 +50,7 @@ class PSDAlphaChannelInfo extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mNames = new ArrayList<String>();
|
||||
|
||||
long left = mSize;
|
||||
while (left > 0) {
|
||||
String name = PSDUtil.readPascalString(pInput);
|
||||
|
||||
+47
-36
@@ -1,13 +1,11 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
@@ -26,7 +24,8 @@ import java.util.List;
|
||||
*/
|
||||
final class PSDEXIF1Data extends PSDImageResource {
|
||||
// protected byte[] mData;
|
||||
protected Directory mDirectory;
|
||||
// protected Directory mDirectory;
|
||||
protected com.twelvemonkeys.imageio.metadata.Directory mDirectory;
|
||||
|
||||
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
@@ -36,24 +35,25 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// This is in essence an embedded TIFF file.
|
||||
// TODO: Extract TIFF parsing to more general purpose package
|
||||
// TODO: Instead, read the byte data, store for later parsing (or store offset, and read on request)
|
||||
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize));
|
||||
|
||||
byte[] bom = new byte[2];
|
||||
stream.readFully(bom);
|
||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
||||
throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
}
|
||||
|
||||
if (stream.readUnsignedShort() != 42) {
|
||||
throw new IIOException("Wrong TIFF magic in EXIF data.");
|
||||
}
|
||||
|
||||
long directoryOffset = stream.readUnsignedInt();
|
||||
mDirectory = Directory.read(stream, directoryOffset);
|
||||
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
||||
mDirectory = new EXIFReader().read(pInput);
|
||||
// byte[] bom = new byte[2];
|
||||
// stream.readFully(bom);
|
||||
// if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
// stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
// }
|
||||
// else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
||||
// throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
// }
|
||||
//
|
||||
// if (stream.readUnsignedShort() != 42) {
|
||||
// throw new IIOException("Wrong TIFF magic in EXIF data.");
|
||||
// }
|
||||
//
|
||||
// long directoryOffset = stream.readUnsignedInt();
|
||||
//
|
||||
// // Read TIFF directory
|
||||
// mDirectory = Directory.read(stream, directoryOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -78,11 +78,13 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
|
||||
pInput.seek(pOffset);
|
||||
int entryCount = pInput.readUnsignedShort();
|
||||
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
directory.mEntries.add(Entry.read(pInput));
|
||||
}
|
||||
|
||||
long nextOffset = pInput.readUnsignedInt();
|
||||
|
||||
if (nextOffset != 0) {
|
||||
Directory next = Directory.read(pInput, nextOffset);
|
||||
directory.mEntries.addAll(next.mEntries);
|
||||
@@ -91,9 +93,9 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public Entry get(int pTag) {
|
||||
public Entry get(int pTagId) {
|
||||
for (Entry entry : mEntries) {
|
||||
if (entry.mTag == pTag) {
|
||||
if (entry.mTagId == pTagId) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -127,7 +129,7 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
};
|
||||
|
||||
private int mTag;
|
||||
final int mTagId;
|
||||
/*
|
||||
1 = BYTE 8-bit unsigned integer.
|
||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||
@@ -153,19 +155,22 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
private long mValueOffset;
|
||||
private Object mValue;
|
||||
|
||||
private Entry() {}
|
||||
private Entry(int pTagId) {
|
||||
mTagId = pTagId;
|
||||
}
|
||||
|
||||
public static Entry read(final ImageInputStream pInput) throws IOException {
|
||||
Entry entry = new Entry();
|
||||
Entry entry = new Entry(pInput.readUnsignedShort());
|
||||
|
||||
entry.mTag = pInput.readUnsignedShort();
|
||||
entry.mType = pInput.readShort();
|
||||
entry.mCount = pInput.readInt(); // Number of values
|
||||
|
||||
// TODO: Handle other sub-IFDs
|
||||
if (entry.mTag == EXIF_IFD) {
|
||||
// GPS IFD: 0x8825, Interoperability IFD: 0xA005
|
||||
if (entry.mTagId == EXIF_IFD) {
|
||||
long offset = pInput.readUnsignedInt();
|
||||
pInput.mark();
|
||||
|
||||
try {
|
||||
entry.mValue = Directory.read(pInput, offset);
|
||||
}
|
||||
@@ -175,6 +180,7 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
}
|
||||
else {
|
||||
int valueLength = entry.getValueLength();
|
||||
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
entry.readValueInLine(pInput);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
@@ -299,22 +305,21 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String getTypeName() {
|
||||
public final String getTypeName() {
|
||||
if (mType > 0 && mType <= TYPE_NAMES.length) {
|
||||
return TYPE_NAMES[mType - 1];
|
||||
}
|
||||
|
||||
return "Unknown type";
|
||||
}
|
||||
|
||||
// TODO: Tag names!
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("0x%04x: %s (%s, %d)", mTag, getValueAsString(), getTypeName(), mCount);
|
||||
public final Object getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
public final String getValueAsString() {
|
||||
if (mValue instanceof String) {
|
||||
return String.format("\"%s\"", mValue);
|
||||
return String.format("%s", mValue);
|
||||
}
|
||||
|
||||
if (mValue != null && mValue.getClass().isArray()) {
|
||||
@@ -338,5 +343,11 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
|
||||
// TODO: Tag names!
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("0x%04x: %s (%s, %d)", mTagId, mType == 2 ? String.format("\"%s\"", mValue) : getValueAsString(), getTypeName(), mCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-4
@@ -48,17 +48,19 @@ class PSDGlobalLayerMask {
|
||||
final int mKind;
|
||||
|
||||
PSDGlobalLayerMask(final ImageInputStream pInput) throws IOException {
|
||||
mColorSpace = pInput.readUnsignedShort();
|
||||
mColorSpace = pInput.readUnsignedShort(); // Undocumented
|
||||
|
||||
mColor1 = pInput.readUnsignedShort();
|
||||
mColor2 = pInput.readUnsignedShort();
|
||||
mColor3 = pInput.readUnsignedShort();
|
||||
mColor4 = pInput.readUnsignedShort();
|
||||
|
||||
mOpacity = pInput.readUnsignedShort();
|
||||
mOpacity = pInput.readUnsignedShort(); // 0-100
|
||||
|
||||
mKind = pInput.readUnsignedByte(); // 0: Selected (ie inverted), 1: Color protected, 128: Use value stored per layer
|
||||
|
||||
// TODO: Variable: Filler zeros
|
||||
|
||||
mKind = pInput.readUnsignedByte();
|
||||
|
||||
pInput.readByte(); // Pad
|
||||
}
|
||||
|
||||
|
||||
+81
-78
@@ -1,5 +1,6 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
@@ -7,8 +8,13 @@ import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.*;
|
||||
import java.util.*;
|
||||
import java.nio.charset.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PSDIPTCData
|
||||
@@ -20,7 +26,7 @@ import java.util.*;
|
||||
final class PSDIPTCData extends PSDImageResource {
|
||||
// TODO: Refactor to be more like PSDEXIF1Data...
|
||||
// TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s)
|
||||
Directory mDirectory;
|
||||
com.twelvemonkeys.imageio.metadata.Directory mDirectory;
|
||||
|
||||
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
@@ -28,7 +34,8 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mDirectory = Directory.read(pInput, mSize);
|
||||
// Read IPTC directory
|
||||
mDirectory = new IPTCReader().read(pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,17 +47,37 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
}
|
||||
|
||||
static class Entry {
|
||||
private int mTagId;
|
||||
private String mValue;
|
||||
final int mTagId;
|
||||
private Object mValue;
|
||||
|
||||
public Entry(int pTagId, String pValue) {
|
||||
public Entry(final int pTagId, final Object pValue) {
|
||||
mTagId = pTagId;
|
||||
mValue = pValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (mTagId >> 8) + ":" + (mTagId & 0xff) + ": " + mValue;
|
||||
return String.format("%d:%d: %s", mTagId >> 8, mTagId & 0xff, mValue);
|
||||
}
|
||||
|
||||
public final String getTypeName() {
|
||||
// TODO: Should this really look like EXIF?
|
||||
if (mTagId == IPTC.TAG_RECORD_VERSION) {
|
||||
return "SHORT";
|
||||
}
|
||||
else if (mValue instanceof String) {
|
||||
return "ASCII";
|
||||
}
|
||||
|
||||
return "Unknown type";
|
||||
}
|
||||
|
||||
public final String getValueAsString() {
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
|
||||
public final Object getValue() {
|
||||
return mValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +87,7 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
private static final int ENCODING_UTF_8 = 0x1b2547;
|
||||
|
||||
private int mEncoding = ENCODING_UNSPECIFIED;
|
||||
|
||||
final List<Entry> mEntries = new ArrayList<Entry>();
|
||||
|
||||
private Directory() {}
|
||||
@@ -69,6 +97,16 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
return "Directory" + mEntries.toString();
|
||||
}
|
||||
|
||||
public Entry get(int pTagId) {
|
||||
for (Entry entry : mEntries) {
|
||||
if (entry.mTagId == pTagId) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator() {
|
||||
return mEntries.iterator();
|
||||
}
|
||||
@@ -81,43 +119,33 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
// For each tag
|
||||
while (pInput.getStreamPosition() < streamEnd) {
|
||||
// Identifies start of a tag
|
||||
byte b = pInput.readByte();
|
||||
if (b != 0x1c) {
|
||||
throw new IIOException("Corrupt IPTC stream segment");
|
||||
byte marker = pInput.readByte();
|
||||
|
||||
if (marker != 0x1c) {
|
||||
throw new IIOException(String.format("Corrupt IPTC stream segment, found 0x%02x (expected 0x1c)", marker));
|
||||
}
|
||||
|
||||
// We need at least four bytes left to read a tag
|
||||
if (pInput.getStreamPosition() + 4 >= streamEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
int directoryType = pInput.readUnsignedByte();
|
||||
int tagType = pInput.readUnsignedByte();
|
||||
int tagId = pInput.readShort();
|
||||
int tagByteCount = pInput.readUnsignedShort();
|
||||
|
||||
if (pInput.getStreamPosition() + tagByteCount > streamEnd) {
|
||||
throw new IIOException("Data for tag extends beyond end of IPTC segment: " + (tagByteCount + pInput.getStreamPosition() - streamEnd));
|
||||
}
|
||||
|
||||
directory.processTag(pInput, directoryType, tagType, tagByteCount);
|
||||
directory.readEntry(pInput, tagId, tagByteCount);
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
private void processTag(ImageInputStream pInput, int directoryType, int tagType, int tagByteCount) throws IOException {
|
||||
int tagIdentifier = (directoryType << 8) | tagType;
|
||||
private void readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException {
|
||||
Object value = null;
|
||||
|
||||
String str = null;
|
||||
switch (tagIdentifier) {
|
||||
switch (pTagId) {
|
||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
||||
// TODO: Use this encoding!?
|
||||
// TODO: Mapping from ISO 646 to Java supported character sets?
|
||||
// TODO: Move somewhere else?
|
||||
mEncoding = parseEncoding(pInput, tagByteCount);
|
||||
mEncoding = parseEncoding(pInput, pLength);
|
||||
return;
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
// short
|
||||
str = Integer.toString(pInput.readUnsignedShort());
|
||||
// A single unsigned short value
|
||||
value = pInput.readUnsignedShort();
|
||||
break;
|
||||
// case IPTC.TAG_RELEASE_DATE:
|
||||
// case IPTC.TAG_EXPIRATION_DATE:
|
||||
@@ -144,50 +172,26 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
// }
|
||||
//
|
||||
default:
|
||||
// Skip non-Application fields, as they are typically not human readable
|
||||
if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) {
|
||||
pInput.skipBytes(pLength);
|
||||
return;
|
||||
}
|
||||
|
||||
// fall through
|
||||
}
|
||||
|
||||
// Skip non-Application fields, as they are typically not human readable
|
||||
if (directoryType << 8 != IPTC.APPLICATION_RECORD) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have a value, treat it as a string
|
||||
if (str == null) {
|
||||
if (tagByteCount < 1) {
|
||||
str = "(No value)";
|
||||
if (value == null) {
|
||||
if (pLength < 1) {
|
||||
value = "(No value)";
|
||||
}
|
||||
else {
|
||||
str = String.format("\"%s\"", parseString(pInput, tagByteCount));
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
mEntries.add(new Entry(tagIdentifier, str));
|
||||
|
||||
// if (directory.containsTag(tagIdentifier)) {
|
||||
// // TODO: Does that REALLY help for performance?!
|
||||
// // this fancy string[] business avoids using an ArrayList for performance reasons
|
||||
// String[] oldStrings;
|
||||
// String[] newStrings;
|
||||
// try {
|
||||
// oldStrings = directory.getStringArray(tagIdentifier);
|
||||
// }
|
||||
// catch (MetadataException e) {
|
||||
// oldStrings = null;
|
||||
// }
|
||||
// if (oldStrings == null) {
|
||||
// newStrings = new String[1];
|
||||
// }
|
||||
// else {
|
||||
// newStrings = new String[oldStrings.length + 1];
|
||||
// System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length);
|
||||
// }
|
||||
// newStrings[newStrings.length - 1] = str;
|
||||
// directory.setStringArray(tagIdentifier, newStrings);
|
||||
// }
|
||||
// else {
|
||||
// directory.setString(tagIdentifier, str);
|
||||
// }
|
||||
mEntries.add(new Entry(pTagId, value));
|
||||
}
|
||||
|
||||
// private Date getDateForTime(final Directory directory, final int tagIdentifier) {
|
||||
@@ -267,22 +271,23 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
// }
|
||||
|
||||
// TODO: Pass encoding as parameter? Use if specified
|
||||
private String parseString(final ImageInputStream pInput, int length) throws IOException {
|
||||
// NOTE: The IPTC "spec" says ISO 646 or ISO 2022 encoding. UTF-8 contains all 646 characters, but not 2022.
|
||||
private String parseString(final ImageInputStream pInput, final int pLength) throws IOException {
|
||||
byte[] data = new byte[pLength];
|
||||
pInput.readFully(data);
|
||||
|
||||
// NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding.
|
||||
// UTF-8 contains all 646 characters, but not 2022.
|
||||
// This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html
|
||||
// First try to decode using UTF-8 (which seems to be the de-facto standard)
|
||||
String str;
|
||||
Charset charset = Charset.forName("UTF-8");
|
||||
CharsetDecoder decoder = charset.newDecoder();
|
||||
CharBuffer chars;
|
||||
byte[] data = new byte[length];
|
||||
pInput.readFully(data);
|
||||
|
||||
try {
|
||||
// First try to decode using UTF-8 (which seems to be the de-facto standard)
|
||||
// Will fail fast on illegal UTF-8-sequences
|
||||
chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
|
||||
CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.decode(ByteBuffer.wrap(data));
|
||||
str = chars.toString();
|
||||
return chars.toString();
|
||||
}
|
||||
catch (CharacterCodingException notUTF8) {
|
||||
if (mEncoding == ENCODING_UTF_8) {
|
||||
@@ -291,10 +296,8 @@ final class PSDIPTCData extends PSDImageResource {
|
||||
|
||||
// Fall back to use ISO-8859-1
|
||||
// This will not fail, but may may create wrong fallback-characters
|
||||
str = StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
||||
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -62,7 +62,9 @@ import java.util.List;
|
||||
* @version $Id: PSDImageReader.java,v 1.0 Apr 29, 2008 4:45:52 PM haraldk Exp$
|
||||
*/
|
||||
// TODO: Implement ImageIO meta data interface
|
||||
// TODO: API for reading separate layers
|
||||
// TODO: Allow reading the extra alpha channels (index after composite data)
|
||||
// TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0)
|
||||
// TODO: Support for API for reading separate layers (index after composite data, and optional alpha channels)
|
||||
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
|
||||
// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
|
||||
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
|
||||
@@ -1144,11 +1146,12 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||
serializer.setIndentation(" ");
|
||||
serializer.serialize(node, true);
|
||||
System.out.println();
|
||||
|
||||
node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME);
|
||||
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||
// serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||
serializer.serialize(node, true);
|
||||
|
||||
if (imageReader.hasThumbnails(0)) {
|
||||
|
||||
+9
-3
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -62,11 +63,16 @@ class PSDImageResource {
|
||||
}
|
||||
|
||||
mSize = pInput.readUnsignedInt();
|
||||
readData(pInput);
|
||||
long startPos = pInput.getStreamPosition();
|
||||
|
||||
// TODO: Sanity check reading here?
|
||||
readData(new SubImageInputStream(pInput, mSize));
|
||||
|
||||
// Data is even-padded
|
||||
// NOTE: This should never happen, however it's safer to keep it here to
|
||||
if (pInput.getStreamPosition() != startPos + mSize) {
|
||||
pInput.seek(startPos + mSize);
|
||||
}
|
||||
|
||||
// Data is even-padded (word aligned)
|
||||
if (mSize % 2 != 0) {
|
||||
pInput.read();
|
||||
}
|
||||
|
||||
+204
-175
@@ -1,14 +1,13 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.FilterIterator;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
@@ -24,7 +23,7 @@ import java.util.List;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDMetadata.java,v 1.0 Nov 4, 2009 5:28:12 PM haraldk Exp$
|
||||
*/
|
||||
public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
public final class PSDMetadata extends AbstractMetadata {
|
||||
|
||||
// TODO: Decide on image/stream metadata...
|
||||
static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0";
|
||||
@@ -60,98 +59,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
|
||||
static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"};
|
||||
|
||||
|
||||
protected PSDMetadata() {
|
||||
// TODO: Allow XMP, EXIF and IPTC as extra formats?
|
||||
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
// TODO: Extract to abstract metadata impl class?
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getAsTree(final String pFormatName) {
|
||||
validateFormatName(pFormatName);
|
||||
|
||||
if (pFormatName.equals(nativeMetadataFormatName)) {
|
||||
return getNativeTree();
|
||||
}
|
||||
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
||||
// TODO: Extract to abstract metadata impl class?
|
||||
assertMutable();
|
||||
|
||||
validateFormatName(pFormatName);
|
||||
|
||||
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
||||
}
|
||||
|
||||
Node node = pRoot.getFirstChild();
|
||||
while (node != null) {
|
||||
// TODO: Merge values from node into this
|
||||
|
||||
// Move to the next sibling
|
||||
node = node.getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
// TODO: Extract to abstract metadata impl class?
|
||||
assertMutable();
|
||||
|
||||
throw new UnsupportedOperationException("Method reset not implemented"); // TODO: Implement
|
||||
}
|
||||
|
||||
// TODO: Extract to abstract metadata impl class?
|
||||
private void assertMutable() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Extract to abstract metadata impl class?
|
||||
private void validateFormatName(final String pFormatName) {
|
||||
String[] metadataFormatNames = getMetadataFormatNames();
|
||||
|
||||
if (metadataFormatNames != null) {
|
||||
for (String metadataFormatName : metadataFormatNames) {
|
||||
if (metadataFormatName.equals(pFormatName)) {
|
||||
return; // Found, we're ok!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
// TODO: Make it a deep clone
|
||||
try {
|
||||
return super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/// Native format support
|
||||
|
||||
private Node getNativeTree() {
|
||||
@Override
|
||||
protected Node getNativeTree() {
|
||||
IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME);
|
||||
|
||||
root.appendChild(createHeaderNode());
|
||||
@@ -195,6 +111,18 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
// TODO: Format spec
|
||||
node = new IIOMetadataNode("ICCProfile");
|
||||
node.setAttribute("colorSpaceType", JAVA_CS[profile.getProfile().getColorSpaceType()]);
|
||||
//
|
||||
// FastByteArrayOutputStream data = new FastByteArrayOutputStream(0);
|
||||
// EncoderStream base64 = new EncoderStream(data, new Base64Encoder(), true);
|
||||
//
|
||||
// try {
|
||||
// base64.write(profile.getProfile().getData());
|
||||
// }
|
||||
// catch (IOException ignore) {
|
||||
// }
|
||||
//
|
||||
// byte[] bytes = data.toByteArray();
|
||||
// node.setAttribute("data", StringUtil.decode(bytes, 0, bytes.length, "ASCII"));
|
||||
node.setUserObject(profile.getProfile());
|
||||
}
|
||||
else if (imageResource instanceof PSDAlphaChannelInfo) {
|
||||
@@ -215,10 +143,12 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (short color : displayInfo.mColors) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
|
||||
builder.append(Integer.toString(color));
|
||||
}
|
||||
|
||||
@@ -324,30 +254,65 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
// Transcode to XMP? ;-)
|
||||
PSDIPTCData iptc = (PSDIPTCData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("IPTC");
|
||||
node = new IIOMetadataNode("Directory");
|
||||
node.setAttribute("type", "IPTC");
|
||||
node.setUserObject(iptc.mDirectory);
|
||||
|
||||
for (Entry entry : iptc.mDirectory) {
|
||||
IIOMetadataNode tag = new IIOMetadataNode("Entry");
|
||||
tag.setAttribute("tag", String.format("%d:%02d", (Integer) entry.getIdentifier() >> 8, (Integer) entry.getIdentifier() & 0xff));
|
||||
|
||||
String field = entry.getFieldName();
|
||||
if (field != null) {
|
||||
tag.setAttribute("field", String.format("%s", field));
|
||||
}
|
||||
tag.setAttribute("value", entry.getValueAsString());
|
||||
|
||||
String type = entry.getTypeName();
|
||||
if (type != null) {
|
||||
tag.setAttribute("type", type);
|
||||
}
|
||||
node.appendChild(tag);
|
||||
}
|
||||
}
|
||||
else if (imageResource instanceof PSDEXIF1Data) {
|
||||
// TODO: Revise/rethink this...
|
||||
// Transcode to XMP? ;-)
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("EXIF");
|
||||
node = new IIOMetadataNode("Directory");
|
||||
node.setAttribute("type", "EXIF");
|
||||
// TODO: Set byte[] data instead
|
||||
node.setUserObject(exif.mDirectory);
|
||||
|
||||
appendEntries(node, exif.mDirectory);
|
||||
}
|
||||
else if (imageResource instanceof PSDXMPData) {
|
||||
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
||||
// Or maybe use the Directory approach used by IPTC and EXIF..
|
||||
PSDXMPData xmp = (PSDXMPData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("XMP");
|
||||
|
||||
try {
|
||||
// BufferedReader reader = new BufferedReader(xmp.getData());
|
||||
// String line;
|
||||
// while ((line = reader.readLine()) != null) {
|
||||
// System.out.println(line);
|
||||
// }
|
||||
//
|
||||
DocumentBuilder builder;
|
||||
Document document;
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(xmp.getData()));
|
||||
factory.setNamespaceAware(true);
|
||||
builder = factory.newDocumentBuilder();
|
||||
document = builder.parse(new InputSource(xmp.getData()));
|
||||
|
||||
|
||||
// Set the entire XMP document as user data
|
||||
node.setUserObject(document);
|
||||
// node.appendChild(document.getFirstChild());
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
@@ -355,7 +320,13 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
else {
|
||||
// Generic resource..
|
||||
node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId));
|
||||
node = new IIOMetadataNode("ImageResource");
|
||||
String value = PSDImageResource.resourceTypeForId(imageResource.mId);
|
||||
if (!"UnknownResource".equals(value)) {
|
||||
node.setAttribute("name", value);
|
||||
}
|
||||
node.setAttribute("length", String.valueOf(imageResource.mSize));
|
||||
// TODO: Set user object: byte array
|
||||
}
|
||||
|
||||
// TODO: More resources
|
||||
@@ -364,9 +335,36 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
resource.appendChild(node);
|
||||
}
|
||||
|
||||
// TODO: Layers and layer info
|
||||
|
||||
// TODO: Global mask etc..
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void appendEntries(IIOMetadataNode pNode, final Directory pDirectory) {
|
||||
for (Entry entry : pDirectory) {
|
||||
IIOMetadataNode tag = new IIOMetadataNode("Entry");
|
||||
tag.setAttribute("tag", String.format("%s", entry.getIdentifier()));
|
||||
|
||||
String field = entry.getFieldName();
|
||||
if (field != null) {
|
||||
tag.setAttribute("field", String.format("%s", field));
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
appendEntries(tag, (Directory) entry.getValue());
|
||||
tag.setAttribute("type", "Directory");
|
||||
}
|
||||
else {
|
||||
tag.setAttribute("value", entry.getValueAsString());
|
||||
tag.setAttribute("type", entry.getTypeName());
|
||||
}
|
||||
|
||||
pNode.appendChild(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard format support
|
||||
|
||||
@Override
|
||||
@@ -461,7 +459,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
|
||||
private String getMultiChannelCS(short pChannels) {
|
||||
if (pChannels < 16) {
|
||||
return Integer.toHexString(pChannels) + "CLR";
|
||||
return String.format("%xCLR", pChannels);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels");
|
||||
@@ -469,88 +467,101 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
|
||||
IIOMetadataNode compressionNode = new IIOMetadataNode("Compression");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("CompressionTypeName");
|
||||
String compression;
|
||||
|
||||
switch (mCompression) {
|
||||
case PSD.COMPRESSION_NONE:
|
||||
compression = "none";
|
||||
break;
|
||||
case PSD.COMPRESSION_RLE:
|
||||
compression = "packbits";
|
||||
compression = "PackBits";
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||
compression = "zip";
|
||||
compression = "Deflate"; // TODO: ZLib? (TIFF native metadata format specifies both.. :-P)
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
node.setAttribute("value", compression);
|
||||
compression_node.appendChild(node);
|
||||
|
||||
node.setAttribute("value", compression);
|
||||
compressionNode.appendChild(node);
|
||||
|
||||
// TODO: Does it make sense to specify lossless for compression "none"?
|
||||
node = new IIOMetadataNode("Lossless");
|
||||
node.setAttribute("value", "true");
|
||||
compression_node.appendChild(node);
|
||||
compressionNode.appendChild(node);
|
||||
|
||||
return compression_node;
|
||||
return compressionNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode data_node = new IIOMetadataNode("Data");
|
||||
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("PlanarConfiguration");
|
||||
node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec
|
||||
data_node.appendChild(node);
|
||||
dataNode.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("SampleFormat");
|
||||
node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral");
|
||||
data_node.appendChild(node);
|
||||
dataNode.appendChild(node);
|
||||
|
||||
String bitDepth = Integer.toString(mHeader.mBits); // bits per plane
|
||||
|
||||
// TODO: Channels might be 5 for RGB + A + Mask...
|
||||
String[] bps = new String[mHeader.mChannels];
|
||||
Arrays.fill(bps, bitDepth);
|
||||
|
||||
node = new IIOMetadataNode("BitsPerSample");
|
||||
node.setAttribute("value", StringUtil.toCSVString(bps, " "));
|
||||
data_node.appendChild(node);
|
||||
dataNode.appendChild(node);
|
||||
|
||||
// TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed?
|
||||
|
||||
return data_node;
|
||||
return dataNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension_node = new IIOMetadataNode("Dimension");
|
||||
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("PixelAspectRatio");
|
||||
// TODO: This is not incorrect wrt resolution info
|
||||
float ratio = 1f;
|
||||
node.setAttribute("value", Float.toString(ratio));
|
||||
dimension_node.appendChild(node);
|
||||
|
||||
// TODO: This is not correct wrt resolution info
|
||||
float aspect = 1f;
|
||||
|
||||
Iterator<PSDPixelAspectRatio> ratios = getResources(PSDPixelAspectRatio.class);
|
||||
if (ratios.hasNext()) {
|
||||
PSDPixelAspectRatio ratio = ratios.next();
|
||||
aspect = (float) ratio.mAspect;
|
||||
}
|
||||
|
||||
node.setAttribute("value", Float.toString(aspect));
|
||||
dimensionNode.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("ImageOrientation");
|
||||
node.setAttribute("value", "Normal");
|
||||
dimension_node.appendChild(node);
|
||||
dimensionNode.appendChild(node);
|
||||
|
||||
// TODO: If no PSDResolutionInfo, this might still be available in the EXIF data...
|
||||
Iterator<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)));
|
||||
dimension_node.appendChild(node);
|
||||
dimensionNode.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("VerticalPixelSize");
|
||||
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes)));
|
||||
dimension_node.appendChild(node);
|
||||
dimensionNode.appendChild(node);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
@@ -580,7 +591,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
<!-- Data type: Integer -->
|
||||
|
||||
*/
|
||||
return dimension_node;
|
||||
return dimensionNode;
|
||||
}
|
||||
|
||||
private static float asMM(final short pUnit, final float pResolution) {
|
||||
@@ -603,18 +614,18 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
PSDEXIF1Data data = exif.next();
|
||||
|
||||
// Get the EXIF DateTime (aka ModifyDate) tag if present
|
||||
PSDEXIF1Data.Entry dateTime = data.mDirectory.get(0x0132); // TODO: Constant
|
||||
Entry dateTime = data.mDirectory.getEntryById(0x0132); // TODO: Constant
|
||||
if (dateTime != null) {
|
||||
node = new IIOMetadataNode("ImageModificationTime");
|
||||
// Format: "YYYY:MM:DD hh:mm:ss" (with quotes! :-P)
|
||||
node = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime
|
||||
// Format: "YYYY:MM:DD hh:mm:ss"
|
||||
String value = dateTime.getValueAsString();
|
||||
|
||||
node.setAttribute("year", value.substring(1, 5));
|
||||
node.setAttribute("month", value.substring(6, 8));
|
||||
node.setAttribute("day", value.substring(9, 11));
|
||||
node.setAttribute("hour", value.substring(12, 14));
|
||||
node.setAttribute("minute", value.substring(15, 17));
|
||||
node.setAttribute("second", value.substring(18, 20));
|
||||
node.setAttribute("year", value.substring(0, 4));
|
||||
node.setAttribute("month", value.substring(5, 7));
|
||||
node.setAttribute("day", value.substring(8, 10));
|
||||
node.setAttribute("hour", value.substring(11, 13));
|
||||
node.setAttribute("minute", value.substring(14, 16));
|
||||
node.setAttribute("second", value.substring(17, 19));
|
||||
|
||||
document_node.appendChild(node);
|
||||
}
|
||||
@@ -625,61 +636,68 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
// TODO: CaptionDigest?, EXIF, XMP
|
||||
// TODO: TIFF uses
|
||||
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||
// Example: TIFF Software field => /Text/TextEntry@keyword = "Software",
|
||||
// /Text/TextEntry@value = Name and version number of the software package(s) used to create the image.
|
||||
|
||||
Iterator<PSDImageResource> textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class);
|
||||
Iterator<PSDImageResource> textResources = getResources(PSDEXIF1Data.class, PSDIPTCData.class, PSDXMPData.class);
|
||||
|
||||
if (!textResources.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
IIOMetadataNode node;
|
||||
|
||||
// TODO: Alpha channel names? (PSDAlphaChannelInfo/PSDUnicodeAlphaNames)
|
||||
// TODO: Reader/writer (PSDVersionInfo)
|
||||
|
||||
while (textResources.hasNext()) {
|
||||
PSDImageResource textResource = textResources.next();
|
||||
|
||||
}
|
||||
|
||||
// int numEntries = tEXt_keyword.size() +
|
||||
// iTXt_keyword.size() + zTXt_keyword.size();
|
||||
// if (numEntries == 0) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// IIOMetadataNode text_node = new IIOMetadataNode("Text");
|
||||
// IIOMetadataNode node = null; // scratch node
|
||||
//
|
||||
// for (int i = 0; i < tEXt_keyword.size(); i++) {
|
||||
// node = new IIOMetadataNode("TextEntry");
|
||||
// node.setAttribute("keyword", (String)tEXt_keyword.get(i));
|
||||
// node.setAttribute("value", (String)tEXt_text.get(i));
|
||||
// node.setAttribute("encoding", "ISO-8859-1");
|
||||
// node.setAttribute("compression", "none");
|
||||
//
|
||||
// text_node.appendChild(node);
|
||||
// }
|
||||
//
|
||||
// for (int i = 0; i < iTXt_keyword.size(); i++) {
|
||||
// node = new IIOMetadataNode("TextEntry");
|
||||
// node.setAttribute("keyword", iTXt_keyword.get(i));
|
||||
// node.setAttribute("value", iTXt_text.get(i));
|
||||
// node.setAttribute("language",
|
||||
// iTXt_languageTag.get(i));
|
||||
// if (iTXt_compressionFlag.get(i)) {
|
||||
// node.setAttribute("compression", "deflate");
|
||||
// } else {
|
||||
// node.setAttribute("compression", "none");
|
||||
// }
|
||||
//
|
||||
// text_node.appendChild(node);
|
||||
// }
|
||||
//
|
||||
// for (int i = 0; i < zTXt_keyword.size(); i++) {
|
||||
// node = new IIOMetadataNode("TextEntry");
|
||||
// node.setAttribute("keyword", (String)zTXt_keyword.get(i));
|
||||
// node.setAttribute("value", (String)zTXt_text.get(i));
|
||||
// node.setAttribute("compression", "deflate");
|
||||
//
|
||||
// text_node.appendChild(node);
|
||||
// }
|
||||
//
|
||||
// return text_node;
|
||||
return null;
|
||||
if (textResource instanceof PSDIPTCData) {
|
||||
PSDIPTCData iptc = (PSDIPTCData) textResource;
|
||||
|
||||
for (Entry entry : iptc.mDirectory) {
|
||||
node = new IIOMetadataNode("TextEntry");
|
||||
|
||||
if (entry.getValue() instanceof String) {
|
||||
node.setAttribute("keyword", String.format("%s", entry.getFieldName()));
|
||||
node.setAttribute("value", entry.getValueAsString());
|
||||
text.appendChild(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (textResource instanceof PSDEXIF1Data) {
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) textResource;
|
||||
|
||||
// TODO: Use name?
|
||||
appendTextEntriesFlat(text, exif.mDirectory);
|
||||
}
|
||||
else if (textResource instanceof PSDXMPData) {
|
||||
// TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF?
|
||||
PSDXMPData xmp = (PSDXMPData) textResource;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private void appendTextEntriesFlat(IIOMetadataNode pNode, Directory pDirectory) {
|
||||
for (Entry entry : pDirectory) {
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
appendTextEntriesFlat(pNode, (Directory) entry.getValue());
|
||||
}
|
||||
else if (entry.getValue() instanceof String) {
|
||||
IIOMetadataNode tag = new IIOMetadataNode("TextEntry");
|
||||
// TODO: Use name!
|
||||
tag.setAttribute("keyword", String.format("%s", entry.getFieldName()));
|
||||
tag.setAttribute("value", entry.getValueAsString());
|
||||
pNode.appendChild(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -693,7 +711,7 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("Alpha");
|
||||
node.setAttribute("value", hasAlpha() ? "nonpremultipled" : "none"); // TODO: Check spec
|
||||
node.setAttribute("value", hasAlpha() ? "nonpremultiplied" : "none"); // TODO: Check spec
|
||||
transparency_node.appendChild(node);
|
||||
|
||||
return transparency_node;
|
||||
@@ -731,4 +749,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
// TODO: Make it a deep clone
|
||||
try {
|
||||
return super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -44,7 +44,6 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
||||
// columns?
|
||||
addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
|
||||
// TODO: Consider using more readable names?!
|
||||
addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
|
||||
|
||||
/*
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ final class PSDUnicodeAlphaNames extends PSDImageResource {
|
||||
|
||||
long left = mSize;
|
||||
while (left > 0) {
|
||||
String name = PSDUtil.readUTF16String(pInput);
|
||||
String name = PSDUtil.readUnicodeString(pInput);
|
||||
mNames.add(name);
|
||||
left -= name.length() * 2 + 4;
|
||||
}
|
||||
|
||||
+12
-2
@@ -60,17 +60,27 @@ final class PSDUtil {
|
||||
}
|
||||
|
||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||
// TODO: Is this REALLY different from the previous method? Maybe the pad should not be read..
|
||||
static String readPascalString(final DataInput pInput) throws IOException {
|
||||
int length = pInput.readUnsignedByte();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
return StringUtil.decode(bytes, 0, bytes.length, "ASCII");
|
||||
}
|
||||
|
||||
static String readUTF16String(final DataInput pInput) throws IOException {
|
||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||
static String readUnicodeString(final DataInput pInput) throws IOException {
|
||||
int length = pInput.readInt();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length * 2];
|
||||
pInput.readFully(bytes);
|
||||
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ final class PSDVersionInfo extends PSDImageResource {
|
||||
mVersion = pInput.readInt();
|
||||
mHasRealMergedData = pInput.readBoolean();
|
||||
|
||||
mWriter = PSDUtil.readUTF16String(pInput);
|
||||
mReader = PSDUtil.readUTF16String(pInput);
|
||||
mWriter = PSDUtil.readUnicodeString(pInput);
|
||||
mReader = PSDUtil.readUnicodeString(pInput);
|
||||
|
||||
mFileVersion = pInput.readInt();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user