mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 19:45:28 -04:00
Merge commit 'upstream/master'
This commit is contained in:
commit
ec4334cbb5
@ -60,6 +60,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
||||
// TODO: Let the decoder decide preferred buffer size
|
||||
this(pStream, pDecoder, 1024);
|
||||
}
|
||||
|
||||
|
@ -107,6 +107,7 @@ public final class PackBitsDecoder implements Decoder {
|
||||
int read = 0;
|
||||
final int max = pBuffer.length;
|
||||
|
||||
// TODO: Don't decode more than single runs, because some writers add pad bytes inside the stream...
|
||||
while (read < max) {
|
||||
int n;
|
||||
|
||||
|
@ -1572,7 +1572,7 @@ public final class StringUtil {
|
||||
* @param pStringArray the string array
|
||||
* @return A string of comma-separated values
|
||||
*/
|
||||
public static String toCSVString(String[] pStringArray) {
|
||||
public static String toCSVString(Object[] pStringArray) {
|
||||
return toCSVString(pStringArray, ", ");
|
||||
}
|
||||
|
||||
@ -1584,7 +1584,7 @@ public final class StringUtil {
|
||||
* @return string of delimiter separated values
|
||||
* @throws IllegalArgumentException if {@code pDelimiterString == null}
|
||||
*/
|
||||
public static String toCSVString(String[] pStringArray, String pDelimiterString) {
|
||||
public static String toCSVString(Object[] pStringArray, String pDelimiterString) {
|
||||
if (pStringArray == null) {
|
||||
return "";
|
||||
}
|
||||
|
@ -115,13 +115,7 @@ public class BASE64 {
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick implementation, using the undocumented
|
||||
* {@code sun.misc.BASE64Decoder.decodeBuffer(String)}.
|
||||
*/
|
||||
public static byte[] decode(String pData) throws java.io.IOException {
|
||||
//return DECODER.decodeBuffer(pData);
|
||||
|
||||
public static byte[] decode(String pData) throws IOException {
|
||||
InputStream in = new DecoderStream(new ByteArrayInputStream(pData.getBytes()), new Base64Decoder());
|
||||
ByteArrayOutputStream bytes = new FastByteArrayOutputStream(pData.length() * 3);
|
||||
FileUtil.copy(in, bytes);
|
||||
@ -131,7 +125,7 @@ public class BASE64 {
|
||||
|
||||
//private final static sun.misc.BASE64Decoder DECODER = new sun.misc.BASE64Decoder();
|
||||
|
||||
public static void main(String[] pArgs) throws java.io.IOException {
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
if (pArgs.length == 1) {
|
||||
System.out.println(encode(pArgs[0].getBytes()));
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides helper classes for service provider implementations.
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.spi;
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides various additional stream implementations.
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.stream;
|
@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides various common utilities for reading and writing images.
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.util;
|
@ -36,6 +36,7 @@ import org.jmock.core.Stub;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.event.IIOReadProgressListener;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@ -1320,7 +1321,9 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> extends
|
||||
|
||||
assertEquals(type.getColorModel(), result.getColorModel());
|
||||
|
||||
// assertEquals(type.getSampleModel(), result.getSampleModel());
|
||||
// The following logically tests
|
||||
// assertEquals(type.getSampleModel(), result.getSampleModel());
|
||||
// but SampleModel does not have a proper equals method.
|
||||
SampleModel expectedModel = type.getSampleModel();
|
||||
SampleModel resultModel = result.getSampleModel();
|
||||
|
||||
@ -1335,10 +1338,6 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> extends
|
||||
}
|
||||
}
|
||||
|
||||
// public void testSetDestinationTypeIllegal() throws IOException {
|
||||
// throw new UnsupportedOperationException("Method testSetDestinationTypeIllegal not implemented"); // TODO: Implement
|
||||
// }
|
||||
//
|
||||
// public void testSetDestinationBands() throws IOException {
|
||||
// throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement
|
||||
// }
|
||||
@ -1347,6 +1346,24 @@ public abstract class ImageReaderAbstractTestCase<T extends ImageReader> extends
|
||||
// throw new UnsupportedOperationException("Method testSetDestinationBands not implemented"); // TODO: Implement
|
||||
// }
|
||||
|
||||
public void testProviderAndMetadataFormatNamesMatch() throws IOException {
|
||||
ImageReaderSpi provider = createProvider();
|
||||
|
||||
ImageReader reader = createReader();
|
||||
reader.setInput(getTestData().get(0).getInputStream());
|
||||
|
||||
IIOMetadata imageMetadata = reader.getImageMetadata(0);
|
||||
if (imageMetadata != null) {
|
||||
assertEquals(provider.getNativeImageMetadataFormatName(), imageMetadata.getNativeMetadataFormatName());
|
||||
}
|
||||
|
||||
IIOMetadata streamMetadata = reader.getStreamMetadata();
|
||||
if (streamMetadata != null) {
|
||||
assertEquals(provider.getNativeStreamMetadataFormatName(), streamMetadata.getNativeMetadataFormatName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected URL getClassLoaderResource(final String pName) {
|
||||
return getClass().getResource(pName);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -113,16 +113,11 @@
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<!-- TODO: Make default -->
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<!--<mainClass>com.mycompany.app.App</mainClass>-->
|
||||
<!-- <packageName>com.twelvemonkeys.imageio.plugins.psd</packageName> -->
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<Implementation-Title>${project.name}</Implementation-Title>
|
||||
<Implementation-Vendor>TwelveMonkeys</Implementation-Vendor>
|
||||
|
@ -101,7 +101,7 @@ interface PSD {
|
||||
int COMPRESSION_ZIP = 2;
|
||||
|
||||
/** ZIP compression with prediction */
|
||||
int COMPRESSION_ZIP_PREDICTON = 3;
|
||||
int COMPRESSION_ZIP_PREDICTION = 3;
|
||||
|
||||
// Color Modes
|
||||
/** Bitmap (monochrome) */
|
||||
@ -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
|
||||
/**
|
||||
|
@ -48,11 +48,11 @@ class PSDAlphaChannelInfo extends PSDImageResource {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(ImageInputStream pInput) throws IOException {
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mNames = new ArrayList<String>();
|
||||
long left = mSize;
|
||||
while (left > 0) {
|
||||
String name = PSDUtil.readPascalStringByte(pInput);
|
||||
String name = PSDUtil.readPascalString(pInput);
|
||||
mNames.add(name);
|
||||
left -= name.length() + 1;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ class PSDColorData {
|
||||
final byte[] mColors;
|
||||
private IndexColorModel mColorModel;
|
||||
|
||||
PSDColorData(ImageInputStream pInput) throws IOException {
|
||||
PSDColorData(final ImageInputStream pInput) throws IOException {
|
||||
int length = pInput.readInt();
|
||||
if (length == 0) {
|
||||
throw new IIOException("No palette information in PSD");
|
||||
@ -72,7 +72,7 @@ class PSDColorData {
|
||||
return mColorModel;
|
||||
}
|
||||
|
||||
private int[] toInterleavedRGB(byte[] pColors) {
|
||||
private static int[] toInterleavedRGB(final byte[] pColors) {
|
||||
int[] rgb = new int[pColors.length / 3];
|
||||
|
||||
for (int i = 0; i < rgb.length; i++) {
|
||||
|
@ -40,7 +40,22 @@ import java.io.IOException;
|
||||
* @version $Id: PSDResolutionInfo.java,v 1.0 May 2, 2008 3:58:19 PM haraldk Exp$
|
||||
*/
|
||||
class PSDDisplayInfo extends PSDImageResource {
|
||||
// TODO: Size of this struct should be 14.. Does not compute...
|
||||
// TODO: Size of this struct should be 14.. Does not compute... Something bogus here
|
||||
|
||||
// ColorSpace definitions:
|
||||
// PSD_CS_RGB = 0, /* RGB */
|
||||
// PSD_CS_HSB = 1, /* Hue, Saturation, Brightness */
|
||||
// PSD_CS_CMYK = 2, /* CMYK */
|
||||
// PSD_CS_PANTONE = 3, /* Pantone matching system (Lab)*/
|
||||
// PSD_CS_FOCOLTONE = 4, /* Focoltone colour system (CMYK)*/
|
||||
// PSD_CS_TRUMATCH = 5, /* Trumatch color (CMYK)*/
|
||||
// PSD_CS_TOYO = 6, /* Toyo 88 colorfinder 1050 (Lab)*/
|
||||
// PSD_CS_LAB = 7, /* L*a*b*/
|
||||
// PSD_CS_GRAYSCALE = 8, /* Grey scale */
|
||||
// PSD_CS_HKS = 10, /* HKS colors (CMYK)*/
|
||||
// PSD_CS_DIC = 11, /* DIC color guide (Lab)*/
|
||||
// PSD_CS_ANPA = 3000, /* Anpa color (Lab)*/
|
||||
|
||||
//typedef _DisplayInfo
|
||||
//{
|
||||
// WORD ColorSpace;
|
||||
@ -50,10 +65,10 @@ class PSDDisplayInfo extends PSDImageResource {
|
||||
// BYTE Padding; /* Always zero */
|
||||
//} DISPLAYINFO;
|
||||
|
||||
private int mColorSpace;
|
||||
private short[] mColors;
|
||||
private short mOpacity;
|
||||
private byte mKind;
|
||||
int mColorSpace;
|
||||
short[] mColors;
|
||||
short mOpacity;
|
||||
byte mKind;
|
||||
|
||||
PSDDisplayInfo(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
@ -67,20 +82,20 @@ class PSDDisplayInfo extends PSDImageResource {
|
||||
|
||||
// long left = mSize;
|
||||
// while (left > 0) {
|
||||
mColorSpace = pInput.readShort();
|
||||
mColorSpace = pInput.readShort();
|
||||
|
||||
// Color[4]...?
|
||||
// Color[4]...?
|
||||
mColors = new short[4];
|
||||
mColors[0] = pInput.readShort();
|
||||
mColors[1] = pInput.readShort();
|
||||
mColors[2] = pInput.readShort();
|
||||
mColors[3] = pInput.readShort();
|
||||
mColors[0] = pInput.readShort();
|
||||
mColors[1] = pInput.readShort();
|
||||
mColors[2] = pInput.readShort();
|
||||
mColors[3] = pInput.readShort();
|
||||
|
||||
mOpacity = pInput.readShort();
|
||||
mOpacity = pInput.readShort();
|
||||
|
||||
mKind = pInput.readByte();
|
||||
mKind = pInput.readByte();
|
||||
|
||||
pInput.readByte(); // Pad
|
||||
pInput.readByte(); // Pad
|
||||
// left -= 14;
|
||||
// }
|
||||
pInput.skipBytes(mSize - 14);
|
||||
|
@ -10,6 +10,7 @@ import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -35,7 +36,7 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// This is in essence an embedded TIFF file.
|
||||
// TODO: Extract TIFF parsing to more general purpose package
|
||||
// TODO: Instead, read the byte data, store for later parsing
|
||||
// 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];
|
||||
@ -67,8 +68,8 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
}
|
||||
|
||||
// TIFF Image file directory (IFD)
|
||||
private static class Directory {
|
||||
List<Entry> mEntries = new ArrayList<Entry>();
|
||||
static class Directory implements Iterable<Entry> {
|
||||
private List<Entry> mEntries = new ArrayList<Entry>();
|
||||
|
||||
private Directory() {}
|
||||
|
||||
@ -90,6 +91,20 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public Entry get(int pTag) {
|
||||
for (Entry entry : mEntries) {
|
||||
if (entry.mTag == pTag) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator() {
|
||||
return mEntries.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Directory%s", mEntries);
|
||||
@ -97,7 +112,7 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
}
|
||||
|
||||
// TIFF IFD Entry
|
||||
private static class Entry {
|
||||
static class Entry {
|
||||
private static final int EXIF_IFD = 0x8769;
|
||||
|
||||
private final static String[] TYPE_NAMES = {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.IOException;
|
||||
@ -61,7 +64,7 @@ class PSDHeader {
|
||||
final short mBits;
|
||||
final short mMode;
|
||||
|
||||
PSDHeader(ImageInputStream pInput) throws IOException {
|
||||
PSDHeader(final ImageInputStream pInput) throws IOException {
|
||||
int signature = pInput.readInt();
|
||||
if (signature != PSD.SIGNATURE_8BPS) {
|
||||
throw new IIOException("Not a PSD document, expected signature \"8BPS\": \"" + PSDUtil.intToStr(signature) + "\" (0x" + Integer.toHexString(signature) + ")");
|
||||
|
@ -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
|
||||
|
||||
}
|
||||
}
|
@ -31,8 +31,15 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.ImageReaderBase;
|
||||
import com.twelvemonkeys.imageio.util.IndexedImageTypeSpecifier;
|
||||
import com.twelvemonkeys.xml.XMLSerializer;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.*;
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.ImageReadParam;
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.awt.*;
|
||||
@ -43,13 +50,11 @@ import java.awt.image.*;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ImageReader for Adobe Photoshop Document format.
|
||||
* ImageReader for Adobe Photoshop Document (PSD) format.
|
||||
*
|
||||
* @see <a href="http://www.fileformat.info/format/psd/egff.htm">Adobe Photoshop File Format Summary<a>
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@ -57,18 +62,19 @@ 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: Allow reading separate (or some?) layers
|
||||
// TODO: API for reading separate layers
|
||||
// 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
|
||||
// See http://www.adobeforums.com/webx?14@@.3bc381dc/0
|
||||
public class PSDImageReader extends ImageReaderBase {
|
||||
private PSDHeader mHeader;
|
||||
private PSDColorData mColorData;
|
||||
private List<PSDImageResource> mImageResources;
|
||||
private PSDGlobalLayerMask mGlobalLayerMask;
|
||||
private List<PSDLayerInfo> mLayerInfo;
|
||||
// private PSDColorData mColorData;
|
||||
// private List<PSDImageResource> mImageResources;
|
||||
// private PSDGlobalLayerMask mGlobalLayerMask;
|
||||
// private List<PSDLayerInfo> mLayerInfo;
|
||||
private ICC_ColorSpace mColorSpace;
|
||||
protected PSDMetadata mMetadata;
|
||||
|
||||
protected PSDImageReader(final ImageReaderSpi pOriginatingProvider) {
|
||||
super(pOriginatingProvider);
|
||||
@ -76,8 +82,9 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
protected void resetMembers() {
|
||||
mHeader = null;
|
||||
mColorData = null;
|
||||
mImageResources = null;
|
||||
// mColorData = null;
|
||||
// mImageResources = null;
|
||||
mMetadata = null;
|
||||
mColorSpace = null;
|
||||
}
|
||||
|
||||
@ -115,9 +122,9 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
);
|
||||
|
||||
case PSD.COLOR_MODE_INDEXED:
|
||||
// TODO: 16 bit indexed?!
|
||||
// TODO: 16 bit indexed?! Does it exist?
|
||||
if (mHeader.mChannels == 1 && mHeader.mBits == 8) {
|
||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(mColorData.getIndexColorModel());
|
||||
return IndexedImageTypeSpecifier.createFromIndexColorModel(mMetadata.mColorData.getIndexColorModel());
|
||||
}
|
||||
|
||||
throw new IIOException(
|
||||
@ -184,6 +191,11 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
throw new IIOException(
|
||||
String.format("Unsupported channel count/bit depth for CMYK PSD: %d channels/%d bits", mHeader.mChannels, mHeader.mBits)
|
||||
);
|
||||
|
||||
case PSD.COLOR_MODE_MULTICHANNEL:
|
||||
// TODO: Implement
|
||||
case PSD.COLOR_MODE_LAB:
|
||||
// TODO: Implement
|
||||
default:
|
||||
throw new IIOException(
|
||||
String.format("Unsupported PSD MODE: %s (%d channels/%d bits)", mHeader.mMode, mHeader.mChannels, mHeader.mBits)
|
||||
@ -254,7 +266,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
if (mColorSpace == null) {
|
||||
ICC_Profile profile = null;
|
||||
for (PSDImageResource resource : mImageResources) {
|
||||
for (PSDImageResource resource : mMetadata.mImageResources) {
|
||||
if (resource instanceof ICCProfile) {
|
||||
profile = ((ICCProfile) resource).getProfile();
|
||||
break;
|
||||
@ -323,6 +335,8 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
int[] byteCounts = null;
|
||||
int compression = mImageInput.readShort();
|
||||
// TODO: Need to make sure compression is set in metadata, even without reading the image data!
|
||||
mMetadata.mCompression = compression;
|
||||
|
||||
switch (compression) {
|
||||
case PSD.COMPRESSION_NONE:
|
||||
@ -336,7 +350,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
// TODO: Could probably use the ZIPDecoder (DeflateDecoder) here..
|
||||
case PSD.COMPRESSION_ZIP_PREDICTON:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||
// TODO: Need to find out if the normal java.util.zip can handle this...
|
||||
// Could be same as PNG prediction? Read up...
|
||||
throw new IIOException("ZIP compression not supported yet");
|
||||
@ -404,7 +418,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
read16bitChannel(c, mHeader.mChannels, data16, interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, c * mHeader.mHeight, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits);
|
||||
throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits));
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
@ -536,6 +550,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
private void read1bitChannel(final int pChannel, final int pChannelCount,
|
||||
final byte[] pData, final int pBands, final int pBandOffset,
|
||||
final ColorModel pSourceColorModel,
|
||||
@ -682,6 +697,9 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
if (mHeader == null) {
|
||||
mHeader = new PSDHeader(mImageInput);
|
||||
|
||||
mMetadata = new PSDMetadata();
|
||||
mMetadata.mHeader = mHeader;
|
||||
|
||||
/*
|
||||
Contains the required data to define the color mode.
|
||||
|
||||
@ -694,9 +712,10 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
around as a black box for use when saving the file.
|
||||
*/
|
||||
if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) {
|
||||
mColorData = new PSDColorData(mImageInput);
|
||||
mMetadata.mColorData = new PSDColorData(mImageInput);
|
||||
}
|
||||
else {
|
||||
// TODO: We need to store the duotone spec if we decide to create a writer...
|
||||
// Skip color mode data for other modes
|
||||
long length = mImageInput.readUnsignedInt();
|
||||
mImageInput.skipBytes(length);
|
||||
@ -708,6 +727,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// TODO: Flags or list of interesting resources to parse
|
||||
// TODO: Obey ignoreMetadata
|
||||
private void readImageResources(final boolean pParseData) throws IOException {
|
||||
// TODO: Avoid unnecessary stream repositioning
|
||||
long pos = mImageInput.getFlushedPosition();
|
||||
@ -716,14 +736,14 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
long length = mImageInput.readUnsignedInt();
|
||||
|
||||
if (pParseData && length > 0) {
|
||||
if (mImageResources == null) {
|
||||
mImageResources = new ArrayList<PSDImageResource>();
|
||||
if (mMetadata.mImageResources == null) {
|
||||
mMetadata.mImageResources = new ArrayList<PSDImageResource>();
|
||||
long expectedEnd = mImageInput.getStreamPosition() + length;
|
||||
|
||||
while (mImageInput.getStreamPosition() < expectedEnd) {
|
||||
// TODO: Have PSDImageResources defer actual parsing? (Just store stream offsets)
|
||||
PSDImageResource resource = PSDImageResource.read(mImageInput);
|
||||
mImageResources.add(resource);
|
||||
mMetadata.mImageResources.add(resource);
|
||||
}
|
||||
|
||||
if (mImageInput.getStreamPosition() != expectedEnd) {
|
||||
@ -735,6 +755,8 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
mImageInput.seek(pos + length + 4);
|
||||
}
|
||||
|
||||
// TODO: Flags or list of interesting resources to parse
|
||||
// TODO: Obey ignoreMetadata
|
||||
private void readLayerAndMaskInfo(final boolean pParseData) throws IOException {
|
||||
// TODO: Make sure we are positioned correctly
|
||||
long length = mImageInput.readUnsignedInt();
|
||||
@ -755,7 +777,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
for (int i = 0; i < layerInfos.length; i++) {
|
||||
layerInfos[i] = new PSDLayerInfo(mImageInput);
|
||||
}
|
||||
mLayerInfo = Arrays.asList(layerInfos);
|
||||
mMetadata.mLayerInfo = Arrays.asList(layerInfos);
|
||||
|
||||
// TODO: Clean-up
|
||||
mImageInput.mark();
|
||||
@ -767,10 +789,10 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
// TODO: If not explicitly needed, skip layers...
|
||||
BufferedImage layer = readLayerData(layerInfo, raw, imageType);
|
||||
|
||||
// TODO: Don't show! Store in metadata somehow...
|
||||
if (layer != null) {
|
||||
showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString());
|
||||
}
|
||||
// TODO: Don't show! Store in meta data somehow...
|
||||
// if (layer != null) {
|
||||
// showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString());
|
||||
// }
|
||||
}
|
||||
|
||||
long read = mImageInput.getStreamPosition() - pos;
|
||||
@ -784,7 +806,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
long layerMaskInfoLength = mImageInput.readUnsignedInt();
|
||||
// System.out.println("GlobalLayerMaskInfo length: " + layerMaskInfoLength);
|
||||
if (layerMaskInfoLength > 0) {
|
||||
mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput);
|
||||
mMetadata.mGlobalLayerMask = new PSDGlobalLayerMask(mImageInput);
|
||||
// System.out.println("mGlobalLayerMask: " + mGlobalLayerMask);
|
||||
}
|
||||
|
||||
@ -795,6 +817,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
mImageInput.skipBytes(toSkip);
|
||||
}
|
||||
else {
|
||||
// Skip entire layer and mask section
|
||||
mImageInput.skipBytes(length);
|
||||
}
|
||||
}
|
||||
@ -838,7 +861,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
else {
|
||||
// 0 = red, 1 = green, etc
|
||||
// ?1 = transparency mask; ?2 = user supplied layer mask
|
||||
// -1 = transparency mask; -2 = user supplied layer mask
|
||||
int c = channelInfo.mChannelId == -1 ? pLayerInfo.mChannelInfo.length - 1 : channelInfo.mChannelId;
|
||||
|
||||
// NOTE: For layers, byte counts are written per channel, while for the composite data
|
||||
@ -861,7 +884,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTON:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||
default:
|
||||
// Explicitly skipped above
|
||||
throw new AssertionError(String.format("Unsupported layer data. Compression: %d", compression));
|
||||
@ -892,7 +915,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
read16bitChannel(c, imageType.getNumBands(), data16, interleavedBands, bandOffset, sourceCM, row16, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException("Unknown PSD bit depth: " + mHeader.mBits);
|
||||
throw new IIOException(String.format("Unknown PSD bit depth: %s", mHeader.mBits));
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
@ -931,6 +954,60 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
return pOriginal;
|
||||
}
|
||||
|
||||
/// Layer support
|
||||
// TODO: For now, leave as Metadata
|
||||
|
||||
/*
|
||||
int getNumLayers(int pImageIndex) throws IOException;
|
||||
|
||||
boolean hasLayers(int pImageIndex) throws IOException;
|
||||
|
||||
BufferedImage readLayer(int pImageIndex, int pLayerIndex, ImageReadParam pParam) throws IOException;
|
||||
|
||||
int getLayerWidth(int pImageIndex, int pLayerIndex) throws IOException;
|
||||
|
||||
int getLayerHeight(int pImageIndex, int pLayerIndex) throws IOException;
|
||||
|
||||
// ?
|
||||
Point getLayerOffset(int pImageIndex, int pLayerIndex) throws IOException;
|
||||
|
||||
*/
|
||||
|
||||
/// Metadata support
|
||||
// TODO
|
||||
|
||||
@Override
|
||||
public IIOMetadata getStreamMetadata() throws IOException {
|
||||
// null might be appropriate here
|
||||
// "For image formats that contain a single image, only image metadata is used."
|
||||
return super.getStreamMetadata();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int pImageIndex) throws IOException {
|
||||
// TODO: Implement
|
||||
checkBounds(pImageIndex);
|
||||
|
||||
readHeader();
|
||||
readImageResources(true);
|
||||
readLayerAndMaskInfo(true);
|
||||
|
||||
// TODO: Need to make sure compression is set in metadata, even without reading the image data!
|
||||
mMetadata.mCompression = mImageInput.readShort();
|
||||
|
||||
// mMetadata.mHeader = mHeader;
|
||||
// mMetadata.mColorData = mColorData;
|
||||
// mMetadata.mImageResources = mImageResources;
|
||||
|
||||
return mMetadata; // TODO: clone if we change to mutable metadata
|
||||
}
|
||||
|
||||
@Override
|
||||
public IIOMetadata getImageMetadata(final int imageIndex, final String formatName, final Set<String> nodeNames) throws IOException {
|
||||
// TODO: It might make sense to overload this, as there's loads of meta data in the file
|
||||
return super.getImageMetadata(imageIndex, formatName, nodeNames);
|
||||
}
|
||||
|
||||
/// Thumbnail support
|
||||
@Override
|
||||
public boolean readerSupportsThumbnails() {
|
||||
@ -944,14 +1021,14 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
List<PSDThumbnail> thumbnails = null;
|
||||
|
||||
if (mImageResources == null) {
|
||||
if (mMetadata.mImageResources == null) {
|
||||
// TODO: Need flag here, to specify what resources to read...
|
||||
readImageResources(true);
|
||||
// TODO: Skip this, requires storing some stream offsets
|
||||
readLayerAndMaskInfo(false);
|
||||
}
|
||||
|
||||
for (PSDImageResource resource : mImageResources) {
|
||||
for (PSDImageResource resource : mMetadata.mImageResources) {
|
||||
if (resource instanceof PSDThumbnail) {
|
||||
if (thumbnails == null) {
|
||||
thumbnails = new ArrayList<PSDThumbnail>();
|
||||
@ -965,13 +1042,13 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNumThumbnails(int pIndex) throws IOException {
|
||||
public int getNumThumbnails(final int pIndex) throws IOException {
|
||||
List<PSDThumbnail> thumbnails = getThumbnailResources(pIndex);
|
||||
|
||||
return thumbnails == null ? 0 : thumbnails.size();
|
||||
}
|
||||
|
||||
private PSDThumbnail getThumbnailResource(int pImageIndex, int pThumbnailIndex) throws IOException {
|
||||
private PSDThumbnail getThumbnailResource(final int pImageIndex, final int pThumbnailIndex) throws IOException {
|
||||
List<PSDThumbnail> thumbnails = getThumbnailResources(pImageIndex);
|
||||
|
||||
if (thumbnails == null) {
|
||||
@ -982,17 +1059,17 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThumbnailWidth(int pImageIndex, int pThumbnailIndex) throws IOException {
|
||||
public int getThumbnailWidth(final int pImageIndex, final int pThumbnailIndex) throws IOException {
|
||||
return getThumbnailResource(pImageIndex, pThumbnailIndex).getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getThumbnailHeight(int pImageIndex, int pThumbnailIndex) throws IOException {
|
||||
public int getThumbnailHeight(final int pImageIndex, final int pThumbnailIndex) throws IOException {
|
||||
return getThumbnailResource(pImageIndex, pThumbnailIndex).getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage readThumbnail(int pImageIndex, int pThumbnailIndex) throws IOException {
|
||||
public BufferedImage readThumbnail(final int pImageIndex, final int pThumbnailIndex) throws IOException {
|
||||
// TODO: Thumbnail progress listeners...
|
||||
PSDThumbnail thumbnail = getThumbnailResource(pImageIndex, pThumbnailIndex);
|
||||
|
||||
@ -1001,6 +1078,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
processThumbnailStarted(pImageIndex, pThumbnailIndex);
|
||||
processThumbnailComplete();
|
||||
|
||||
// TODO: Returning a cached mutable thumbnail is not really safe...
|
||||
return thumbnail.getThumbnail();
|
||||
}
|
||||
|
||||
@ -1052,11 +1130,26 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
// System.out.println("imageReader.mHeader: " + imageReader.mHeader);
|
||||
|
||||
imageReader.readImageResources(true);
|
||||
// System.out.println("imageReader.mImageResources: " + imageReader.mImageResources);
|
||||
System.out.println("imageReader.mImageResources: " + imageReader.mMetadata.mImageResources);
|
||||
System.out.println();
|
||||
|
||||
imageReader.readLayerAndMaskInfo(true);
|
||||
System.out.println("imageReader.mLayerInfo: " + imageReader.mLayerInfo);
|
||||
System.out.println("imageReader.mLayerInfo: " + imageReader.mMetadata.mLayerInfo);
|
||||
// System.out.println("imageReader.mGlobalLayerMask: " + imageReader.mGlobalLayerMask);
|
||||
System.out.println();
|
||||
|
||||
IIOMetadata metadata = imageReader.getImageMetadata(0);
|
||||
Node node;
|
||||
XMLSerializer serializer;
|
||||
|
||||
node = metadata.getAsTree(IIOMetadataFormatImpl.standardMetadataFormatName);
|
||||
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||
serializer.serialize(node, true);
|
||||
System.out.println();
|
||||
|
||||
node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME);
|
||||
serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||
serializer.serialize(node, true);
|
||||
|
||||
if (imageReader.hasThumbnails(0)) {
|
||||
int thumbnails = imageReader.getNumThumbnails(0);
|
||||
|
@ -67,8 +67,12 @@ public class PSDImageReaderSpi extends ImageReaderSpi {
|
||||
STANDARD_INPUT_TYPE,
|
||||
// new String[]{"com.twelvemkonkeys.imageio.plugins.psd.PSDImageWriterSpi"},
|
||||
null,
|
||||
true, null, null, null, null,
|
||||
true, null, null, null, null
|
||||
true, // supports standard stream metadata
|
||||
null, null, // native stream format name and class
|
||||
null, null, // extra stream formats
|
||||
true, // supports standard image metadata
|
||||
PSDMetadata.NATIVE_METADATA_FORMAT_NAME, PSDMetadata.NATIVE_METADATA_FORMAT_CLASS_NAME,
|
||||
null, null // extra image metadata formats
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,8 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.IOException;
|
||||
@ -41,6 +43,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;
|
||||
@ -50,9 +55,17 @@ class PSDImageResource {
|
||||
|
||||
mName = PSDUtil.readPascalString(pInput);
|
||||
|
||||
// Skip pad
|
||||
int nameSize = mName.length() + 1;
|
||||
if (nameSize % 2 != 0) {
|
||||
pInput.readByte();
|
||||
}
|
||||
|
||||
mSize = pInput.readUnsignedInt();
|
||||
readData(pInput);
|
||||
|
||||
// TODO: Sanity check reading here?
|
||||
|
||||
// Data is even-padded
|
||||
if (mSize % 2 != 0) {
|
||||
pInput.read();
|
||||
@ -84,7 +97,10 @@ class PSDImageResource {
|
||||
protected StringBuilder toStringBuilder() {
|
||||
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
|
||||
|
||||
builder.append(resourceTypeForId(mId));
|
||||
String fakeType = resourceTypeForId(mId);
|
||||
if (fakeType != null) {
|
||||
builder.append("(").append(fakeType).append(")");
|
||||
}
|
||||
|
||||
builder.append("[ID: 0x");
|
||||
builder.append(Integer.toHexString(mId));
|
||||
@ -103,26 +119,32 @@ 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:
|
||||
case PSD.RES_VERSION_INFO:
|
||||
case PSD.RES_EXIF_DATA_1:
|
||||
// case PSD.RES_EXIF_DATA_3:
|
||||
case PSD.RES_XMP_DATA:
|
||||
case PSD.RES_PRINT_SCALE:
|
||||
case PSD.RES_PIXEL_ASPECT_RATIO:
|
||||
case PSD.RES_PRINT_FLAGS_INFORMATION:
|
||||
return "";
|
||||
return null;
|
||||
default:
|
||||
try {
|
||||
for (Field field : PSD.class.getDeclaredFields()) {
|
||||
if (field.getName().startsWith("RES_") && field.getInt(null) == pId) {
|
||||
return "(" + field.getName().substring(4) + ")";
|
||||
String name = field.getName().substring(4);
|
||||
return StringUtil.lispToCamel(name.replace("_", "-").toLowerCase(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException ignore) {
|
||||
}
|
||||
|
||||
return "(unknown resource)";
|
||||
return "UnknownResource";
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,15 +166,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:
|
||||
|
@ -98,14 +98,12 @@ class PSDLayerInfo {
|
||||
mLayerName = PSDUtil.readPascalString(pInput);
|
||||
|
||||
int layerNameSize = mLayerName.length() + 1;
|
||||
// readPascalString has already read pad byte for word alignment
|
||||
if (layerNameSize % 2 != 0) {
|
||||
layerNameSize++;
|
||||
}
|
||||
// Skip two more pad bytes if needed
|
||||
|
||||
// Skip pad bytes for long word alignment
|
||||
if (layerNameSize % 4 != 0) {
|
||||
pInput.skipBytes(2);
|
||||
layerNameSize += 2;
|
||||
int skip = layerNameSize % 4;
|
||||
pInput.skipBytes(skip);
|
||||
layerNameSize += skip;
|
||||
}
|
||||
|
||||
// TODO: There's some data skipped here...
|
||||
|
@ -0,0 +1,734 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.FilterIterator;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PSDMetadata
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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 {
|
||||
|
||||
// TODO: Decide on image/stream metadata...
|
||||
static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0";
|
||||
static final String NATIVE_METADATA_FORMAT_CLASS_NAME = "com.twelvemonkeys.imageio.plugins.psd.PSDMetadataFormat";
|
||||
|
||||
PSDHeader mHeader;
|
||||
PSDColorData mColorData;
|
||||
int mCompression = -1;
|
||||
List<PSDImageResource> mImageResources;
|
||||
PSDGlobalLayerMask mGlobalLayerMask;
|
||||
List<PSDLayerInfo> mLayerInfo;
|
||||
|
||||
static final String[] COLOR_MODES = {
|
||||
"MONOCHROME", "GRAYSCALE", "INDEXED", "RGB", "CMYK", null, null, "MULTICHANNEL", "DUOTONE", "LAB"
|
||||
};
|
||||
|
||||
static final String[] DISPLAY_INFO_CS = {
|
||||
"RGB", "HSB", "CMYK", "PANTONE", "FOCOLTONE", "TRUMATCH", "TOYO", "LAB", "GRAYSCALE", null, "HKS", "DIC",
|
||||
null, // 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() {
|
||||
IIOMetadataNode root = new IIOMetadataNode(NATIVE_METADATA_FORMAT_NAME);
|
||||
|
||||
root.appendChild(createHeaderNode());
|
||||
|
||||
if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) {
|
||||
root.appendChild(createPaletteNode());
|
||||
}
|
||||
|
||||
if (mImageResources != null && !mImageResources.isEmpty()) {
|
||||
root.appendChild(createImageResourcesNode());
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private Node createHeaderNode() {
|
||||
IIOMetadataNode header = new IIOMetadataNode("Header");
|
||||
|
||||
header.setAttribute("type", "PSD");
|
||||
header.setAttribute("version", "1");
|
||||
header.setAttribute("channels", Integer.toString(mHeader.mChannels));
|
||||
header.setAttribute("height", Integer.toString(mHeader.mHeight));
|
||||
header.setAttribute("width", Integer.toString(mHeader.mWidth));
|
||||
header.setAttribute("bits", Integer.toString(mHeader.mBits));
|
||||
header.setAttribute("mode", COLOR_MODES[mHeader.mMode]);
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
private Node createImageResourcesNode() {
|
||||
IIOMetadataNode resource = new IIOMetadataNode("ImageResources");
|
||||
IIOMetadataNode node;
|
||||
|
||||
for (PSDImageResource imageResource : mImageResources) {
|
||||
// TODO: Always add name (if set) and id (as resourceId) to all nodes?
|
||||
// Resource Id is useful for people with access to the PSD spec..
|
||||
|
||||
if (imageResource instanceof 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;
|
||||
|
||||
node = new IIOMetadataNode("AlphaChannelInfo");
|
||||
|
||||
for (String name : alphaChannelInfo.mNames) {
|
||||
IIOMetadataNode nameNode = new IIOMetadataNode("Name");
|
||||
nameNode.setAttribute("value", name);
|
||||
node.appendChild(nameNode);
|
||||
}
|
||||
}
|
||||
else if (imageResource instanceof PSDDisplayInfo) {
|
||||
PSDDisplayInfo displayInfo = (PSDDisplayInfo) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("DisplayInfo");
|
||||
node.setAttribute("colorSpace", DISPLAY_INFO_CS[displayInfo.mColorSpace]);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (short color : displayInfo.mColors) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(" ");
|
||||
}
|
||||
builder.append(Integer.toString(color));
|
||||
}
|
||||
|
||||
node.setAttribute("colors", builder.toString());
|
||||
node.setAttribute("opacity", Integer.toString(displayInfo.mOpacity));
|
||||
node.setAttribute("kind", DISPLAY_INFO_KINDS[displayInfo.mKind]);
|
||||
}
|
||||
else if (imageResource instanceof PSDGridAndGuideInfo) {
|
||||
PSDGridAndGuideInfo info = (PSDGridAndGuideInfo) imageResource;
|
||||
|
||||
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) {
|
||||
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
||||
PSDXMPData xmp = (PSDXMPData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("XMP");
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Generic resource..
|
||||
node = new IIOMetadataNode(PSDImageResource.resourceTypeForId(imageResource.mId));
|
||||
}
|
||||
|
||||
// TODO: More resources
|
||||
|
||||
node.setAttribute("resourceId", String.format("0x%04x", imageResource.mId));
|
||||
resource.appendChild(node);
|
||||
}
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
/// Standard format support
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardChromaNode() {
|
||||
IIOMetadataNode chroma_node = new IIOMetadataNode("Chroma");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("ColorSpaceType");
|
||||
String cs;
|
||||
switch (mHeader.mMode) {
|
||||
case PSD.COLOR_MODE_MONOCHROME:
|
||||
case PSD.COLOR_MODE_GRAYSCALE:
|
||||
case PSD.COLOR_MODE_DUOTONE: // Rationale: Spec says treat as gray...
|
||||
cs = "GRAY";
|
||||
break;
|
||||
case PSD.COLOR_MODE_RGB:
|
||||
case PSD.COLOR_MODE_INDEXED:
|
||||
cs = "RGB";
|
||||
break;
|
||||
case PSD.COLOR_MODE_CMYK:
|
||||
cs = "CMYK";
|
||||
break;
|
||||
case PSD.COLOR_MODE_MULTICHANNEL:
|
||||
cs = getMultiChannelCS(mHeader.mChannels);
|
||||
break;
|
||||
case PSD.COLOR_MODE_LAB:
|
||||
cs = "Lab";
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
node.setAttribute("name", cs);
|
||||
chroma_node.appendChild(node);
|
||||
|
||||
// TODO: Channels might be 5 for RGB + A + Mask... Probably not correct
|
||||
node = new IIOMetadataNode("NumChannels");
|
||||
node.setAttribute("value", Integer.toString(mHeader.mChannels));
|
||||
chroma_node.appendChild(node);
|
||||
|
||||
// TODO: Check if this is correct with bitmap (monchrome)
|
||||
node = new IIOMetadataNode("BlackIsZero");
|
||||
node.setAttribute("value", "true");
|
||||
chroma_node.appendChild(node);
|
||||
|
||||
if (mHeader.mMode == PSD.COLOR_MODE_INDEXED) {
|
||||
node = createPaletteNode();
|
||||
chroma_node.appendChild(node);
|
||||
}
|
||||
|
||||
// TODO: Hardcode background color to white?
|
||||
// if (bKGD_present) {
|
||||
// if (bKGD_colorType == PNGImageReader.PNG_COLOR_PALETTE) {
|
||||
// node = new IIOMetadataNode("BackgroundIndex");
|
||||
// node.setAttribute("value", Integer.toString(bKGD_index));
|
||||
// } else {
|
||||
// node = new IIOMetadataNode("BackgroundColor");
|
||||
// int r, g, b;
|
||||
//
|
||||
// if (bKGD_colorType == PNGImageReader.PNG_COLOR_GRAY) {
|
||||
// r = g = b = bKGD_gray;
|
||||
// } else {
|
||||
// r = bKGD_red;
|
||||
// g = bKGD_green;
|
||||
// b = bKGD_blue;
|
||||
// }
|
||||
// node.setAttribute("red", Integer.toString(r));
|
||||
// node.setAttribute("green", Integer.toString(g));
|
||||
// node.setAttribute("blue", Integer.toString(b));
|
||||
// }
|
||||
// chroma_node.appendChild(node);
|
||||
// }
|
||||
|
||||
return chroma_node;
|
||||
}
|
||||
|
||||
private IIOMetadataNode createPaletteNode() {
|
||||
IIOMetadataNode node = new IIOMetadataNode("Palette");
|
||||
IndexColorModel cm = mColorData.getIndexColorModel();
|
||||
|
||||
for (int i = 0; i < cm.getMapSize(); i++) {
|
||||
IIOMetadataNode entry = new IIOMetadataNode("PaletteEntry");
|
||||
entry.setAttribute("index", Integer.toString(i));
|
||||
entry.setAttribute("red", Integer.toString(cm.getRed(i)));
|
||||
entry.setAttribute("green", Integer.toString(cm.getGreen(i)));
|
||||
entry.setAttribute("blue", Integer.toString(cm.getBlue(i)));
|
||||
|
||||
node.appendChild(entry);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private String getMultiChannelCS(short pChannels) {
|
||||
if (pChannels < 16) {
|
||||
return Integer.toHexString(pChannels) + "CLR";
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compression_node = new IIOMetadataNode("Compression");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("CompressionTypeName");
|
||||
String compression;
|
||||
switch (mCompression) {
|
||||
case PSD.COMPRESSION_NONE:
|
||||
compression = "none";
|
||||
break;
|
||||
case PSD.COMPRESSION_RLE:
|
||||
compression = "packbits";
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||
compression = "zip";
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
node.setAttribute("value", compression);
|
||||
compression_node.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("Lossless");
|
||||
node.setAttribute("value", "true");
|
||||
compression_node.appendChild(node);
|
||||
|
||||
return compression_node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode data_node = new IIOMetadataNode("Data");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("PlanarConfiguration");
|
||||
node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec
|
||||
data_node.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("SampleFormat");
|
||||
node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral");
|
||||
data_node.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);
|
||||
|
||||
// TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed?
|
||||
|
||||
return data_node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimension_node = 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);
|
||||
|
||||
node = new IIOMetadataNode("ImageOrientation");
|
||||
node.setAttribute("value", "Normal");
|
||||
dimension_node.appendChild(node);
|
||||
|
||||
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);
|
||||
|
||||
node = new IIOMetadataNode("VerticalPixelSize");
|
||||
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes)));
|
||||
dimension_node.appendChild(node);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
/*
|
||||
<!ELEMENT "HorizontalPixelOffset" EMPTY>
|
||||
<!-- The horizonal position, in pixels, where the image should be
|
||||
rendered onto a raster display -->
|
||||
<!ATTLIST "HorizontalPixelOffset" "value" #CDATA #REQUIRED>
|
||||
<!-- Data type: Integer -->
|
||||
|
||||
<!ELEMENT "VerticalPixelOffset" EMPTY>
|
||||
<!-- The vertical position, in pixels, where the image should be
|
||||
rendered onto a raster display -->
|
||||
<!ATTLIST "VerticalPixelOffset" "value" #CDATA #REQUIRED>
|
||||
<!-- Data type: Integer -->
|
||||
|
||||
<!ELEMENT "HorizontalScreenSize" EMPTY>
|
||||
<!-- The width, in pixels, of the raster display into which the
|
||||
image should be rendered -->
|
||||
<!ATTLIST "HorizontalScreenSize" "value" #CDATA #REQUIRED>
|
||||
<!-- Data type: Integer -->
|
||||
|
||||
<!ELEMENT "VerticalScreenSize" EMPTY>
|
||||
<!-- The height, in pixels, of the raster display into which the
|
||||
image should be rendered -->
|
||||
<!ATTLIST "VerticalScreenSize" "value" #CDATA #REQUIRED>
|
||||
<!-- Data type: Integer -->
|
||||
|
||||
*/
|
||||
return dimension_node;
|
||||
}
|
||||
|
||||
private static float asMM(final short pUnit, final float pResolution) {
|
||||
// Unit: 1 -> pixels per inch, 2 -> pixels pr cm
|
||||
return (pUnit == 1 ? 25.4f : 10) / pResolution;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDocumentNode() {
|
||||
IIOMetadataNode document_node = new IIOMetadataNode("Document");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("FormatVersion");
|
||||
node.setAttribute("value", "1"); // PSD format version is always 1
|
||||
document_node.appendChild(node);
|
||||
|
||||
// Get EXIF data if present
|
||||
Iterator<PSDEXIF1Data> exif = getResources(PSDEXIF1Data.class);
|
||||
if (exif.hasNext()) {
|
||||
PSDEXIF1Data data = exif.next();
|
||||
|
||||
// Get the EXIF DateTime (aka ModifyDate) tag if present
|
||||
PSDEXIF1Data.Entry dateTime = data.mDirectory.get(0x0132); // TODO: Constant
|
||||
if (dateTime != null) {
|
||||
node = new IIOMetadataNode("ImageModificationTime");
|
||||
// Format: "YYYY:MM:DD hh:mm:ss" (with quotes! :-P)
|
||||
String value = dateTime.getValueAsString();
|
||||
|
||||
node.setAttribute("year", value.substring(1, 5));
|
||||
node.setAttribute("month", value.substring(6, 8));
|
||||
node.setAttribute("day", value.substring(9, 11));
|
||||
node.setAttribute("hour", value.substring(12, 14));
|
||||
node.setAttribute("minute", value.substring(15, 17));
|
||||
node.setAttribute("second", value.substring(18, 20));
|
||||
|
||||
document_node.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
return document_node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
// TODO: CaptionDigest?, EXIF, XMP
|
||||
|
||||
Iterator<PSDImageResource> textResources = getResources(PSDEXIF1Data.class, PSDXMPData.class);
|
||||
|
||||
while (textResources.hasNext()) {
|
||||
PSDImageResource textResource = textResources.next();
|
||||
|
||||
}
|
||||
|
||||
// int numEntries = tEXt_keyword.size() +
|
||||
// iTXt_keyword.size() + zTXt_keyword.size();
|
||||
// if (numEntries == 0) {
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTileNode() {
|
||||
return super.getStandardTileNode();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTransparencyNode() {
|
||||
IIOMetadataNode transparency_node = new IIOMetadataNode("Transparency");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("Alpha");
|
||||
node.setAttribute("value", hasAlpha() ? "nonpremultipled" : "none"); // TODO: Check spec
|
||||
transparency_node.appendChild(node);
|
||||
|
||||
return transparency_node;
|
||||
}
|
||||
|
||||
private boolean hasAlpha() {
|
||||
return mHeader.mMode == PSD.COLOR_MODE_RGB && mHeader.mChannels >= 4 ||
|
||||
mHeader.mMode == PSD.COLOR_MODE_CMYK & mHeader.mChannels >= 5;
|
||||
}
|
||||
|
||||
<T extends PSDImageResource> Iterator<T> getResources(final Class<T> pResourceType) {
|
||||
// NOTE: The cast here is wrong, strictly speaking, but it does not matter...
|
||||
@SuppressWarnings({"unchecked"})
|
||||
Iterator<T> iterator = (Iterator<T>) mImageResources.iterator();
|
||||
|
||||
return new FilterIterator<T>(iterator, new FilterIterator.Filter<T>() {
|
||||
public boolean accept(final T pElement) {
|
||||
return pResourceType.isInstance(pElement);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Iterator<PSDImageResource> getResources(final Class<? extends PSDImageResource>... pResourceTypes) {
|
||||
Iterator<PSDImageResource> iterator = mImageResources.iterator();
|
||||
|
||||
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||
public boolean accept(final PSDImageResource pElement) {
|
||||
for (Class<?> type : pResourceTypes) {
|
||||
if (type.isInstance(pElement)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* PSDMetadataFormat
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDMetadataFormat.java,v 1.0 Nov 4, 2009 5:27:53 PM haraldk Exp$
|
||||
*/
|
||||
public final class PSDMetadataFormat extends IIOMetadataFormatImpl {
|
||||
|
||||
private final static PSDMetadataFormat sInstance = new PSDMetadataFormat();
|
||||
|
||||
/**
|
||||
* Private constructor.
|
||||
* <p/>
|
||||
* The {@link javax.imageio.metadata.IIOMetadata} class will instantiate this class
|
||||
* by reflection, invoking the static {@code getInstance()} method.
|
||||
*
|
||||
* @see javax.imageio.metadata.IIOMetadata#getMetadataFormat
|
||||
* @see #getInstance()
|
||||
*/
|
||||
private PSDMetadataFormat() {
|
||||
// Defines the root element
|
||||
super(PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_SOME);
|
||||
|
||||
// root -> PSDHeader
|
||||
// TODO: How do I specify that the header is required?
|
||||
addElement("Header", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_EMPTY);
|
||||
|
||||
addAttribute("Header", "type", DATATYPE_STRING, false, "PSD", Arrays.asList("PSD", "PSB"));
|
||||
addAttribute("Header", "version", DATATYPE_INTEGER, false, "1", Arrays.asList("1"));
|
||||
|
||||
addAttribute("Header", "channels", DATATYPE_INTEGER, true, null, "1", "24", true, true);
|
||||
// rows?
|
||||
addAttribute("Header", "height", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
// columns?
|
||||
addAttribute("Header", "width", DATATYPE_INTEGER, true, null, "1", "30000", true, true);
|
||||
addAttribute("Header", "bits", DATATYPE_INTEGER, true, null, Arrays.asList("1", "8", "16"));
|
||||
// TODO: Consider using more readable names?!
|
||||
addAttribute("Header", "mode", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.COLOR_MODES));
|
||||
|
||||
/*
|
||||
Contains the required data to define the color mode.
|
||||
|
||||
For indexed color images, the count will be equal to 768, and the mode data
|
||||
will contain the color table for the image, in non-interleaved order.
|
||||
|
||||
For duotone images, the mode data will contain the duotone specification,
|
||||
the format of which is not documented. Non-Photoshop readers can treat
|
||||
the duotone image as a grayscale image, and keep the duotone specification
|
||||
around as a black box for use when saving the file.
|
||||
*/
|
||||
// root -> Palette
|
||||
// Color map for indexed, optional
|
||||
// NOTE: Palette, PaletteEntry naming taken from the standard format, native PSD naming is ColorModeData
|
||||
// NOTE: PSD stores these as 256 Red, 256 Green, 256 Blue.. Should we do the same in the meta data?
|
||||
addElement("Palette", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, 256, 256); // 768 = 256 * 3
|
||||
addElement("PaletteEntry", "Palette", CHILD_POLICY_EMPTY);
|
||||
addAttribute("PaletteEntry", "index", DATATYPE_INTEGER, true, null, "0", "255", true, true);
|
||||
addAttribute("PaletteEntry", "red", DATATYPE_INTEGER, true, null, "0", "255", true, true);
|
||||
addAttribute("PaletteEntry", "green", DATATYPE_INTEGER, true, null, "0", "255", true, true);
|
||||
addAttribute("PaletteEntry", "blue", DATATYPE_INTEGER, true, null, "0", "255", true, true);
|
||||
// No alpha allowed in indexed color PSD
|
||||
|
||||
// TODO: Duotone spec, optional (use same element as palette?)
|
||||
// Or use object or raw bytes..
|
||||
|
||||
// root -> ImageResources
|
||||
// Image resources, optional
|
||||
addElement("ImageResources", PSDMetadata.NATIVE_METADATA_FORMAT_NAME, CHILD_POLICY_SEQUENCE); // SOME?
|
||||
|
||||
// root -> ImageResources -> ImageResource
|
||||
// Generic resource
|
||||
addElement("ImageResource", "ImageResources", CHILD_POLICY_ALL);
|
||||
// TODO: Allow arbitrary values to be added as a generic resource...
|
||||
|
||||
// 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, 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_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_STRING, true, null, Arrays.asList(PSDMetadata.DISPLAY_INFO_KINDS));
|
||||
|
||||
// root -> ImageResources -> EXIF
|
||||
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?)
|
||||
|
||||
// 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
|
||||
addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addBooleanAttribute("PrintFlags", "labels", false, false);
|
||||
addBooleanAttribute("PrintFlags", "cropMasks", false, false);
|
||||
addBooleanAttribute("PrintFlags", "colorBars", false, false);
|
||||
addBooleanAttribute("PrintFlags", "registrationMarks", false, false);
|
||||
addBooleanAttribute("PrintFlags", "negative", false, false);
|
||||
addBooleanAttribute("PrintFlags", "flip", false, false);
|
||||
addBooleanAttribute("PrintFlags", "interpolate", false, false);
|
||||
addBooleanAttribute("PrintFlags", "caption", false, false);
|
||||
|
||||
// root -> ImageResources -> PrintFlagsInformation
|
||||
addElement("PrintFlagsInformation", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addAttribute("PrintFlagsInformation", "version", DATATYPE_INTEGER, false, "1");
|
||||
addBooleanAttribute("PrintFlagsInformation", "cropMarks", false, false);
|
||||
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);
|
||||
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);
|
||||
addAttribute("ResolutionInfo", "vResUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.RESOLUTION_UNITS));
|
||||
addAttribute("ResolutionInfo", "heightUnit", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.DIMENSION_UNITS));
|
||||
|
||||
// root -> ImageResources -> UnicodeAlphaNames
|
||||
addElement("UnicodeAlphaNames", "ImageResources", 0, Integer.MAX_VALUE);
|
||||
addChildElement("UnicodeAlphaNames", "Name"); // TODO: Does this really work?
|
||||
|
||||
|
||||
// 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("XMP", Document.class, true, null);
|
||||
|
||||
// TODO: Layers
|
||||
//addElement("ChannelSourceDestinationRange", "LayerSomething", CHILD_POLICY_CHOICE);
|
||||
|
||||
// TODO: Global layer mask info
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public boolean canNodeAppear(final String pElementName, final ImageTypeSpecifier pImageType) {
|
||||
// TODO: PSDColorData and PaletteEntry only for indexed color model
|
||||
throw new UnsupportedOperationException("Method canNodeAppear not implemented"); // TODO: Implement
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shared instance of the {@code PSDMetadataFormat}.
|
||||
*
|
||||
* @return the shared instance.
|
||||
* @see javax.imageio.metadata.IIOMetadata#getMetadataFormat
|
||||
*/
|
||||
public static PSDMetadataFormat getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
@ -26,14 +26,14 @@ final class PSDPrintFlags extends PSDImageResource {
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mLabels = pInput.readUnsignedByte() != 0;
|
||||
mCropMasks = pInput.readUnsignedByte() != 0;
|
||||
mColorBars = pInput.readUnsignedByte() != 0;
|
||||
mRegistrationMarks = pInput.readUnsignedByte() != 0;
|
||||
mNegative = pInput.readUnsignedByte() != 0;
|
||||
mFlip = pInput.readUnsignedByte() != 0;
|
||||
mInterpolate = pInput.readUnsignedByte() != 0;
|
||||
mCaption = pInput.readUnsignedByte() != 0;
|
||||
mLabels = pInput.readBoolean();
|
||||
mCropMasks = pInput.readBoolean();
|
||||
mColorBars = pInput.readBoolean();
|
||||
mRegistrationMarks = pInput.readBoolean();
|
||||
mNegative = pInput.readBoolean();
|
||||
mFlip = pInput.readBoolean();
|
||||
mInterpolate = pInput.readBoolean();
|
||||
mCaption = pInput.readBoolean();
|
||||
|
||||
pInput.skipBytes(mSize - 8);
|
||||
}
|
||||
|
@ -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);
|
||||
@ -24,8 +24,8 @@ final class PSDPrintFlagsInformation extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mVersion = pInput.readUnsignedShort();
|
||||
mCropMasks = pInput.readUnsignedByte() != 0;
|
||||
mField = pInput.readUnsignedByte();
|
||||
mCropMasks = pInput.readBoolean();
|
||||
mField = pInput.readUnsignedByte(); // TODO: Is this really pad?
|
||||
mBleedWidth = pInput.readUnsignedInt();
|
||||
mBleedScale = pInput.readUnsignedShort();
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -50,13 +50,12 @@ class PSDResolutionInfo extends PSDImageResource {
|
||||
// WORD HeightUnit; /* 1=in, 2=cm, 3=pt, 4=picas, 5=columns */
|
||||
// } RESOLUTIONINFO;
|
||||
|
||||
private float mHRes;
|
||||
private short mHResUnit;
|
||||
private short mWidthUnit;
|
||||
private float mVRes;
|
||||
private short mVResUnit;
|
||||
private short mHeightUnit;
|
||||
|
||||
float mHRes;
|
||||
short mHResUnit;
|
||||
short mWidthUnit;
|
||||
float mVRes;
|
||||
short mVResUnit;
|
||||
short mHeightUnit;
|
||||
|
||||
PSDResolutionInfo(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
|
@ -37,7 +37,7 @@ class PSDThumbnail extends PSDImageResource {
|
||||
*/
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// TODO: Support for RAW RGB (format == 0)
|
||||
// TODO: Support for RAW RGB (format == 0): Extract RAW reader from PICT RAW QuickTime decompressor
|
||||
int format = pInput.readInt();
|
||||
switch (format) {
|
||||
case 0:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -31,8 +31,10 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.io.enc.DecoderStream;
|
||||
import com.twelvemonkeys.io.enc.PackBitsDecoder;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.DataInput;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.zip.ZipInputStream;
|
||||
@ -58,22 +60,21 @@ final class PSDUtil {
|
||||
}
|
||||
|
||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||
static String readPascalString(ImageInputStream pInput) throws IOException {
|
||||
// TODO: Is this REALLY different from the previous method? Maybe the pad should not be read..
|
||||
static String readPascalString(final DataInput pInput) throws IOException {
|
||||
int length = pInput.readUnsignedByte();
|
||||
// int length = pInput.readUnsignedShort();
|
||||
byte[] bytes = new byte[length];
|
||||
pInput.readFully(bytes);
|
||||
if (length % 2 == 0) {
|
||||
pInput.readByte(); // Pad
|
||||
}
|
||||
return new String(bytes);
|
||||
|
||||
return StringUtil.decode(bytes, 0, bytes.length, "ASCII");
|
||||
}
|
||||
|
||||
static String readPascalStringByte(ImageInputStream pInput) throws IOException {
|
||||
int length = pInput.readUnsignedByte();
|
||||
byte[] bytes = new byte[length];
|
||||
static String readUTF16String(final DataInput pInput) throws IOException {
|
||||
int length = pInput.readInt();
|
||||
byte[] bytes = new byte[length * 2];
|
||||
pInput.readFully(bytes);
|
||||
return new String(bytes);
|
||||
|
||||
return StringUtil.decode(bytes, 0, bytes.length, "UTF-16");
|
||||
}
|
||||
|
||||
static DataInputStream createPackBitsStream(final ImageInputStream pInput, long pLength) {
|
||||
|
@ -0,0 +1,57 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* PSDVersionInfo
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDVersionInfo.java,v 1.0 Nov 6, 2009 1:02:19 PM haraldk Exp$
|
||||
*/
|
||||
final class PSDVersionInfo extends PSDImageResource {
|
||||
|
||||
int mVersion;
|
||||
boolean mHasRealMergedData;
|
||||
String mWriter;
|
||||
String mReader;
|
||||
int mFileVersion;
|
||||
|
||||
PSDVersionInfo(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
/*
|
||||
4 bytes version
|
||||
1 byte hasRealMergedData
|
||||
Unicode string: writer name
|
||||
Unicode string: reader name
|
||||
4 bytes file version.
|
||||
*/
|
||||
|
||||
mVersion = pInput.readInt();
|
||||
mHasRealMergedData = pInput.readBoolean();
|
||||
|
||||
mWriter = PSDUtil.readUTF16String(pInput);
|
||||
mReader = PSDUtil.readUTF16String(pInput);
|
||||
|
||||
mFileVersion = pInput.readInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
|
||||
builder.append(", version: ").append(mVersion);
|
||||
builder.append(", hasRealMergedData: ").append(mHasRealMergedData);
|
||||
builder.append(", writer: ").append(mWriter);
|
||||
builder.append(", reader: ").append(mReader);
|
||||
builder.append(", file version: ").append(mFileVersion);
|
||||
builder.append("]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys-servlet</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3-SNAPSHOT</version>
|
||||
<name>TwelveMonkeys Servlet</name>
|
||||
|
||||
<parent>
|
||||
@ -89,6 +89,22 @@
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.2</version>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Implementation-Title>${project.name}</Implementation-Title>
|
||||
<Implementation-Vendor>TwelveMonkeys</Implementation-Vendor>
|
||||
<Implementation-Version>${project.version}</Implementation-Version>
|
||||
<Implementation-URL>http://github.com/haraldk/TwelveMonkeys</Implementation-URL>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user