mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-10-04 11:26:44 -04:00
Merge branch 'master' into 3.0-structure
Conflicts: imageio/imageio-psd/pom.xml twelvemonkeys-imageio/pom.xml twelvemonkeys-servlet/pom.xml
This commit is contained in:
@@ -172,14 +172,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
try {
|
||||
return mStream.length();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw unchecked(e, RuntimeException.class);
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
|
||||
private <T extends Throwable> T unchecked(IOException pExcption, Class<T> pClass) {
|
||||
// Ugly hack to fool the compiler..
|
||||
return (T) pExcption;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
@@ -86,7 +86,7 @@ class CMAPChunk extends IFFChunk {
|
||||
if (numColors == 32) {
|
||||
paletteSize = 64;
|
||||
}
|
||||
else {
|
||||
else if (numColors != 64) {
|
||||
throw new IIOException("Unknown number of colors for EHB: " + numColors);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,9 @@ class CMAPChunk extends IFFChunk {
|
||||
mGreens[i] = pInput.readByte();
|
||||
mBlues[i] = pInput.readByte();
|
||||
}
|
||||
if (isEHB) {
|
||||
|
||||
if (isEHB && numColors == 32) {
|
||||
// Create the half-brite colors
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2);
|
||||
mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2);
|
||||
|
@@ -94,13 +94,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// http://home.comcast.net/~erniew/lwsdk/docs/filefmts/ilbm.html
|
||||
// http://www.fileformat.info/format/iff/spec/7866a9f0e53c42309af667c5da3bd426/view.htm
|
||||
// - Contains definitions of some "new" chunks, as well as alternative FORM types
|
||||
|
||||
// TODO: One other existing deep bit ordering that you may encounter is the 21-bit
|
||||
// NewTek format.
|
||||
//
|
||||
// NewTek deep ILBM bit ordering:
|
||||
// saved first ------------------------------------------------------> saved last
|
||||
// R7 G7 B7 R6 G6 B6 R5 G5 B5 R4 G4 B4 R3 G3 B3 R2 G2 B2 R1 G1 B1 R0 G0 B0
|
||||
// http://amigan.1emu.net/reg/iff.html
|
||||
|
||||
private BMHDChunk mHeader;
|
||||
private CMAPChunk mColorMap;
|
||||
|
@@ -175,7 +175,7 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
|
||||
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
|
||||
// Annotation ANNO chunk, 8 + annoData.length bytes
|
||||
String annotation = "Written by " + getOriginatingProvider().getDescription(null);
|
||||
String annotation = "Written by " + getOriginatingProvider().getDescription(null) + " by " + getOriginatingProvider().getVendorName();
|
||||
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes());
|
||||
|
||||
ColorModel cm = pImage.getColorModel();
|
||||
|
@@ -14,7 +14,6 @@
|
||||
ImageIO plugin for Adobe Photoshop Document (PSD).
|
||||
</description>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
@@ -24,6 +23,10 @@
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-metadata</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@@ -103,10 +103,10 @@ Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
|
||||
}
|
||||
|
||||
public float[] toCIEXYZ(float[] colorvalue) {
|
||||
throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
|
||||
return sRGB.toCIEXYZ(toRGB(colorvalue));
|
||||
}
|
||||
|
||||
public float[] fromCIEXYZ(float[] colorvalue) {
|
||||
throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
|
||||
return sRGB.fromCIEXYZ(fromRGB(colorvalue));
|
||||
}
|
||||
}
|
||||
|
@@ -225,11 +225,11 @@ interface PSD {
|
||||
|
||||
// 03f8
|
||||
/** Color transfer functions */
|
||||
int RES_COLOR_TRANSFER_FUNCITON = 0x03f8;
|
||||
int RES_COLOR_TRANSFER_FUNCTION = 0x03f8;
|
||||
|
||||
// 03f9
|
||||
/** Duotone transfer functions */
|
||||
int RES_DUOTONE_TRANSFER_FUNCITON = 0x03f9;
|
||||
int RES_DUOTONE_TRANSFER_FUNCTOON = 0x03f9;
|
||||
|
||||
// 03fa
|
||||
/** Duotone image information */
|
||||
@@ -385,7 +385,7 @@ interface PSD {
|
||||
* (Photoshop 5.0) Unicode Alpha Names
|
||||
* Unicode string (4 bytes length followed by string).
|
||||
*/
|
||||
int RES_UNICODE_ALPHA_NAME = 0x0415;
|
||||
int RES_UNICODE_ALPHA_NAMES = 0x0415;
|
||||
|
||||
// 1046
|
||||
/**
|
||||
|
@@ -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);
|
||||
|
@@ -1,17 +1,10 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
|
||||
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;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* EXIF metadata.
|
||||
@@ -25,7 +18,6 @@ import java.util.List;
|
||||
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
||||
*/
|
||||
final class PSDEXIF1Data extends PSDImageResource {
|
||||
// protected byte[] mData;
|
||||
protected Directory mDirectory;
|
||||
|
||||
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
@@ -35,308 +27,16 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
|
||||
builder.append(", ").append(mDirectory);
|
||||
|
||||
builder.append("]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// TIFF Image file directory (IFD)
|
||||
static class Directory implements Iterable<Entry> {
|
||||
private List<Entry> mEntries = new ArrayList<Entry>();
|
||||
|
||||
private Directory() {}
|
||||
|
||||
public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException {
|
||||
Directory directory = new Directory();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
public Entry get(int pTag) {
|
||||
for (Entry entry : mEntries) {
|
||||
if (entry.mTag == pTag) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator() {
|
||||
return mEntries.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Directory%s", mEntries);
|
||||
}
|
||||
}
|
||||
|
||||
// TIFF IFD Entry
|
||||
static class Entry {
|
||||
private static final int EXIF_IFD = 0x8769;
|
||||
|
||||
private final static String[] TYPE_NAMES = {
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
};
|
||||
|
||||
private final static int[] TYPE_LENGTHS = {
|
||||
1, 1, 2, 4, 8,
|
||||
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
};
|
||||
|
||||
private int mTag;
|
||||
/*
|
||||
1 = BYTE 8-bit unsigned integer.
|
||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||
must be NUL (binary zero).
|
||||
3 = SHORT 16-bit (2-byte) unsigned integer.
|
||||
4 = LONG 32-bit (4-byte) unsigned integer.
|
||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||
fraction; the second, the denominator.
|
||||
|
||||
TIFF 6.0 and above:
|
||||
6 = SBYTE An 8-bit signed (twos-complement) integer.
|
||||
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
|
||||
the definition of the field.
|
||||
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
||||
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
||||
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
||||
fraction, the second the denominator.
|
||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||
*/
|
||||
private short mType;
|
||||
private int mCount;
|
||||
private long mValueOffset;
|
||||
private Object mValue;
|
||||
|
||||
private Entry() {}
|
||||
|
||||
public static Entry read(final ImageInputStream pInput) throws IOException {
|
||||
Entry entry = new Entry();
|
||||
|
||||
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) {
|
||||
long offset = pInput.readUnsignedInt();
|
||||
pInput.mark();
|
||||
try {
|
||||
entry.mValue = Directory.read(pInput, offset);
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
int valueLength = entry.getValueLength();
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
entry.readValueInLine(pInput);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
}
|
||||
else {
|
||||
entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
entry.readValue(pInput);
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void readValue(final ImageInputStream pInput) throws IOException {
|
||||
long pos = pInput.getStreamPosition();
|
||||
try {
|
||||
pInput.seek(mValueOffset);
|
||||
readValueInLine(pInput);
|
||||
}
|
||||
finally {
|
||||
pInput.seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void readValueInLine(ImageInputStream pInput) throws IOException {
|
||||
mValue = readValueDirect(pInput, mType, mCount);
|
||||
}
|
||||
|
||||
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
switch (pType) {
|
||||
case 2:
|
||||
// TODO: This might be UTF-8 or ISO-8859-1, even though against the spec
|
||||
byte[] ascii = new byte[pCount];
|
||||
pInput.readFully(ascii);
|
||||
return StringUtil.decode(ascii, 0, ascii.length, "ASCII");
|
||||
case 1:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedByte();
|
||||
}
|
||||
case 6:
|
||||
if (pCount == 1) {
|
||||
return pInput.readByte();
|
||||
}
|
||||
case 7:
|
||||
byte[] bytes = new byte[pCount];
|
||||
pInput.readFully(bytes);
|
||||
return bytes;
|
||||
case 3:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedShort();
|
||||
}
|
||||
case 8:
|
||||
if (pCount == 1) {
|
||||
return pInput.readShort();
|
||||
}
|
||||
|
||||
short[] shorts = new short[pCount];
|
||||
pInput.readFully(shorts, 0, shorts.length);
|
||||
return shorts;
|
||||
case 4:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt();
|
||||
}
|
||||
case 9:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt();
|
||||
}
|
||||
|
||||
int[] ints = new int[pCount];
|
||||
pInput.readFully(ints, 0, ints.length);
|
||||
return ints;
|
||||
case 11:
|
||||
if (pCount == 1) {
|
||||
return pInput.readFloat();
|
||||
}
|
||||
|
||||
float[] floats = new float[pCount];
|
||||
pInput.readFully(floats, 0, floats.length);
|
||||
return floats;
|
||||
case 12:
|
||||
if (pCount == 1) {
|
||||
return pInput.readDouble();
|
||||
}
|
||||
|
||||
double[] doubles = new double[pCount];
|
||||
pInput.readFully(doubles, 0, doubles.length);
|
||||
return doubles;
|
||||
|
||||
// TODO: Consider using a Rational class
|
||||
case 5:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
||||
}
|
||||
|
||||
double[] rationals = new double[pCount];
|
||||
for (int i = 0; i < rationals.length; i++) {
|
||||
rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case 10:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt() / (double) pInput.readInt();
|
||||
}
|
||||
|
||||
double[] srationals = new double[pCount];
|
||||
for (int i = 0; i < srationals.length; i++) {
|
||||
srationals[i] = pInput.readInt() / (double) pInput.readInt();
|
||||
}
|
||||
|
||||
return srationals;
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
||||
}
|
||||
}
|
||||
|
||||
private int getValueLength() {
|
||||
if (mType > 0 && mType <= TYPE_LENGTHS.length) {
|
||||
return TYPE_LENGTHS[mType - 1] * mCount;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private 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 String getValueAsString() {
|
||||
if (mValue instanceof String) {
|
||||
return String.format("\"%s\"", mValue);
|
||||
}
|
||||
|
||||
if (mValue != null && mValue.getClass().isArray()) {
|
||||
Class<?> type = mValue.getClass().getComponentType();
|
||||
if (byte.class == type) {
|
||||
return Arrays.toString((byte[]) mValue);
|
||||
}
|
||||
if (short.class == type) {
|
||||
return Arrays.toString((short[]) mValue);
|
||||
}
|
||||
if (int.class == type) {
|
||||
return Arrays.toString((int[]) mValue);
|
||||
}
|
||||
if (float.class == type) {
|
||||
return Arrays.toString((float[]) mValue);
|
||||
}
|
||||
if (double.class == type) {
|
||||
return Arrays.toString((double[]) mValue);
|
||||
}
|
||||
}
|
||||
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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)) {
|
||||
|
@@ -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;
|
||||
@@ -43,6 +44,9 @@ import java.lang.reflect.Field;
|
||||
* @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$
|
||||
*/
|
||||
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 String mName;
|
||||
final long mSize;
|
||||
@@ -59,9 +63,16 @@ class PSDImageResource {
|
||||
}
|
||||
|
||||
mSize = pInput.readUnsignedInt();
|
||||
readData(pInput);
|
||||
long startPos = pInput.getStreamPosition();
|
||||
|
||||
// Data is even-padded
|
||||
readData(new SubImageInputStream(pInput, mSize));
|
||||
|
||||
// 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();
|
||||
}
|
||||
@@ -114,6 +125,8 @@ class PSDImageResource {
|
||||
case PSD.RES_ALPHA_CHANNEL_INFO:
|
||||
case PSD.RES_DISPLAY_INFO:
|
||||
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:
|
||||
case PSD.RES_ICC_PROFILE:
|
||||
@@ -121,6 +134,8 @@ class PSDImageResource {
|
||||
case PSD.RES_EXIF_DATA_1:
|
||||
// case PSD.RES_EXIF_DATA_3:
|
||||
case PSD.RES_XMP_DATA:
|
||||
case PSD.RES_PRINT_SCALE:
|
||||
case PSD.RES_PIXEL_ASPECT_RATIO:
|
||||
case PSD.RES_PRINT_FLAGS_INFORMATION:
|
||||
return null;
|
||||
default:
|
||||
@@ -135,7 +150,7 @@ class PSDImageResource {
|
||||
catch (IllegalAccessException ignore) {
|
||||
}
|
||||
|
||||
return "unknown resource";
|
||||
return "UnknownResource";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,17 +172,27 @@ class PSDImageResource {
|
||||
return new PSDDisplayInfo(id, pInput);
|
||||
case PSD.RES_PRINT_FLAGS:
|
||||
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:
|
||||
return new PSDThumbnail(id, pInput);
|
||||
case PSD.RES_ICC_PROFILE:
|
||||
return new ICCProfile(id, pInput);
|
||||
case PSD.RES_UNICODE_ALPHA_NAMES:
|
||||
return new PSDUnicodeAlphaNames(id, pInput);
|
||||
case PSD.RES_VERSION_INFO:
|
||||
return new PSDVersionInfo(id, pInput);
|
||||
case PSD.RES_EXIF_DATA_1:
|
||||
return new PSDEXIF1Data(id, pInput);
|
||||
case PSD.RES_XMP_DATA:
|
||||
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:
|
||||
return new PSDPrintFlagsInformation(id, pInput);
|
||||
default:
|
||||
|
@@ -1,17 +1,14 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTC;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.FilterIterator;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
@@ -24,7 +21,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";
|
||||
@@ -43,102 +40,32 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
|
||||
static final String[] DISPLAY_INFO_CS = {
|
||||
"RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC",
|
||||
null, // ... (until index 2999),
|
||||
null, // TODO: ... (until index 2999),
|
||||
"ANPA"
|
||||
};
|
||||
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() {
|
||||
// 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());
|
||||
@@ -155,8 +82,9 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
}
|
||||
|
||||
private Node createHeaderNode() {
|
||||
IIOMetadataNode header = new IIOMetadataNode("PSDHeader");
|
||||
IIOMetadataNode header = new IIOMetadataNode("Header");
|
||||
|
||||
header.setAttribute("type", "PSD");
|
||||
header.setAttribute("version", "1");
|
||||
header.setAttribute("channels", Integer.toString(mHeader.mChannels));
|
||||
header.setAttribute("height", Integer.toString(mHeader.mHeight));
|
||||
@@ -175,7 +103,27 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
// TODO: Always add name (if set) and id (as resourceId) to all nodes?
|
||||
// Resource Id is useful for people with access to the PSD spec..
|
||||
|
||||
if (imageResource instanceof PSDAlphaChannelInfo) {
|
||||
if (imageResource instanceof ICCProfile) {
|
||||
ICCProfile profile = (ICCProfile) imageResource;
|
||||
|
||||
// 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) {
|
||||
PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("AlphaChannelInfo");
|
||||
@@ -185,8 +133,6 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
nameNode.setAttribute("value", name);
|
||||
node.appendChild(nameNode);
|
||||
}
|
||||
|
||||
resource.appendChild(node);
|
||||
}
|
||||
else if (imageResource instanceof PSDDisplayInfo) {
|
||||
PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource;
|
||||
@@ -195,55 +141,199 @@ 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));
|
||||
}
|
||||
|
||||
node.setAttribute("colors", builder.toString());
|
||||
node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity));
|
||||
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...
|
||||
PSDIPTCData iptc = (PSDIPTCData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "IPTC");
|
||||
node.setUserObject(iptc.mDirectory);
|
||||
|
||||
appendEntries(node, "IPTC", iptc.mDirectory);
|
||||
}
|
||||
else if (imageResource instanceof PSDEXIF1Data) {
|
||||
// TODO: Revise/rethink this...
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "EXIF");
|
||||
// TODO: Set byte[] data instead
|
||||
node.setUserObject(exif.mDirectory);
|
||||
|
||||
appendEntries(node, "EXIF", exif.mDirectory);
|
||||
}
|
||||
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...
|
||||
// Or maybe use the Directory approach used by IPTC and EXIF..
|
||||
PSDXMPData xmp = (PSDXMPData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("XMPData");
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "XMP");
|
||||
appendEntries(node, "XMP", xmp.mDirectory);
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(xmp.getData()));
|
||||
|
||||
// Set the entire XMP document as user data
|
||||
node.setUserObject(document);
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
resource.appendChild(node);
|
||||
// Set the entire XMP document as user data
|
||||
node.setUserObject(xmp.mData);
|
||||
}
|
||||
else {
|
||||
// Generic resource..
|
||||
node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId));
|
||||
|
||||
resource.appendChild(node);
|
||||
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
|
||||
|
||||
node.setAttribute("resourceId", Integer.toHexString(imageResource.mId));
|
||||
node.setAttribute("resourceId", String.format("0x%04x", imageResource.mId));
|
||||
resource.appendChild(node);
|
||||
}
|
||||
|
||||
// TODO: Layers and layer info
|
||||
|
||||
// TODO: Global mask etc..
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void appendEntries(final IIOMetadataNode pNode, final String pType, final Directory pDirectory) {
|
||||
for (Entry entry : pDirectory) {
|
||||
Object tagId = entry.getIdentifier();
|
||||
|
||||
IIOMetadataNode tag = new IIOMetadataNode("Entry");
|
||||
tag.setAttribute("tag", String.format("%s", tagId));
|
||||
|
||||
String field = entry.getFieldName();
|
||||
if (field != null) {
|
||||
tag.setAttribute("field", String.format("%s", field));
|
||||
}
|
||||
else {
|
||||
if ("IPTC".equals(pType)) {
|
||||
tag.setAttribute("field", String.format("%s:%s", (Integer) tagId >> 8, (Integer) tagId & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
appendEntries(tag, pType, (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
|
||||
@@ -338,7 +428,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");
|
||||
@@ -346,88 +436,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:
|
||||
@@ -457,7 +560,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) {
|
||||
@@ -480,18 +583,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(TIFF.TAG_DATE_TIME);
|
||||
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);
|
||||
}
|
||||
@@ -502,61 +605,95 @@ 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(PSD.RES_IPTC_NAA, PSD.RES_EXIF_DATA_1, PSD.RES_XMP_DATA);
|
||||
|
||||
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;
|
||||
|
||||
appendTextEntriesFlat(text, iptc.mDirectory, new FilterIterator.Filter<Entry>() {
|
||||
public boolean accept(final Entry pEntry) {
|
||||
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||
|
||||
switch (tagId) {
|
||||
case IPTC.TAG_SOURCE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (textResource instanceof PSDEXIF1Data) {
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) textResource;
|
||||
|
||||
appendTextEntriesFlat(text, exif.mDirectory, new FilterIterator.Filter<Entry>() {
|
||||
public boolean accept(final Entry pEntry) {
|
||||
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||
|
||||
switch (tagId) {
|
||||
case TIFF.TAG_SOFTWARE:
|
||||
case TIFF.TAG_ARTIST:
|
||||
case TIFF.TAG_COPYRIGHT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (textResource instanceof PSDXMPData) {
|
||||
// TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF?
|
||||
// TODO: Use XMP IPTC/EXIF/TIFFF NativeDigest field to validate if the values are in sync...
|
||||
PSDXMPData xmp = (PSDXMPData) textResource;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private void appendTextEntriesFlat(final IIOMetadataNode pNode, final Directory pDirectory, final FilterIterator.Filter<Entry> pFilter) {
|
||||
FilterIterator<Entry> pEntries = new FilterIterator<Entry>(pDirectory.iterator(), pFilter);
|
||||
while (pEntries.hasNext()) {
|
||||
Entry entry = pEntries.next();
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
appendTextEntriesFlat(pNode, (Directory) entry.getValue(), pFilter);
|
||||
}
|
||||
else if (entry.getValue() instanceof String) {
|
||||
IIOMetadataNode tag = new IIOMetadataNode("TextEntry");
|
||||
String fieldName = entry.getFieldName();
|
||||
|
||||
if (fieldName != null) {
|
||||
tag.setAttribute("keyword", String.format("%s", fieldName));
|
||||
}
|
||||
else {
|
||||
// TODO: This should never happen, as we filter out only specific nodes
|
||||
tag.setAttribute("keyword", String.format("%s", entry.getIdentifier()));
|
||||
}
|
||||
|
||||
tag.setAttribute("value", entry.getValueAsString());
|
||||
pNode.appendChild(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -570,7 +707,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;
|
||||
@@ -593,13 +730,13 @@ public final class PSDMetadata extends IIOMetadata implements Cloneable {
|
||||
});
|
||||
}
|
||||
|
||||
Iterator<PSDImageResource> getResources(final Class<? extends PSDImageResource>... pResourceTypes) {
|
||||
Iterator<PSDImageResource> getResources(final int... pResourceTypes) {
|
||||
Iterator<PSDImageResource> iterator = mImageResources.iterator();
|
||||
|
||||
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||
public boolean accept(final PSDImageResource pElement) {
|
||||
for (Class<?> type : pResourceTypes) {
|
||||
if (type.isInstance(pElement)) {
|
||||
public boolean accept(final PSDImageResource pResource) {
|
||||
for (int type : pResourceTypes) {
|
||||
if (type == pResource.mId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -608,4 +745,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,9 +1,11 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -32,20 +34,18 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
||||
|
||||
// root -> PSDHeader
|
||||
// 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("PSDHeader", "signature", DATATYPE_STRING, false, "8BPS", Arrays.asList("8BPS"));
|
||||
addAttribute("PSDHeader", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1"));
|
||||
addAttribute("Header", "type", DATATYPE_STRING, false, "PSD", Arrays.asList("PSD", "PSB"));
|
||||
addAttribute("Header", "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?
|
||||
addAttribute("PSDHeader", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
addAttribute("Header", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
// columns?
|
||||
addAttribute("PSDHeader", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
addAttribute("PSDHeader", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
|
||||
// TODO: Consider using more readable names?!
|
||||
addAttribute("PSDHeader", "mode", DATATYPE_INTEGER, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
|
||||
addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
|
||||
addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
|
||||
|
||||
/*
|
||||
Contains the required data to define the color mode.
|
||||
@@ -85,22 +85,46 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
||||
// root -> ImageResources -> AlphaChannelInfo
|
||||
addElement("AlphaChannelInfo", "ImageResources", 0, Integer.MAX_VALUE); // The format probably does not support that many layers..
|
||||
addElement("Name", "AlphaChannelInfo", CHILD_POLICY_EMPTY);
|
||||
addAttribute("Name", "value", DATATYPE_STRING, true, 0, Integer.MAX_VALUE);
|
||||
addAttribute("Name", "value", DATATYPE_STRING, true, null);
|
||||
|
||||
// root -> ImageResources -> DisplayInfo
|
||||
addElement("DisplayInfo", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
// TODO: Consider using human readable strings
|
||||
// TODO: Limit values (0-8, 10, 11, 3000)
|
||||
addAttribute("DisplayInfo", "colorSpace", DATATYPE_INTEGER, true, null);
|
||||
addAttribute("DisplayInfo", "colorSpace", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_CS));
|
||||
addAttribute("DisplayInfo", "colors", DATATYPE_INTEGER, true, 4, 4);
|
||||
addAttribute("DisplayInfo", "opacity", DATATYPE_INTEGER, true, null, "0", "100", true, true);
|
||||
// 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
|
||||
addElement("EXIF1Data", "ImageResources", CHILD_POLICY_ALL);
|
||||
// root -> ImageResources -> EXIF
|
||||
addElement("EXIF", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addObjectValue("EXIF", Directory.class, true, null);
|
||||
// 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", 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
|
||||
addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addBooleanAttribute("PrintFlags", "labels", false, false);
|
||||
@@ -114,29 +138,53 @@ public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
||||
|
||||
// root -> ImageResources -> PrintFlagsInformation
|
||||
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);
|
||||
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", "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
|
||||
addElement("ResolutionInfo", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addAttribute("ResolutionInfo", "hRes", DATATYPE_FLOAT, true, null);
|
||||
// TODO: Or use string and more friendly names? "pixels/inch"/"pixels/cm" and "inch"/"cm"/"pt"/"pica"/"column"
|
||||
addAttribute("ResolutionInfo", "hResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2"));
|
||||
addAttribute("ResolutionInfo", "widthUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5"));
|
||||
addAttribute("ResolutionInfo", "hResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
|
||||
addAttribute("ResolutionInfo", "widthUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
|
||||
addAttribute("ResolutionInfo", "vRes", DATATYPE_FLOAT, true, null);
|
||||
// TODO: Or use more friendly names?
|
||||
addAttribute("ResolutionInfo", "vResUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2"));
|
||||
addAttribute("ResolutionInfo", "heightUnit", DATATYPE_INTEGER, true, null, Arrays.asList("1", "2", "3", "4", "5"));
|
||||
addAttribute("ResolutionInfo", "vResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
|
||||
addAttribute("ResolutionInfo", "heightUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
|
||||
|
||||
// ??? 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?)
|
||||
addObjectValue("XMPData", Document.class, true, null);
|
||||
addObjectValue("XMP", Document.class, true, null);
|
||||
|
||||
// TODO: Layers
|
||||
//addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE);
|
||||
|
@@ -11,14 +11,14 @@ import java.io.IOException;
|
||||
* @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
|
||||
*/
|
||||
final class PSDPrintFlags extends PSDImageResource {
|
||||
private boolean mLabels;
|
||||
private boolean mCropMasks;
|
||||
private boolean mColorBars;
|
||||
private boolean mRegistrationMarks;
|
||||
private boolean mNegative;
|
||||
private boolean mFlip;
|
||||
private boolean mInterpolate;
|
||||
private boolean mCaption;
|
||||
boolean mLabels;
|
||||
boolean mCropMasks;
|
||||
boolean mColorBars;
|
||||
boolean mRegistrationMarks;
|
||||
boolean mNegative;
|
||||
boolean mFlip;
|
||||
boolean mInterpolate;
|
||||
boolean mCaption;
|
||||
|
||||
PSDPrintFlags(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
|
@@ -11,11 +11,11 @@ import java.io.IOException;
|
||||
* @version $Id: PSDPrintFlagsInfo.java,v 1.0 Jul 28, 2009 5:16:27 PM haraldk Exp$
|
||||
*/
|
||||
final class PSDPrintFlagsInformation extends PSDImageResource {
|
||||
private int mVersion;
|
||||
private boolean mCropMasks;
|
||||
private int mField;
|
||||
private long mBleedWidth;
|
||||
private int mBleedScale;
|
||||
int mVersion;
|
||||
boolean mCropMasks;
|
||||
int mField;
|
||||
long mBleedWidth;
|
||||
int mBleedScale;
|
||||
|
||||
PSDPrintFlagsInformation(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
@@ -25,7 +25,7 @@ final class PSDPrintFlagsInformation extends PSDImageResource {
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mVersion = pInput.readUnsignedShort();
|
||||
mCropMasks = pInput.readBoolean();
|
||||
mField = pInput.readUnsignedByte();
|
||||
mField = pInput.readUnsignedByte(); // TODO: Is this really pad?
|
||||
mBleedWidth = pInput.readUnsignedInt();
|
||||
mBleedScale = pInput.readUnsignedShort();
|
||||
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -21,6 +23,7 @@ import java.nio.charset.Charset;
|
||||
*/
|
||||
final class PSDXMPData extends PSDImageResource {
|
||||
protected byte[] mData;
|
||||
Directory mDirectory;
|
||||
|
||||
PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
@@ -29,7 +32,9 @@ final class PSDXMPData extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
|
||||
pInput.readFully(mData);
|
||||
//pInput.readFully(mData);
|
||||
|
||||
mDirectory = new XMPReader().read(pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,7 +52,7 @@ final class PSDXMPData extends PSDImageResource {
|
||||
builder.append("\"]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a character stream containing the XMP metadata (XML).
|
||||
|
@@ -2,4 +2,5 @@ Implement source subsampling and region of interest
|
||||
Separate package for the resources (seems to be a lot)?
|
||||
Possibility to read only some resources? readResources(int[] resourceKeys)?
|
||||
- Probably faster when we only need the color space
|
||||
PSDImageWriter
|
||||
Support for Photoshop specific TIFF tags (extension for TIFFImageReader)?
|
||||
PSDImageWriter?
|
@@ -11,4 +11,6 @@
|
||||
- http://vinetto.sourceforge.net/docs.html
|
||||
- We probably want to support all of these
|
||||
|
||||
- Thumbnail (API) support? Does it make sense? It's all thumbnails..
|
||||
|
||||
DONE:
|
||||
|
Reference in New Issue
Block a user