Work in progress for PSD metadata support:

- Changes to native format spec
 - Implemented more of native format
 - Added several more resource type implementations
 - IPTC metadata support
This commit is contained in:
Harald Kuhr 2009-11-08 14:39:32 +01:00
parent bf5c6e9d47
commit 54cf727dee
12 changed files with 821 additions and 59 deletions

View File

@ -225,11 +225,11 @@ interface PSD {
// 03f8 // 03f8
/** Color transfer functions */ /** Color transfer functions */
int RES_COLOR_TRANSFER_FUNCITON = 0x03f8; int RES_COLOR_TRANSFER_FUNCTION = 0x03f8;
// 03f9 // 03f9
/** Duotone transfer functions */ /** Duotone transfer functions */
int RES_DUOTONE_TRANSFER_FUNCITON = 0x03f9; int RES_DUOTONE_TRANSFER_FUNCTOON = 0x03f9;
// 03fa // 03fa
/** Duotone image information */ /** Duotone image information */
@ -385,7 +385,7 @@ interface PSD {
* (Photoshop 5.0) Unicode Alpha Names * (Photoshop 5.0) Unicode Alpha Names
* Unicode string (4 bytes length followed by string). * Unicode string (4 bytes length followed by string).
*/ */
int RES_UNICODE_ALPHA_NAME = 0x0415; int RES_UNICODE_ALPHA_NAMES = 0x0415;
// 1046 // 1046
/** /**

View File

@ -0,0 +1,58 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDGridAndGuideInfo
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDGridAndGuideInfo.java,v 1.0 Nov 7, 2009 8:46:13 PM haraldk Exp$
*/
final class PSDGridAndGuideInfo extends PSDImageResource {
/* Grid & guide header */
//typedef struct {
// guint32 fVersion; /* Version - always 1 for PS */
// guint32 fGridCycleV; /* Vertical grid size */
// guint32 fGridCycleH; /* Horizontal grid size */
// guint32 fGuideCount; /* Number of guides */
//} GuideHeader;
/* Guide resource block */
//typedef struct {
// guint32 fLocation; /* Guide position in Pixels * 100 */
// gchar fDirection; /* Guide orientation */
//} GuideResource;
int mVersion;
int mGridCycleVertical;
int mGridCycleHorizontal;
int mGuideCount;
GuideResource[] mGuides;
PSDGridAndGuideInfo(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mVersion = pInput.readInt();
mGridCycleVertical = pInput.readInt();
mGridCycleHorizontal = pInput.readInt();
mGuideCount = pInput.readInt();
mGuides = new GuideResource[mGuideCount];
for (GuideResource guide : mGuides) {
guide.mLocation = pInput.readInt();
guide.mDirection = pInput.readByte();
}
}
static class GuideResource {
int mLocation;
byte mDirection; // 0: vertical, 1: horizontal
}
}

View File

@ -0,0 +1,418 @@
package com.twelvemonkeys.imageio.plugins.psd;
import com.twelvemonkeys.lang.StringUtil;
import javax.imageio.IIOException;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.*;
import java.util.*;
/**
* PSDIPTCData
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$
*/
final class PSDIPTCData extends PSDImageResource {
// TODO: Refactor to be more like PSDEXIF1Data...
// TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s)
Directory mDirectory;
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mDirectory = Directory.read(pInput, mSize);
}
@Override
public String toString() {
StringBuilder builder = toStringBuilder();
builder.append(", ").append(mDirectory);
builder.append("]");
return builder.toString();
}
static class Entry {
private int mTagId;
private String mValue;
public Entry(int pTagId, String pValue) {
mTagId = pTagId;
mValue = pValue;
}
@Override
public String toString() {
return (mTagId >> 8) + ":" + (mTagId & 0xff) + ": " + mValue;
}
}
static class Directory implements Iterable<Entry> {
private static final int ENCODING_UNKNOWN = -1;
private static final int ENCODING_UNSPECIFIED = 0;
private static final int ENCODING_UTF_8 = 0x1b2547;
private int mEncoding = ENCODING_UNSPECIFIED;
final List<Entry> mEntries = new ArrayList<Entry>();
private Directory() {}
@Override
public String toString() {
return "Directory" + mEntries.toString();
}
public Iterator<Entry> iterator() {
return mEntries.iterator();
}
public static Directory read(final ImageInputStream pInput, final long pSize) throws IOException {
Directory directory = new Directory();
final long streamEnd = pInput.getStreamPosition() + pSize;
// For each tag
while (pInput.getStreamPosition() < streamEnd) {
// Identifies start of a tag
byte b = pInput.readByte();
if (b != 0x1c) {
throw new IIOException("Corrupt IPTC stream segment");
}
// We need at least four bytes left to read a tag
if (pInput.getStreamPosition() + 4 >= streamEnd) {
break;
}
int directoryType = pInput.readUnsignedByte();
int tagType = pInput.readUnsignedByte();
int tagByteCount = pInput.readUnsignedShort();
if (pInput.getStreamPosition() + tagByteCount > streamEnd) {
throw new IIOException("Data for tag extends beyond end of IPTC segment: " + (tagByteCount + pInput.getStreamPosition() - streamEnd));
}
directory.processTag(pInput, directoryType, tagType, tagByteCount);
}
return directory;
}
private void processTag(ImageInputStream pInput, int directoryType, int tagType, int tagByteCount) throws IOException {
int tagIdentifier = (directoryType << 8) | tagType;
String str = null;
switch (tagIdentifier) {
case IPTC.TAG_CODED_CHARACTER_SET:
// TODO: Use this encoding!?
// TODO: Move somewhere else?
mEncoding = parseEncoding(pInput, tagByteCount);
return;
case IPTC.TAG_RECORD_VERSION:
// short
str = Integer.toString(pInput.readUnsignedShort());
break;
// case IPTC.TAG_RELEASE_DATE:
// case IPTC.TAG_EXPIRATION_DATE:
// case IPTC.TAG_REFERENCE_DATE:
// case IPTC.TAG_DATE_CREATED:
// case IPTC.TAG_DIGITAL_CREATION_DATE:
// // Date object
// Date date = parseISO8601DatePart(pInput, tagByteCount);
// if (date != null) {
// directory.setDate(tagIdentifier, date);
// return;
// }
// case IPTC.TAG_RELEASE_TIME:
// case IPTC.TAG_EXPIRATION_TIME:
// case IPTC.TAG_TIME_CREATED:
// case IPTC.TAG_DIGITAL_CREATION_TIME:
// // NOTE: Spec says fields should be sent in order, so this is okay
// date = getDateForTime(directory, tagIdentifier);
//
// Date time = parseISO8601TimePart(pInput, tagByteCount, date);
// if (time != null) {
// directory.setDate(tagIdentifier, time);
// return;
// }
//
default:
// fall through
}
// Skip non-Application fields, as they are typically not human readable
if (directoryType << 8 != IPTC.APPLICATION_RECORD) {
return;
}
// If we don't have a value, treat it as a string
if (str == null) {
if (tagByteCount < 1) {
str = "(No value)";
}
else {
str = String.format("\"%s\"", parseString(pInput, tagByteCount));
}
}
mEntries.add(new Entry(tagIdentifier, str));
// if (directory.containsTag(tagIdentifier)) {
// // TODO: Does that REALLY help for performance?!
// // this fancy string[] business avoids using an ArrayList for performance reasons
// String[] oldStrings;
// String[] newStrings;
// try {
// oldStrings = directory.getStringArray(tagIdentifier);
// }
// catch (MetadataException e) {
// oldStrings = null;
// }
// if (oldStrings == null) {
// newStrings = new String[1];
// }
// else {
// newStrings = new String[oldStrings.length + 1];
// System.arraycopy(oldStrings, 0, newStrings, 0, oldStrings.length);
// }
// newStrings[newStrings.length - 1] = str;
// directory.setStringArray(tagIdentifier, newStrings);
// }
// else {
// directory.setString(tagIdentifier, str);
// }
}
// private Date getDateForTime(final Directory directory, final int tagIdentifier) {
// int dateTag;
//
// switch (tagIdentifier) {
// case IPTC.TAG_RELEASE_TIME:
// dateTag = IPTC.TAG_RELEASE_DATE;
// break;
// case IPTC.TAG_EXPIRATION_TIME:
// dateTag = IPTC.TAG_EXPIRATION_DATE;
// break;
// case IPTC.TAG_TIME_CREATED:
// dateTag = IPTC.TAG_DATE_CREATED;
// break;
// case IPTC.TAG_DIGITAL_CREATION_TIME:
// dateTag = IPTC.TAG_DIGITAL_CREATION_DATE;
// break;
// default:
// return new Date(0l);
// }
//
// return directory.containsTag(dateTag) ? directory.getDate(dateTag) : new Date(0l);
// }
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
return tagByteCount == 3
&& (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8
? ENCODING_UTF_8 : ENCODING_UNKNOWN;
}
// private Date parseISO8601TimePart(final ImageInputStream pInputStream, int tagByteCount, final Date date) throws IOException {
// // ISO 8601: HHMMSS±HHMM
// if (tagByteCount >= 11) {
// String timeStr = parseString(pInputStream, tagByteCount);
// try {
// int hour = Integer.parseInt(timeStr.substring(0, 2));
// int minute = Integer.parseInt(timeStr.substring(2, 4));
// int second = Integer.parseInt(timeStr.substring(4, 6));
// String tzOffset = timeStr.substring(6, 11);
//
// TimeZone zone = new SimpleTimeZone(Integer.parseInt(tzOffset.charAt(0) == '+' ? tzOffset.substring(1) : tzOffset), tzOffset);
//
// GregorianCalendar calendar = new GregorianCalendar(zone);
// calendar.setTime(date);
//
// calendar.add(Calendar.HOUR_OF_DAY, hour);
// calendar.add(Calendar.MINUTE, minute);
// calendar.add(Calendar.SECOND, second);
//
// return calendar.getTime();
// }
// catch (NumberFormatException e) {
// // fall through and we'll store whatever was there as a String
// }
// }
// return null;
// }
//
// private Date parseISO8601DatePart(final ImageInputStream pInputStream, int tagByteCount) throws IOException {
// // ISO 8601: CCYYMMDD
// if (tagByteCount >= 8) {
// String dateStr = parseString(pInputStream, tagByteCount);
// try {
// int year = Integer.parseInt(dateStr.substring(0, 4));
// int month = Integer.parseInt(dateStr.substring(4, 6)) - 1;
// int day = Integer.parseInt(dateStr.substring(6, 8));
// GregorianCalendar calendar = new GregorianCalendar(year, month, day);
// return calendar.getTime();
// }
// catch (NumberFormatException e) {
// // fall through and we'll store whatever was there as a String
// }
// }
// return null;
// }
// TODO: Pass encoding as parameter? Use if specified
private String parseString(final ImageInputStream pInput, int length) throws IOException {
// NOTE: The IPTC "spec" says ISO 646 or ISO 2022 encoding. UTF-8 contains all 646 characters, but not 2022.
// This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html
// First try to decode using UTF-8 (which seems to be the de-facto standard)
String str;
Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer chars;
byte[] data = new byte[length];
pInput.readFully(data);
try {
// Will fail fast on illegal UTF-8-sequences
chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
.onUnmappableCharacter(CodingErrorAction.REPORT)
.decode(ByteBuffer.wrap(data));
str = chars.toString();
}
catch (CharacterCodingException notUTF8) {
if (mEncoding == ENCODING_UTF_8) {
throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8);
}
// Fall back to use ISO-8859-1
// This will not fail, but may may create wrong fallback-characters
str = StringUtil.decode(data, 0, data.length, "ISO8859_1");
}
return str;
}
}
static interface IPTC {
static final int ENVELOPE_RECORD = 1 << 8;
static final int APPLICATION_RECORD = 2 << 8;
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
/** 2:00 Record Version (mandatory) */
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
// /** 2:03 Object Type Reference */
// public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
// /** 2:04 Object Attribute Reference (repeatable) */
// public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
// /** 2:05 Object Name */
// public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
// /** 2:07 Edit Status */
// public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
// /** 2:08 Editorial Update */
// public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
// /** 2:10 Urgency */
// public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
// /** 2:12 Subect Reference (repeatable) */
// public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
// /** 2:15 Category */
// public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
// /** 2:20 Supplemental Category (repeatable) */
// public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
// /** 2:22 Fixture Identifier */
// public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
// /** 2:25 Keywords (repeatable) */
// public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
// /** 2:26 Content Locataion Code (repeatable) */
// public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
// /** 2:27 Content Locataion Name (repeatable) */
// public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
// /** 2:30 Release Date */
// public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
// /** 2:35 Release Time */
// public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
// /** 2:37 Expiration Date */
// public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
// /** 2:38 Expiration Time */
// public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
// /** 2:40 Special Instructions */
// public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
// /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */
// public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
// /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
// public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
// /** 2:47 Reference Date (mandatory if 2:45 present) */
// public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
// /** 2:50 Reference Number (mandatory if 2:45 present) */
// public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
// /** 2:55 Date Created */
// public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
// /** 2:60 Time Created */
// public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
// /** 2:62 Digital Creation Date */
// public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
// /** 2:63 Digital Creation Date */
// public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
// /** 2:65 Originating Program */
// public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
// /** 2:70 Program Version (only valid if 2:65 present) */
// public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
// /** 2:75 Object Cycle (a: morning, p: evening, b: both) */
// public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
// /** 2:80 By-line (repeatable) */
// public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
// /** 2:85 By-line Title (repeatable) */
// public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
// /** 2:90 City */
// public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
// /** 2:92 Sub-location */
// public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
// /** 2:95 Province/State */
// public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
// /** 2:100 Country/Primary Location Code */
// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
// /** 2:101 Country/Primary Location Name */
// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
// /** 2:103 Original Transmission Reference */
// public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
// /** 2:105 Headline */
// public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
// /** 2:110 Credit */
// public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
// /** 2:115 Source */
// public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
// /** 2:116 Copyright Notice */
// public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
// /** 2:118 Contact */
// public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
// /** 2:120 Catption/Abstract */
// public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
// /** 2:122 Writer/Editor (repeatable) */
// public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
// /** 2:125 Rasterized Caption (binary data) */
// public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
// /** 2:130 Image Type */
// public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
// /** 2:131 Image Orientation */
// public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
// /** 2:135 Language Identifier */
// public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
//
// // TODO: Should we expose this field?
// /**
// * 2:199 JobMinder Assignment Data (Custom IPTC field).
// * A common custom IPTC field used by a now discontinued application called JobMinder.
// *
// * @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
// */
// static final int CUSTOM_TAG_JOBMINDER_ASSIGMENT_DATA = APPLICATION_RECORD | 199;
//
// // TODO: 2:150-2:154 Audio and 2:200-2:202 Object Preview Data
}
}

View File

@ -43,6 +43,9 @@ import java.lang.reflect.Field;
* @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$ * @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$
*/ */
class PSDImageResource { class PSDImageResource {
// TODO: Refactor image resources to separate package
// TODO: Change constructor to store stream offset and length only (+ possibly the name), defer reading
final short mId; final short mId;
final String mName; final String mName;
final long mSize; final long mSize;
@ -61,6 +64,8 @@ class PSDImageResource {
mSize = pInput.readUnsignedInt(); mSize = pInput.readUnsignedInt();
readData(pInput); readData(pInput);
// TODO: Sanity check reading here?
// Data is even-padded // Data is even-padded
if (mSize % 2 != 0) { if (mSize % 2 != 0) {
pInput.read(); pInput.read();
@ -114,6 +119,8 @@ class PSDImageResource {
case PSD.RES_ALPHA_CHANNEL_INFO: case PSD.RES_ALPHA_CHANNEL_INFO:
case PSD.RES_DISPLAY_INFO: case PSD.RES_DISPLAY_INFO:
case PSD.RES_PRINT_FLAGS: case PSD.RES_PRINT_FLAGS:
case PSD.RES_IPTC_NAA:
case PSD.RES_GRID_AND_GUIDES_INFO:
case PSD.RES_THUMBNAIL_PS4: case PSD.RES_THUMBNAIL_PS4:
case PSD.RES_THUMBNAIL: case PSD.RES_THUMBNAIL:
case PSD.RES_ICC_PROFILE: case PSD.RES_ICC_PROFILE:
@ -121,6 +128,8 @@ class PSDImageResource {
case PSD.RES_EXIF_DATA_1: case PSD.RES_EXIF_DATA_1:
// case PSD.RES_EXIF_DATA_3: // case PSD.RES_EXIF_DATA_3:
case PSD.RES_XMP_DATA: case PSD.RES_XMP_DATA:
case PSD.RES_PRINT_SCALE:
case PSD.RES_PIXEL_ASPECT_RATIO:
case PSD.RES_PRINT_FLAGS_INFORMATION: case PSD.RES_PRINT_FLAGS_INFORMATION:
return null; return null;
default: default:
@ -135,7 +144,7 @@ class PSDImageResource {
catch (IllegalAccessException ignore) { catch (IllegalAccessException ignore) {
} }
return "unknown resource"; return "UnknownResource";
} }
} }
@ -157,17 +166,27 @@ class PSDImageResource {
return new PSDDisplayInfo(id, pInput); return new PSDDisplayInfo(id, pInput);
case PSD.RES_PRINT_FLAGS: case PSD.RES_PRINT_FLAGS:
return new PSDPrintFlags(id, pInput); return new PSDPrintFlags(id, pInput);
case PSD.RES_IPTC_NAA:
return new PSDIPTCData(id, pInput);
case PSD.RES_GRID_AND_GUIDES_INFO:
return new PSDGridAndGuideInfo(id, pInput);
case PSD.RES_THUMBNAIL_PS4: case PSD.RES_THUMBNAIL_PS4:
case PSD.RES_THUMBNAIL: case PSD.RES_THUMBNAIL:
return new PSDThumbnail(id, pInput); return new PSDThumbnail(id, pInput);
case PSD.RES_ICC_PROFILE: case PSD.RES_ICC_PROFILE:
return new ICCProfile(id, pInput); return new ICCProfile(id, pInput);
case PSD.RES_UNICODE_ALPHA_NAMES:
return new PSDUnicodeAlphaNames(id, pInput);
case PSD.RES_VERSION_INFO: case PSD.RES_VERSION_INFO:
return new PSDVersionInfo(id, pInput); return new PSDVersionInfo(id, pInput);
case PSD.RES_EXIF_DATA_1: case PSD.RES_EXIF_DATA_1:
return new PSDEXIF1Data(id, pInput); return new PSDEXIF1Data(id, pInput);
case PSD.RES_XMP_DATA: case PSD.RES_XMP_DATA:
return new PSDXMPData(id, pInput); return new PSDXMPData(id, pInput);
case PSD.RES_PRINT_SCALE:
return new PSDPrintScale(id, pInput);
case PSD.RES_PIXEL_ASPECT_RATIO:
return new PSDPixelAspectRatio(id, pInput);
case PSD.RES_PRINT_FLAGS_INFORMATION: case PSD.RES_PRINT_FLAGS_INFORMATION:
return new PSDPrintFlagsInformation(id, pInput); return new PSDPrintFlagsInformation(id, pInput);
default: default:

View File

@ -43,11 +43,24 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
static final String[] DISPLAY_INFO_CS = { static final String[] DISPLAY_INFO_CS = {
"RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC", "RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC",
null, // ... (until index 2999), null, // TODO: ... (until index 2999),
"ANPA" "ANPA"
}; };
static final String[] DISPLAY_INFO_KINDS = {"selected", "protected"}; static final String[] DISPLAY_INFO_KINDS = {"selected", "protected"};
static final String[] RESOLUTION_UNITS = {null, "pixels/inch", "pixels/cm"};
static final String[] DIMENSION_UNITS = {null, "in", "cm", "pt", "picas", "columns"};
static final String[] JAVA_CS = {
"XYZ", "Lab", "Yuv", "YCbCr", "Yxy", "RGB", "GRAY", "HSV", "HLS", "CMYK", "CMY",
"2CLR", "3CLR", "4CLR", "5CLR", "6CLR", "7CLR", "8CLR", "9CLR", "ACLR", "BCLR", "CCLR", "DCLR", "ECLR", "FCLR"
};
static final String[] GUIDE_ORIENTATIONS = {"vertical", "horizontal"};
static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"};
protected PSDMetadata() { protected PSDMetadata() {
// TODO: Allow XMP, EXIF and IPTC as extra formats? // TODO: Allow XMP, EXIF and IPTC as extra formats?
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null); super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
@ -155,8 +168,9 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
} }
private Node createHeaderNode() { private Node createHeaderNode() {
IIOMetadataNode header = new IIOMetadataNode("PSDHeader"); IIOMetadataNode header = new IIOMetadataNode("Header");
header.setAttribute("type", "PSD");
header.setAttribute("version", "1"); header.setAttribute("version", "1");
header.setAttribute("channels", Integer.toString(mHeader.mChannels)); header.setAttribute("channels", Integer.toString(mHeader.mChannels));
header.setAttribute("height", Integer.toString(mHeader.mHeight)); header.setAttribute("height", Integer.toString(mHeader.mHeight));
@ -175,7 +189,15 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
// TODO: Always add name (if set) and id (as resourceId) to all nodes? // 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.. // Resource Id is useful for people with access to the PSD spec..
if (imageResource instanceof PSDAlphaChannelInfo) { if (imageResource instanceof ICCProfile) {
ICCProfile profile = (ICCProfile) imageResource;
// TODO: Format spec
node = new IIOMetadataNode("ICCProfile");
node.setAttribute("colorSpaceType", JAVA_CS[profile.getProfile().getColorSpaceType()]);
node.setUserObject(profile.getProfile());
}
else if (imageResource instanceof PSDAlphaChannelInfo) {
PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource; PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource;
node = new IIOMetadataNode("AlphaChannelInfo"); node = new IIOMetadataNode("AlphaChannelInfo");
@ -185,8 +207,6 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
nameNode.setAttribute("value", name); nameNode.setAttribute("value", name);
node.appendChild(nameNode); node.appendChild(nameNode);
} }
resource.appendChild(node);
} }
else if (imageResource instanceof PSDDisplayInfo) { else if (imageResource instanceof PSDDisplayInfo) {
PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource; PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource;
@ -205,14 +225,121 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
node.setAttribute("colors", builder.toString()); node.setAttribute("colors", builder.toString());
node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity)); node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity));
node.setAttribute("kind", DISPLAY_INFO_KINDS[displayInfo.mKind]); node.setAttribute("kind", DISPLAY_INFO_KINDS[displayInfo.mKind]);
}
else if (imageResource instanceof PSDGridAndGuideInfo) {
PSDGridAndGuideInfo info = (PSDGridAndGuideInfo) imageResource;
resource.appendChild(node); node = new IIOMetadataNode("GridAndGuideInfo");
node.setAttribute("version", String.valueOf(info.mVersion));
node.setAttribute("verticalGridCycle", String.valueOf(info.mGridCycleVertical));
node.setAttribute("horizontalGridCycle", String.valueOf(info.mGridCycleHorizontal));
for (PSDGridAndGuideInfo.GuideResource guide : info.mGuides) {
IIOMetadataNode guideNode = new IIOMetadataNode("Guide");
guideNode.setAttribute("location", Integer.toString(guide.mLocation));
guideNode.setAttribute("orientation", GUIDE_ORIENTATIONS[guide.mDirection]);
}
}
else if (imageResource instanceof PSDPixelAspectRatio) {
PSDPixelAspectRatio aspectRatio = (PSDPixelAspectRatio) imageResource;
node = new IIOMetadataNode("PixelAspectRatio");
node.setAttribute("version", String.valueOf(aspectRatio.mVersion));
node.setAttribute("aspectRatio", String.valueOf(aspectRatio.mAspect));
}
else if (imageResource instanceof PSDPrintFlags) {
PSDPrintFlags flags = (PSDPrintFlags) imageResource;
node = new IIOMetadataNode("PrintFlags");
node.setAttribute("labels", String.valueOf(flags.mLabels));
node.setAttribute("cropMarks", String.valueOf(flags.mCropMasks));
node.setAttribute("colorBars", String.valueOf(flags.mColorBars));
node.setAttribute("registrationMarks", String.valueOf(flags.mRegistrationMarks));
node.setAttribute("negative", String.valueOf(flags.mNegative));
node.setAttribute("flip", String.valueOf(flags.mFlip));
node.setAttribute("interpolate", String.valueOf(flags.mInterpolate));
node.setAttribute("caption", String.valueOf(flags.mCaption));
}
else if (imageResource instanceof PSDPrintFlagsInformation) {
PSDPrintFlagsInformation information = (PSDPrintFlagsInformation) imageResource;
node = new IIOMetadataNode("PrintFlagsInformation");
node.setAttribute("version", String.valueOf(information.mVersion));
node.setAttribute("cropMarks", String.valueOf(information.mCropMasks));
node.setAttribute("field", String.valueOf(information.mField));
node.setAttribute("bleedWidth", String.valueOf(information.mBleedWidth));
node.setAttribute("bleedScale", String.valueOf(information.mBleedScale));
}
else if (imageResource instanceof PSDPrintScale) {
PSDPrintScale printScale = (PSDPrintScale) imageResource;
node = new IIOMetadataNode("PrintScale");
node.setAttribute("style", PRINT_SCALE_STYLES[printScale.mStyle]);
node.setAttribute("xLocation", String.valueOf(printScale.mXLocation));
node.setAttribute("yLocation", String.valueOf(printScale.mYlocation));
node.setAttribute("scale", String.valueOf(printScale.mScale));
}
else if (imageResource instanceof PSDResolutionInfo) {
PSDResolutionInfo information = (PSDResolutionInfo) imageResource;
node = new IIOMetadataNode("ResolutionInfo");
node.setAttribute("horizontalResolution", String.valueOf(information.mHRes));
node.setAttribute("horizontalResolutionUnit", RESOLUTION_UNITS[information.mHResUnit]);
node.setAttribute("widthUnit", DIMENSION_UNITS[information.mWidthUnit]);
node.setAttribute("verticalResolution", String.valueOf(information.mVRes));
node.setAttribute("verticalResolutionUnit", RESOLUTION_UNITS[information.mVResUnit]);
node.setAttribute("heightUnit", DIMENSION_UNITS[information.mHeightUnit]);
}
else if (imageResource instanceof PSDUnicodeAlphaNames) {
PSDUnicodeAlphaNames alphaNames = (PSDUnicodeAlphaNames) imageResource;
node = new IIOMetadataNode("UnicodeAlphaNames");
for (String name : alphaNames.mNames) {
IIOMetadataNode nameNode = new IIOMetadataNode("Name");
nameNode.setAttribute("value", name);
node.appendChild(nameNode);
}
}
else if (imageResource instanceof PSDVersionInfo) {
PSDVersionInfo information = (PSDVersionInfo) imageResource;
node = new IIOMetadataNode("VersionInfo");
node.setAttribute("version", String.valueOf(information.mVersion));
node.setAttribute("hasRealMergedData", String.valueOf(information.mHasRealMergedData));
node.setAttribute("writer", information.mWriter);
node.setAttribute("reader", information.mReader);
node.setAttribute("fileVersion", String.valueOf(information.mFileVersion));
}
else if (imageResource instanceof PSDThumbnail) {
// TODO: Revise/rethink this...
PSDThumbnail thumbnail = (PSDThumbnail) imageResource;
node = new IIOMetadataNode("Thumbnail");
// TODO: Thumbnail attributes + access to data, to avoid JPEG re-compression problems
node.setUserObject(thumbnail.getThumbnail());
}
else if (imageResource instanceof PSDIPTCData) {
// TODO: Revise/rethink this...
// Transcode to XMP? ;-)
PSDIPTCData iptc = (PSDIPTCData) imageResource;
node = new IIOMetadataNode("IPTC");
node.setUserObject(iptc.mDirectory);
}
else if (imageResource instanceof PSDEXIF1Data) {
// TODO: Revise/rethink this...
// Transcode to XMP? ;-)
PSDEXIF1Data exif = (PSDEXIF1Data) imageResource;
node = new IIOMetadataNode("EXIF");
node.setUserObject(exif.mDirectory);
} }
else if (imageResource instanceof PSDXMPData) { else if (imageResource instanceof PSDXMPData) {
// TODO: Revise/rethink this... // TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
PSDXMPData xmp = (PSDXMPData) imageResource; PSDXMPData xmp = (PSDXMPData) imageResource;
node = new IIOMetadataNode("XMPData"); node = new IIOMetadataNode("XMP");
try { try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
@ -225,20 +352,16 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
catch (Exception e) { catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
resource.appendChild(node);
} }
else { else {
// Generic resource.. // Generic resource..
node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId)); node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId));
resource.appendChild(node);
} }
// TODO: More resources // TODO: More resources
node.setAttribute("resourceId", Integer.toHexString(imageResource.mId)); node.setAttribute("resourceId", String.format("0x%04x", imageResource.mId));
resource.appendChild(node);
} }
return resource; return resource;

View File

@ -4,6 +4,7 @@ import org.w3c.dom.Document;
import javax.imageio.ImageTypeSpecifier; import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadataFormatImpl; import javax.imageio.metadata.IIOMetadataFormatImpl;
import java.awt.image.BufferedImage;
import java.util.Arrays; import java.util.Arrays;
/** /**
@ -32,20 +33,19 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
// root -> PSDHeader // root -> PSDHeader
// TODO: How do I specify that the header is required? // TODO: How do I specify that the header is required?
addElement("PSDHeader", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY); addElement("Header", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY);
// TODO: Do the first two make sense? addAttribute("Header", "type", DATATYPE_STRING, false, "PSD", Arrays.asList("PSD", "PSB"));
// addAttribute("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS")); addAttribute("Header", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1"));
addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1"));
addAttribute("PSDHeader", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true); addAttribute("Header", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true);
// rows? // rows?
addAttribute("PSDHeader", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true); addAttribute("Header", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
// columns? // columns?
addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true); addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16")); addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
// TODO: Consider using more readable names?! // TODO: Consider using more readable names?!
addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.COLOR_MODES)); addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
/* /*
Contains the required data to define the color mode. Contains the required data to define the color mode.
@ -85,22 +85,46 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
// root -> ImageResources -> AlphaChannelInfo // root -> ImageResources -> AlphaChannelInfo
addElement("AlphaChannelInfo", "ImageResources", 0, Integer.MAX_VALUE); // The format probably does not support that many layers.. addElement("AlphaChannelInfo", "ImageResources", 0, Integer.MAX_VALUE); // The format probably does not support that many layers..
addElement("Name", "AlphaChannelInfo", CHILD_POLICY_EMPTY); addElement("Name", "AlphaChannelInfo", CHILD_POLICY_EMPTY);
addAttribute("Name", "value", DATATYPE_STRING, true, 0, Integer.MAX_VALUE); addAttribute("Name", "value", DATATYPE_STRING, true, null);
// root -> ImageResources -> DisplayInfo // root -> ImageResources -> DisplayInfo
addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY); addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY);
// TODO: Consider using human readable strings // TODO: Consider using human readable strings
// TODO: Limit values (0-8, 10, 11, 3000) // TODO: Limit values (0-8, 10, 11, 3000)
addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null); addAttribute("DisplayInfo", "colorSpace", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_CS));
addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4); addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4);
addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true); addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true);
// TODO: Consider using human readable strings // TODO: Consider using human readable strings
addAttribute("DisplayInfo", "kind", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS)); addAttribute("DisplayInfo", "kind", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS));
// root -> ImageResources -> EXIF1Data // root -> ImageResources -> EXIF
addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL); addElement("EXIF", "ImageResources", CHILD_POLICY_EMPTY);
addObjectValue("EXIF", PSDEXIF1Data.Directory.class, true, null);
// TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?) // TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?)
// root -> ImageResources -> GridAndGuideInfo
addElement("GridAndGuideInfo", "ImageResources", 0, Integer.MAX_VALUE);
addAttribute("GridAndGuideInfo", "version", DATATYPE_INTEGER, false, "1");
addAttribute("GridAndGuideInfo", "verticalGridCycle", DATATYPE_INTEGER, false, "576");
addAttribute("GridAndGuideInfo", "horizontalGridCycle", DATATYPE_INTEGER, false, "576");
addElement("Guide", "GridAndGuideInfo", CHILD_POLICY_EMPTY);
addAttribute("Guide", "location", DATATYPE_INTEGER, true, null, "0", Integer.toString(Integer.MAX_VALUE), true, true);
addAttribute("Guide", "orientation", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.GUIDE_ORIENTATIONS));
// root -> ImageResources -> ICCProfile
addElement("ICCProfile", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("ICCProfile", "colorSpaceType", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.JAVA_CS));
// root -> ImageResources -> IPTC
addElement("IPTC", "ImageResources", CHILD_POLICY_EMPTY);
addObjectValue("IPTC", PSDIPTCData.Directory.class, true, null);
// TODO: Incorporate IPTC metadata here somehow... (or treat as opaque bytes?)
// root -> ImageResources -> PixelAspectRatio
addElement("PixelAspectRatio", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("PixelAspectRatio", "version", DATATYPE_STRING, false, "1");
addAttribute("PixelAspectRatio", "aspectRatio", DATATYPE_DOUBLE, true, null, "0", Double.toString(Double.POSITIVE_INFINITY), true, false);
// root -> ImageResources -> PrintFlags // root -> ImageResources -> PrintFlags
addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY); addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY);
addBooleanAttribute("PrintFlags", "labels", false, false); addBooleanAttribute("PrintFlags", "labels", false, false);
@ -114,29 +138,53 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
// root -> ImageResources -> PrintFlagsInformation // root -> ImageResources -> PrintFlagsInformation
addElement("PrintFlagsInformation", "ImageResources", CHILD_POLICY_EMPTY); addElement("PrintFlagsInformation", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, true, null); addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, false, "1");
addBooleanAttribute("PrintFlagsInformation", "cropMarks", false, false); addBooleanAttribute("PrintFlagsInformation", "cropMarks", false, false);
addAttribute("PrintFlagsInformation", "field", DATATYPE_INTEGER, true, null); addAttribute("PrintFlagsInformation", "field", DATATYPE_INTEGER, true, "0");
addAttribute("PrintFlagsInformation", "bleedWidth", DATATYPE_INTEGER, true, null, "0", String.valueOf(Long.MAX_VALUE), true, true); // TODO: LONG??! addAttribute("PrintFlagsInformation", "bleedWidth", DATATYPE_INTEGER, true, null, "0", String.valueOf(Long.MAX_VALUE), true, true); // TODO: LONG??!
addAttribute("PrintFlagsInformation", "bleedScale", DATATYPE_INTEGER, true, null, "0", String.valueOf(Integer.MAX_VALUE), true, true); addAttribute("PrintFlagsInformation", "bleedScale", DATATYPE_INTEGER, true, null, "0", String.valueOf(Integer.MAX_VALUE), true, true);
// root -> ImageResources -> PrintScale
addElement("PrintScale", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("PrintScale", "style", DATATYPE_STRING, false, null, Arrays.asList(PSDMetadata.PRINT_SCALE_STYLES));
addAttribute("PrintScale", "xLocation", DATATYPE_FLOAT, true, null);
addAttribute("PrintScale", "yLocation", DATATYPE_FLOAT, true, null);
addAttribute("PrintScale", "scale", DATATYPE_FLOAT, true, null);
// root -> ImageResources -> ResolutionInfo // root -> ImageResources -> ResolutionInfo
addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY); addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null); addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null);
// TODO: Or use string and more friendly names? "pixels/inch"/"pixels/cm" and "inch"/"cm"/"pt"/"pica"/"column" addAttribute("ResolutionInfo", "hResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
addAttribute("ResolutionInfo", "hResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); addAttribute("ResolutionInfo", "widthUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
addAttribute("ResolutionInfo", "widthUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5"));
addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null); addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null);
// TODO: Or use more friendly names? addAttribute("ResolutionInfo", "vResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
addAttribute("ResolutionInfo", "vResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2")); addAttribute("ResolutionInfo", "heightUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
addAttribute("ResolutionInfo", "heightUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5"));
// ??? addElement("Thumbnail", "ImageResources", CHILD_POLICY_CHOICE); // root -> ImageResources -> UnicodeAlphaNames
addElement("UnicodeAlphaNames", "ImageResources", 0, Integer.MAX_VALUE);
addChildElement("UnicodeAlphaNames", "Name"); // TODO: Does this really work?
// root -> ImageResources -> XMPData
addElement("XMPData", "ImageResources", CHILD_POLICY_CHOICE); // root -> ImageResources -> VersionInfo
addElement("VersionInfo", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("VersionInfo", "version", DATATYPE_INTEGER, false, "1");
addBooleanAttribute("VersionInfo", "hasRealMergedData", false, false);
addAttribute("VersionInfo", "writer", DATATYPE_STRING, true, null);
addAttribute("VersionInfo", "reader", DATATYPE_STRING, true, null);
addAttribute("VersionInfo", "fileVersion", DATATYPE_INTEGER, true, "1");
// root -> ImageResources -> Thumbnail
addElement("Thumbnail", "ImageResources", CHILD_POLICY_EMPTY);
addObjectValue("Thumbnail", BufferedImage.class, true, null);
// root -> ImageResources -> UnicodeAlphaName
addElement("UnicodeAlphaName", "ImageResources", CHILD_POLICY_EMPTY);
addAttribute("UnicodeAlphaName", "value", DATATYPE_STRING, true, null);
// root -> ImageResources -> XMP
addElement("XMP", "ImageResources", CHILD_POLICY_CHOICE);
// TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?) // TODO: Incorporate XMP metadata here somehow (or treat as opaque bytes?)
addObjectValue("XMPData", Document.class, true, null); addObjectValue("XMP", Document.class, true, null);
// TODO: Layers // TODO: Layers
//addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE); //addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE);

View File

@ -0,0 +1,27 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDPixelAspectRatio
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDPixelAspectRatio.java,v 1.0 Nov 7, 2009 8:23:09 PM haraldk Exp$
*/
final class PSDPixelAspectRatio extends PSDImageResource {
// 4 bytes (version = 1), 8 bytes double, x / y of a pixel
int mVersion;
double mAspect;
PSDPixelAspectRatio(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mVersion = pInput.readInt();
mAspect = pInput.readDouble();
}
}

View File

@ -11,14 +11,14 @@ import java.io.IOException;
* @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$ * @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
*/ */
final class PSDPrintFlags extends PSDImageResource { final class PSDPrintFlags extends PSDImageResource {
private boolean mLabels; boolean mLabels;
private boolean mCropMasks; boolean mCropMasks;
private boolean mColorBars; boolean mColorBars;
private boolean mRegistrationMarks; boolean mRegistrationMarks;
private boolean mNegative; boolean mNegative;
private boolean mFlip; boolean mFlip;
private boolean mInterpolate; boolean mInterpolate;
private boolean mCaption; boolean mCaption;
PSDPrintFlags(final short pId, final ImageInputStream pInput) throws IOException { PSDPrintFlags(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput); super(pId, pInput);

View File

@ -11,11 +11,11 @@ import java.io.IOException;
* @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$ * @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
*/ */
final class PSDPrintFlagsInformation extends PSDImageResource { final class PSDPrintFlagsInformation extends PSDImageResource {
private int mVersion; int mVersion;
private boolean mCropMasks; boolean mCropMasks;
private int mField; int mField;
private long mBleedWidth; long mBleedWidth;
private int mBleedScale; int mBleedScale;
PSDPrintFlagsInformation(final short pId, final ImageInputStream pInput) throws IOException { PSDPrintFlagsInformation(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput); super(pId, pInput);
@ -25,7 +25,7 @@ final class PSDPrintFlagsInformation extends PSDImageResource {
protected void readData(final ImageInputStream pInput) throws IOException { protected void readData(final ImageInputStream pInput) throws IOException {
mVersion = pInput.readUnsignedShort(); mVersion = pInput.readUnsignedShort();
mCropMasks = pInput.readBoolean(); mCropMasks = pInput.readBoolean();
mField = pInput.readUnsignedByte(); mField = pInput.readUnsignedByte(); // TODO: Is this really pad?
mBleedWidth = pInput.readUnsignedInt(); mBleedWidth = pInput.readUnsignedInt();
mBleedScale = pInput.readUnsignedShort(); mBleedScale = pInput.readUnsignedShort();

View File

@ -0,0 +1,35 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
/**
* PSDPrintScale
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDPrintScale.java,v 1.0 Nov 7, 2009 9:41:17 PM haraldk Exp$
*/
final class PSDPrintScale extends PSDImageResource {
// 2 bytes style (0 = centered, 1 = size to fit, 2 = user defined).
// 4 bytes x location (floating point).
// 4 bytes y location (floating point).
// 4 bytes scale (floating point)
short mStyle;
float mXLocation;
float mYlocation;
float mScale;
PSDPrintScale(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mStyle = pInput.readShort();
mXLocation = pInput.readFloat();
mYlocation = pInput.readFloat();
mScale = pInput.readFloat();
}
}

View File

@ -0,0 +1,33 @@
package com.twelvemonkeys.imageio.plugins.psd;
import javax.imageio.stream.ImageInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* PSDUnicodeAlphaNames
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: PSDUnicodeAlphaNames.java,v 1.0 Nov 7, 2009 9:16:56 PM haraldk Exp$
*/
final class PSDUnicodeAlphaNames extends PSDImageResource {
List<String> mNames;
PSDUnicodeAlphaNames(final short pId, final ImageInputStream pInput) throws IOException {
super(pId, pInput);
}
@Override
protected void readData(final ImageInputStream pInput) throws IOException {
mNames = new ArrayList<String>();
long left = mSize;
while (left > 0) {
String name = PSDUtil.readUTF16String(pInput);
mNames.add(name);
left -= name.length() * 2 + 4;
}
}
}

View File

@ -2,4 +2,5 @@ Implement source subsampling and region of interest
Separate package for the resources (seems to be a lot)? Separate package for the resources (seems to be a lot)?
Possibility to read only some resources? readResources(int[] resourceKeys)? Possibility to read only some resources? readResources(int[] resourceKeys)?
- Probably faster when we only need the color space - Probably faster when we only need the color space
PSDImageWriter Support for Photoshop specific TIFF tags (extension for TIFFImageReader)?
PSDImageWriter?