mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-19 00:00:03 -04:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 779f720aa0 | |||
| 7ea9b8f537 | |||
| d2742b36de | |||
| 3d4d60c664 | |||
| 1c58bdbb81 | |||
| 4b7a17ba30 | |||
| 261c6f8038 | |||
| ef09b599f6 | |||
| 20d6e35640 | |||
| d97a0cc00b | |||
| c7fd5b3dd9 | |||
| 64b21b83bb | |||
| d24c2c1b08 | |||
| aad80d043f | |||
| effd80d42f | |||
| 2ab9cbadee | |||
| d1cc3deec1 | |||
| b7124585c5 | |||
| 69d77071b9 | |||
| ceca94135b | |||
| dbca2fc099 | |||
| 54cf727dee | |||
| bf5c6e9d47 | |||
| def1d47344 | |||
| 5dab7eb1ff | |||
| 9849aeb2a7 | |||
| 0a71af5405 | |||
| 96a74e0b81 | |||
| c801926a02 | |||
| ebc365528a | |||
| 317ed16814 | |||
| ecc79e0478 | |||
| 8572633686 | |||
| 67b985bc1d | |||
| 9c443f28e3 | |||
| 46ab06f471 | |||
| 18b86f8d26 | |||
| 82fdde897d | |||
| f49a487c88 | |||
| fb2c555d21 | |||
| 4bd0763d48 | |||
| 4674b9e344 | |||
| 68b30413ba | |||
| ebd9153e40 | |||
| 669f575585 | |||
| d04d4a9a97 | |||
| 17e8de8c99 | |||
| ef7029f306 | |||
| 30b97483bd |
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>twelvemonkeys-core</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys Core</name>
|
||||
<description>
|
||||
The TwelveMonkeys Core library. Contains common utility classes.
|
||||
@@ -53,6 +53,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>
|
||||
</project>
|
||||
@@ -52,6 +52,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
* The stream will use a default decode buffer size.
|
||||
*
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
@@ -59,9 +60,24 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
*
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
* @param pBufferSize the size of the decode buffer
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
||||
super(pStream);
|
||||
mDecoder = pDecoder;
|
||||
mBuffer = new byte[1024];
|
||||
mBuffer = new byte[pBufferSize];
|
||||
mBufferPos = 0;
|
||||
mBufferLimit = 0;
|
||||
}
|
||||
@@ -142,7 +158,7 @@ public final class DecoderStream extends FilterInputStream {
|
||||
|
||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
||||
long total = 0;
|
||||
|
||||
|
||||
while (total < pLength) {
|
||||
int avail = mBufferLimit - mBufferPos;
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ public final class PackBitsDecoder implements Decoder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}.
|
||||
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
|
||||
* <p/>
|
||||
* As some implementations of PackBits-like encoders treat {@code -128} as length of
|
||||
* a compressed run, instead of a no-op, it's possible to disable no-ops
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -51,13 +51,13 @@ public class XMLSerializer {
|
||||
// Main problem: Sun's Java 5 does not have LS 3.0 support
|
||||
// This class has no dependencies, which probably makes it more useful
|
||||
|
||||
// TODO: Don't insert initial and ending line-break for text-nodes
|
||||
// TODO: Support not inserting line-breaks, to preserve space
|
||||
// TODO: Support line breaking (at configurable width)
|
||||
// TODO: Support skipping XML declaration?
|
||||
// TODO: Support standalone?
|
||||
// TODO: Support more than version 1.0?
|
||||
// TODO: Consider using IOException to communicate trouble, rather than RTE,
|
||||
// to be more compatible...
|
||||
// TODO: Support not inserting line-breaks, to preserve space
|
||||
|
||||
// TODO: Idea: Create a SerializationContext that stores attributes on
|
||||
// serialization, to keep the serialization thread-safe
|
||||
@@ -75,32 +75,51 @@ public class XMLSerializer {
|
||||
mContext = new SerializationContext();
|
||||
}
|
||||
|
||||
public void setIndentation(String pIndent) {
|
||||
public final void setIndentation(String pIndent) {
|
||||
mContext.indent = pIndent != null ? pIndent : " ";
|
||||
}
|
||||
|
||||
public void setStripComments(boolean pStrip) {
|
||||
public final void setStripComments(boolean pStrip) {
|
||||
mContext.stripComments = pStrip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire document, along with the XML declaration
|
||||
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||
*
|
||||
* @param pDocument the document to serialize.
|
||||
*/
|
||||
public void serialize(final Document pDocument) {
|
||||
serialize(pDocument, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire sub tree starting at {@code pRootNode}, along with an optional XML declaration
|
||||
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||
*
|
||||
* @param pRootNode the root node to serialize.
|
||||
* @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}.
|
||||
*/
|
||||
public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) {
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(mOutput, mEncoding));
|
||||
try {
|
||||
writeXMLDeclararion(out);
|
||||
writeXML(out, pDocument, mContext.copy());
|
||||
if (pWriteXMLDeclaration) {
|
||||
writeXMLDeclaration(out);
|
||||
}
|
||||
writeXML(out, pRootNode, mContext.copy());
|
||||
}
|
||||
finally {
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeXMLDeclararion(final PrintWriter pOut) {
|
||||
private void writeXMLDeclaration(final PrintWriter pOut) {
|
||||
pOut.print("<?xml version=\"1.0\" encoding=\"");
|
||||
pOut.print(mEncoding.name());
|
||||
pOut.println("\"?>");
|
||||
}
|
||||
|
||||
private void writeXML(final PrintWriter pOut, final Document pDocument, final SerializationContext pContext) {
|
||||
private void writeXML(final PrintWriter pOut, final Node pDocument, final SerializationContext pContext) {
|
||||
writeNodeRecursive(pOut, pDocument, pContext);
|
||||
}
|
||||
|
||||
@@ -133,7 +152,7 @@ public class XMLSerializer {
|
||||
writeComment(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.PROCESSING_INSTRUCTION_NODE:
|
||||
writeProcessingInstruction(pOut, pNode);
|
||||
writeProcessingInstruction(pOut, (ProcessingInstruction) pNode);
|
||||
break;
|
||||
case Node.ATTRIBUTE_NODE:
|
||||
throw new IllegalArgumentException("Malformed input Document: Attribute nodes should only occur inside Element nodes");
|
||||
@@ -148,9 +167,16 @@ public class XMLSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
private void writeProcessingInstruction(final PrintWriter pOut, final Node pNode) {
|
||||
private void writeProcessingInstruction(final PrintWriter pOut, final ProcessingInstruction pNode) {
|
||||
pOut.print("\n<?");
|
||||
pOut.print(pNode.getNodeValue());
|
||||
pOut.print(pNode.getTarget());
|
||||
String value = pNode.getData();
|
||||
|
||||
if (value != null) {
|
||||
pOut.print(" ");
|
||||
pOut.print(value);
|
||||
}
|
||||
|
||||
pOut.println("?>");
|
||||
}
|
||||
|
||||
@@ -161,8 +187,11 @@ public class XMLSerializer {
|
||||
pOut.print(maybeEscapeElementValue(value));
|
||||
}
|
||||
else if (!StringUtil.isEmpty(value)) {
|
||||
indentToLevel(pOut, pContext);
|
||||
pOut.println(maybeEscapeElementValue(value.trim()));
|
||||
String escapedValue = maybeEscapeElementValue(value.trim());
|
||||
//if (escapedValue.length() + (pContext.level * pContext.indent.length()) > 78) {
|
||||
indentToLevel(pOut, pContext);
|
||||
//}
|
||||
pOut.println(escapedValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +212,7 @@ public class XMLSerializer {
|
||||
else if ("default".equals(space.getNodeValue())) {
|
||||
pContext.preserveSpace = false;
|
||||
}
|
||||
// No other values are allowed per spec, ingore
|
||||
// No other values are allowed per spec, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -200,7 +229,7 @@ public class XMLSerializer {
|
||||
}
|
||||
|
||||
String value = pNode.getNodeValue();
|
||||
validateCommenValue(value);
|
||||
validateCommentValue(value);
|
||||
|
||||
if (value.startsWith(" ")) {
|
||||
pOut.print("<!--");
|
||||
@@ -229,7 +258,7 @@ public class XMLSerializer {
|
||||
int startEscape = needsEscapeElement(pValue);
|
||||
|
||||
if (startEscape < 0) {
|
||||
// If no escpaing is needed, simply return original
|
||||
// If no escaping is needed, simply return original
|
||||
return pValue;
|
||||
}
|
||||
else {
|
||||
@@ -355,6 +384,7 @@ public class XMLSerializer {
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -365,7 +395,7 @@ public class XMLSerializer {
|
||||
return pValue;
|
||||
}
|
||||
|
||||
private static String validateCommenValue(final String pValue) {
|
||||
private static String validateCommentValue(final String pValue) {
|
||||
if (pValue.indexOf("--") >= 0) {
|
||||
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
|
||||
}
|
||||
@@ -388,6 +418,9 @@ public class XMLSerializer {
|
||||
|
||||
// TODO: Attributes should probably include namespaces, so that it works
|
||||
// even if the document was created using attributes instead of namespaces...
|
||||
// In that case, prefix will be null...
|
||||
|
||||
// TODO: Don't insert duplicate/unnecessary namesspace declarations
|
||||
|
||||
// Handle namespace
|
||||
String namespace = pNode.getNamespaceURI();
|
||||
@@ -425,6 +458,7 @@ public class XMLSerializer {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider not indenting/newline if the first child is a text node
|
||||
// Iterate children if any
|
||||
if (pNode.hasChildNodes()) {
|
||||
pOut.print(">");
|
||||
@@ -433,11 +467,9 @@ public class XMLSerializer {
|
||||
}
|
||||
|
||||
NodeList children = pNode.getChildNodes();
|
||||
//pContext.level++;
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
writeNodeRecursive(pOut, children.item(i), pContext.push());
|
||||
}
|
||||
//pContext.level--;
|
||||
|
||||
if (!pContext.preserveSpace) {
|
||||
indentToLevel(pOut, pContext);
|
||||
@@ -496,12 +528,15 @@ public class XMLSerializer {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
DocumentBuilder builder;
|
||||
|
||||
try {
|
||||
builder = factory.newDocumentBuilder();
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
//noinspection ThrowableInstanceNeverThrown BOGUS
|
||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
||||
}
|
||||
|
||||
DOMImplementation dom = builder.getDOMImplementation();
|
||||
|
||||
Document document = dom.createDocument("http://www.twelvemonkeys.com/xml/test", "test", dom.createDocumentType("test", null, null));
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-batik</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO Batik Plugin</name>
|
||||
<description>
|
||||
<![CDATA[
|
||||
@@ -17,7 +17,7 @@
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+8
-3
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.svg;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
@@ -50,12 +51,16 @@ public class SVGImageReaderSpi extends ImageReaderSpi {
|
||||
private final static boolean SVG_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
|
||||
|
||||
/**
|
||||
* Creates an SVGImageReaderSpi
|
||||
* Creates an {@code SVGImageReaderSpi}.
|
||||
*/
|
||||
public SVGImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(SVGImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private SVGImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys", // Vendor name
|
||||
"2.0", // Version
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
SVG_READER_AVAILABLE ? new String[]{"svg", "SVG"} : new String[]{""}, // Names
|
||||
SVG_READER_AVAILABLE ? new String[]{"svg"} : null, // Suffixes
|
||||
SVG_READER_AVAILABLE ? new String[]{"image/svg", "image/x-svg", "image/svg+xml", "image/svg-xml"} : null, // Mime-types
|
||||
|
||||
+8
-3
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
@@ -50,12 +51,16 @@ public class TIFFImageReaderSpi extends ImageReaderSpi {
|
||||
final static boolean TIFF_CLASSES_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.tiff.TIFFImageReader");
|
||||
|
||||
/**
|
||||
* Creates an SVGImageReaderSpi
|
||||
* Creates a {@code TIFFImageReaderSpi}.
|
||||
*/
|
||||
public TIFFImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(TIFFImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private TIFFImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys", // Vendor name
|
||||
"2.0", // Version
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "TIFF"} : new String[] {""}, // Names
|
||||
TIFF_CLASSES_AVAILABLE ? new String[]{"tiff", "tif"} : null, // Suffixes
|
||||
TIFF_CLASSES_AVAILABLE ? new String[]{"image/tiff", "image/x-tiff"} : null, // Mime-types
|
||||
|
||||
+8
-3
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.tiff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
@@ -46,12 +47,16 @@ import java.util.Locale;
|
||||
public class TIFFImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
/**
|
||||
* Creates a TIFFImageWriterSpi.
|
||||
* Creates a {@code TIFFImageWriterSpi}.
|
||||
*/
|
||||
public TIFFImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(TIFFImageWriterSpi.class));
|
||||
}
|
||||
|
||||
private TIFFImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys", // Vendor name
|
||||
"2.0", // Version
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
new String[]{"tiff", "TIFF"}, // Names
|
||||
new String[]{"tif", "tiff"}, // Suffixes
|
||||
new String[]{"image/tiff", "image/x-tiff"}, // Mime-types
|
||||
|
||||
+8
-3
@@ -28,6 +28,7 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.wmf;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
@@ -51,12 +52,16 @@ public class WMFImageReaderSpi extends ImageReaderSpi {
|
||||
private final static boolean WMF_READER_AVAILABLE = SystemUtil.isClassAvailable("com.twelvemonkeys.imageio.plugins.svg.SVGImageReader");
|
||||
|
||||
/**
|
||||
* Creates an SVGImageReaderSpi
|
||||
* Creates a {@code WMFImageReaderSpi}.
|
||||
*/
|
||||
public WMFImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(WMFImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private WMFImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys", // Vendor name
|
||||
"2.0", // Version
|
||||
pProviderInfo.getVendorName(), // Vendor name
|
||||
pProviderInfo.getVersion(), // Version
|
||||
WMF_READER_AVAILABLE ? new String[]{"wmf", "WMF"} : new String[]{""}, // Names
|
||||
WMF_READER_AVAILABLE ? new String[]{"wmf", "emf"} : null, // Suffixes
|
||||
WMF_READER_AVAILABLE ? new String[]{"application/x-msmetafile", "image/x-wmf"} : null, // Mime-types
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO Core</name>
|
||||
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
</project>
|
||||
+47
-27
@@ -41,6 +41,7 @@ import java.awt.event.ActionEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.IndexColorModel;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
@@ -48,13 +49,14 @@ import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* ImageReaderBase
|
||||
* Abstract base class for image readers.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ImageReaderBase.java,v 1.0 Sep 20, 2007 5:28:37 PM haraldk Exp$
|
||||
*/
|
||||
public abstract class ImageReaderBase extends ImageReader {
|
||||
|
||||
/**
|
||||
* For convenience. Only set if the input is an {@code ImageInputStream}.
|
||||
* @see #setInput(Object, boolean, boolean)
|
||||
@@ -71,11 +73,10 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
* the extension object is unsuitable, an
|
||||
* {@code IllegalArgumentException} should be thrown.
|
||||
*
|
||||
* @param pOriginatingProvider the {@code ImageReaderSpi} that is
|
||||
* invoking this constructor, or {@code null}.
|
||||
* @param pProvider the {@code ImageReaderSpi} that is invoking this constructor, or {@code null}.
|
||||
*/
|
||||
protected ImageReaderBase(final ImageReaderSpi pOriginatingProvider) {
|
||||
super(pOriginatingProvider);
|
||||
protected ImageReaderBase(final ImageReaderSpi pProvider) {
|
||||
super(pProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,7 +98,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
* @see ImageInputStream
|
||||
*/
|
||||
@Override
|
||||
public void setInput(Object pInput, boolean pSeekForwardOnly, boolean pIgnoreMetadata) {
|
||||
public void setInput(final Object pInput, final boolean pSeekForwardOnly, final boolean pIgnoreMetadata) {
|
||||
resetMembers();
|
||||
super.setInput(pInput, pSeekForwardOnly, pIgnoreMetadata);
|
||||
if (pInput instanceof ImageInputStream) {
|
||||
@@ -129,21 +130,21 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
protected abstract void resetMembers();
|
||||
|
||||
/**
|
||||
* Defaul implementation that always return {@code null}.
|
||||
* Default implementation that always returns {@code null}.
|
||||
*
|
||||
* @param pImageIndex ignored, unless overriden
|
||||
* @return {@code null}, unless overriden
|
||||
* @throws IOException never, unless overriden.
|
||||
* @param pImageIndex ignored, unless overridden
|
||||
* @return {@code null}, unless overridden
|
||||
* @throws IOException never, unless overridden.
|
||||
*/
|
||||
public IIOMetadata getImageMetadata(int pImageIndex) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defaul implementation that always return {@code null}.
|
||||
* Default implementation that always returns {@code null}.
|
||||
*
|
||||
* @return {@code null}, unless overriden
|
||||
* @throws IOException never, unless overriden.
|
||||
* @return {@code null}, unless overridden
|
||||
* @throws IOException never, unless overridden.
|
||||
*/
|
||||
public IIOMetadata getStreamMetadata() throws IOException {
|
||||
return null;
|
||||
@@ -152,9 +153,9 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
/**
|
||||
* Default implementation that always returns {@code 1}.
|
||||
*
|
||||
* @param pAllowSearch ignored, unless overriden
|
||||
* @return {@code 1}, unless overriden
|
||||
* @throws IOException never, unless overriden
|
||||
* @param pAllowSearch ignored, unless overridden
|
||||
* @return {@code 1}, unless overridden
|
||||
* @throws IOException never, unless overridden
|
||||
*/
|
||||
public int getNumImages(boolean pAllowSearch) throws IOException {
|
||||
assertInput();
|
||||
@@ -167,8 +168,7 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
* @param pIndex the image index
|
||||
*
|
||||
* @throws java.io.IOException if an error occurs during reading
|
||||
* @throws IndexOutOfBoundsException if not
|
||||
* <tt>minIndex <= pIndex < numImages</tt>
|
||||
* @throws IndexOutOfBoundsException if not {@code minIndex <= pIndex < numImages}
|
||||
*/
|
||||
protected void checkBounds(int pIndex) throws IOException {
|
||||
assertInput();
|
||||
@@ -216,10 +216,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
* @throws IllegalArgumentException if {@code pTypes}
|
||||
* is {@code null} or empty, or if an object not of type
|
||||
* {@code ImageTypeSpecifier} is retrieved from it.
|
||||
* Or, if the resulting image would
|
||||
* have a width or height less than 1,
|
||||
* or if the product of
|
||||
* {@code pWidth} and {@code pHeight} is greater than
|
||||
* Or, if the resulting image would have a width or height less than 1,
|
||||
* or if the product of {@code pWidth} and {@code pHeight} is greater than
|
||||
* {@code Integer.MAX_VALUE}.
|
||||
*/
|
||||
public static BufferedImage getDestination(final ImageReadParam pParam, final Iterator<ImageTypeSpecifier> pTypes,
|
||||
@@ -343,12 +341,20 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
private static class ImageLabel extends JLabel {
|
||||
Paint mBackground;
|
||||
|
||||
public ImageLabel(BufferedImage pImage) {
|
||||
final Paint mCheckeredBG;
|
||||
final Color mDefaultBG;
|
||||
|
||||
public ImageLabel(final BufferedImage pImage) {
|
||||
super(new BufferedImageIcon(pImage));
|
||||
setOpaque(false);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
||||
|
||||
mBackground = createTexture();
|
||||
mCheckeredBG = createTexture();
|
||||
|
||||
// For indexed color, default to the color of the transparent pixel, if any
|
||||
mDefaultBG = getDefaultBackground(pImage);
|
||||
|
||||
mBackground = mDefaultBG != null ? mDefaultBG : mCheckeredBG;
|
||||
|
||||
JPopupMenu popup = createBackgroundPopup();
|
||||
|
||||
@@ -366,7 +372,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
private JPopupMenu createBackgroundPopup() {
|
||||
JPopupMenu popup = new JPopupMenu();
|
||||
ButtonGroup group = new ButtonGroup();
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Default", mBackground), popup, group);
|
||||
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Checkered", mCheckeredBG), popup, group);
|
||||
popup.addSeparator();
|
||||
addCheckBoxItem(new ChangeBackgroundAction("White", Color.WHITE), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Light", Color.LIGHT_GRAY), popup, group);
|
||||
@@ -374,7 +381,8 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Dark", Color.DARK_GRAY), popup, group);
|
||||
addCheckBoxItem(new ChangeBackgroundAction("Black", Color.BLACK), popup, group);
|
||||
popup.addSeparator();
|
||||
addCheckBoxItem(new ChooseBackgroundAction("Choose...", Color.BLUE), popup, group);
|
||||
addCheckBoxItem(new ChooseBackgroundAction("Choose...", mDefaultBG != null ? mDefaultBG : Color.BLUE), popup, group);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
@@ -384,7 +392,19 @@ public abstract class ImageReaderBase extends ImageReader {
|
||||
pPopup.add(item);
|
||||
}
|
||||
|
||||
private Paint createTexture() {
|
||||
private static Color getDefaultBackground(BufferedImage pImage) {
|
||||
if (pImage.getColorModel() instanceof IndexColorModel) {
|
||||
IndexColorModel cm = (IndexColorModel) pImage.getColorModel();
|
||||
int transparent = cm.getTransparentPixel();
|
||||
if (transparent >= 0) {
|
||||
return new Color(cm.getRGB(transparent), false);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Paint createTexture() {
|
||||
GraphicsConfiguration graphicsConfiguration = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
|
||||
BufferedImage pattern = graphicsConfiguration.createCompatibleImage(20, 20);
|
||||
Graphics2D g = pattern.createGraphics();
|
||||
|
||||
+20
-14
@@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageWriteParam;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
@@ -39,13 +40,18 @@ import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* ImageWriterBase
|
||||
* Abstract base class for image writers.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ImageWriterBase.java,v 1.0 Sep 24, 2007 12:22:28 AM haraldk Exp$
|
||||
*/
|
||||
public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
public abstract class ImageWriterBase extends ImageWriter {
|
||||
|
||||
/**
|
||||
* For convenience. Only set if the output is an {@code ImageInputStream}.
|
||||
* @see #setOutput(Object)
|
||||
*/
|
||||
protected ImageOutputStream mImageOutput;
|
||||
|
||||
/**
|
||||
@@ -59,8 +65,7 @@ public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
* the extension object is unsuitable, an
|
||||
* {@code IllegalArgumentException} should be thrown.
|
||||
*
|
||||
* @param pProvider the {@code ImageWriterSpi} that
|
||||
* is constructing this object, or {@code null}.
|
||||
* @param pProvider the {@code ImageWriterSpi} that is constructing this object, or {@code null}.
|
||||
*/
|
||||
protected ImageWriterBase(final ImageWriterSpi pProvider) {
|
||||
super(pProvider);
|
||||
@@ -71,8 +76,9 @@ public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOutput(Object pOutput) {
|
||||
public void setOutput(final Object pOutput) {
|
||||
super.setOutput(pOutput);
|
||||
|
||||
if (pOutput instanceof ImageOutputStream) {
|
||||
mImageOutput = (ImageOutputStream) pOutput;
|
||||
}
|
||||
@@ -92,10 +98,10 @@ public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
/**
|
||||
* Returns {@code null}
|
||||
*
|
||||
* @param pParam igonred.
|
||||
* @param pParam ignored.
|
||||
* @return {@code null}.
|
||||
*/
|
||||
public IIOMetadata getDefaultStreamMetadata(javax.imageio.ImageWriteParam pParam) {
|
||||
public IIOMetadata getDefaultStreamMetadata(final ImageWriteParam pParam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -103,20 +109,20 @@ public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
* Returns {@code null}
|
||||
*
|
||||
* @param pInData ignored.
|
||||
* @param pParam igonred.
|
||||
* @param pParam ignored.
|
||||
* @return {@code null}.
|
||||
*/
|
||||
public IIOMetadata convertStreamMetadata(IIOMetadata pInData, ImageWriteParam pParam) {
|
||||
public IIOMetadata convertStreamMetadata(final IIOMetadata pInData, final ImageWriteParam pParam) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected static Rectangle getSourceRegion(ImageWriteParam pParam, int pWidth, int pHeight) {
|
||||
protected static Rectangle getSourceRegion(final ImageWriteParam pParam, final int pWidth, final int pHeight) {
|
||||
return IIOUtil.getSourceRegion(pParam, pWidth, pHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for getting the area of interest (AOI) of an image.
|
||||
* The AOI is defined by the {@link IIOParam#setSourceRegion(java.awt.Rectangle)}
|
||||
* The AOI is defined by the {@link javax.imageio.IIOParam#setSourceRegion(java.awt.Rectangle)}
|
||||
* method.
|
||||
* <p/>
|
||||
* Note: If it is possible for the reader to read the AOI directly, such a
|
||||
@@ -129,14 +135,14 @@ public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
* region), or the original image, if no source region was set, or
|
||||
* {@code pParam} was {@code null}
|
||||
*/
|
||||
protected static BufferedImage fakeAOI(BufferedImage pImage, ImageWriteParam pParam) {
|
||||
protected static BufferedImage fakeAOI(final BufferedImage pImage, final ImageWriteParam pParam) {
|
||||
return IIOUtil.fakeAOI(pImage, getSourceRegion(pParam, pImage.getWidth(), pImage.getHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method for getting the subsampled image.
|
||||
* The subsampling is defined by the
|
||||
* {@link IIOParam#setSourceSubsampling(int, int, int, int)}
|
||||
* {@link javax.imageio.IIOParam#setSourceSubsampling(int, int, int, int)}
|
||||
* method.
|
||||
* <p/>
|
||||
* NOTE: This method does not take the subsampling offsets into
|
||||
@@ -152,7 +158,7 @@ public abstract class ImageWriterBase extends javax.imageio.ImageWriter {
|
||||
* original image, if no subsampling was specified, or
|
||||
* {@code pParam} was {@code null}
|
||||
*/
|
||||
protected static Image fakeSubsampling(Image pImage, ImageWriteParam pParam) {
|
||||
protected static Image fakeSubsampling(final Image pImage, final ImageWriteParam pParam) {
|
||||
return IIOUtil.fakeSubsampling(pImage, pParam);
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
package com.twelvemonkeys.imageio.spi;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
/**
|
||||
* Provides provider info, like vendor name and version,
|
||||
* for {@link javax.imageio.spi.ImageReaderWriterSpi} subclasses based on information in the manifest.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ProviderInfo.java,v 1.0 Oct 31, 2009 3:49:39 PM haraldk Exp$
|
||||
*
|
||||
* @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/jar/jar.html#JAR%20Manifest">JAR Manifest</a>
|
||||
*/
|
||||
public class ProviderInfo {
|
||||
// TODO: Consider reading the META-INF/MANIFEST.MF from the class path using java.util.jar.Manifest.
|
||||
// Use the manifest that is located in the same class path folder as the package.
|
||||
|
||||
private final String mTitle;
|
||||
private final String mVendorName;
|
||||
private final String mVersion;
|
||||
|
||||
/**
|
||||
* Creates a provider information instance based on the given package.
|
||||
*
|
||||
* @param pPackage the package to get provider information from.
|
||||
* This should typically be the package containing the Spi class.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code pPackage == null}
|
||||
*/
|
||||
public ProviderInfo(final Package pPackage) {
|
||||
Validate.notNull(pPackage, "package");
|
||||
|
||||
String title = pPackage.getImplementationTitle();
|
||||
mTitle = title != null ? title : pPackage.getName();
|
||||
|
||||
String vendor = pPackage.getImplementationVendor();
|
||||
mVendorName = vendor != null ? vendor : fakeVendor(pPackage);
|
||||
|
||||
String version = pPackage.getImplementationVersion();
|
||||
mVersion = version != null ? version : fakeVersion(pPackage);
|
||||
}
|
||||
|
||||
private static String fakeVendor(final Package pPackage) {
|
||||
String name = pPackage.getName();
|
||||
return name.startsWith("com.twelvemonkeys") ? "TwelveMonkeys" : name;
|
||||
}
|
||||
|
||||
private String fakeVersion(Package pPackage) {
|
||||
String name = pPackage.getName();
|
||||
return name.startsWith("com.twelvemonkeys") ? "DEV" : "Unspecified";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the implementation title, as specified in the manifest entry
|
||||
* {@code Implementation-Title} for the package.
|
||||
* If the title is unavailable, the package name or some default name
|
||||
* for known packages are used.
|
||||
*
|
||||
* @return the implementation title
|
||||
*/
|
||||
final String getImplementationTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the vendor name, as specified in the manifest entry
|
||||
* {@code Implementation-Vendor} for the package.
|
||||
* If the vendor name is unavailable, the package name or some default name
|
||||
* for known packages are used.
|
||||
*
|
||||
* @return the vendor name.
|
||||
*/
|
||||
public final String getVendorName() {
|
||||
return mVendorName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the version/build number string, as specified in the manifest entry
|
||||
* {@code Implementation-Version} for the package.
|
||||
* If the version is unavailable, some arbitrary (non-{@code null}) value is used.
|
||||
*
|
||||
* @return the vendor name.
|
||||
*/
|
||||
public final String getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mTitle + ", " + mVersion + " by " + mVendorName;
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides helper classes for service provider implementations.
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.spi;
|
||||
+3
-8
@@ -154,7 +154,7 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (mStream != null) {
|
||||
mStream.close();
|
||||
//mStream.close();
|
||||
mStream = null;
|
||||
mBuffer = null;
|
||||
}
|
||||
@@ -172,14 +172,9 @@ public final class BufferedImageInputStream extends ImageInputStreamImpl impleme
|
||||
try {
|
||||
return mStream.length();
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw unchecked(e, RuntimeException.class);
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "UnusedDeclaration"})
|
||||
private <T extends Throwable> T unchecked(IOException pExcption, Class<T> pClass) {
|
||||
// Ugly hack to fool the compiler..
|
||||
return (T) pExcption;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A wrapper for {@link ImageInputStream} to limit the number of bytes that can be read.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SubImageInputStream.java,v 1.0 Nov 8, 2009 2:50:58 PM haraldk Exp$
|
||||
*/
|
||||
public final class SubImageInputStream extends ImageInputStreamImpl {
|
||||
// NOTE: This class is based on com.sun.imageio.plugins.common.SubImageInputStream, but fixes some of its bugs.
|
||||
|
||||
private final ImageInputStream mStream;
|
||||
private final long mStartPos;
|
||||
private final long mLength;
|
||||
|
||||
/**
|
||||
* Creates a {@link ImageInputStream}, reading up to a maximum number of bytes from the underlying stream.
|
||||
*
|
||||
* @param pStream the underlying stream
|
||||
* @param pLength the maximum length to read from the stream.
|
||||
* Note that {@code pStream} may contain less than this maximum number of bytes.
|
||||
*
|
||||
* @throws IOException if {@code pStream}'s position can't be determined.
|
||||
* @throws IllegalArgumentException if {@code pStream == null} or {@code pLength < 0}
|
||||
*/
|
||||
public SubImageInputStream(final ImageInputStream pStream, final long pLength) throws IOException {
|
||||
Validate.notNull(pStream, "stream");
|
||||
if (pLength < 0) {
|
||||
throw new IllegalArgumentException("length < 0");
|
||||
}
|
||||
|
||||
mStream = pStream;
|
||||
mStartPos = pStream.getStreamPosition();
|
||||
mLength = pLength;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (streamPos >= mLength) { // Local EOF
|
||||
return -1;
|
||||
}
|
||||
else {
|
||||
int read = mStream.read();
|
||||
|
||||
if (read >= 0) {
|
||||
streamPos++;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (streamPos >= mLength) { // Local EOF
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Safe cast, as pLength can never cause int overflow
|
||||
int length = (int) Math.min(pLength, mLength - streamPos);
|
||||
int count = mStream.read(pBytes, pOffset, length);
|
||||
|
||||
if (count >= 0) {
|
||||
streamPos += count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
try {
|
||||
long length = mStream.length();
|
||||
return length < 0 ? -1 : Math.min(length - mStartPos, mLength);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
if (pPosition < getFlushedPosition()) {
|
||||
throw new IndexOutOfBoundsException("pos < flushedPosition");
|
||||
}
|
||||
|
||||
mStream.seek(mStartPos + pPosition);
|
||||
streamPos = pPosition;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"FinalizeDoesntCallSuperFinalize"})
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
// Empty finalizer (for improved performance; no need to call super.finalize() in this case)
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides various additional stream implementations.
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
+27
-15
@@ -1,6 +1,7 @@
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
|
||||
import com.twelvemonkeys.image.ImageUtil;
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
|
||||
import javax.imageio.IIOParam;
|
||||
import javax.imageio.spi.IIOServiceProvider;
|
||||
@@ -61,21 +62,6 @@ public final class IIOUtil {
|
||||
return new BufferedOutputStream(new IIOOutputStreamAdapter(pStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
|
||||
*
|
||||
* @param pRegistry the registry to unregister from
|
||||
* @param pProvider the provider to unregister
|
||||
* @param pCategory the category to unregister from
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static <T> void deregisterProvider(final ServiceRegistry pRegistry, final IIOServiceProvider pProvider, final Class<T> pCategory) {
|
||||
// http://www.ibm.com/developerworks/java/library/j-jtp04298.html
|
||||
// TODO: Consider placing this method in a ImageReaderSpiBase class or similar
|
||||
pRegistry.deregisterServiceProvider(pCategory.cast(pProvider), pCategory);
|
||||
}
|
||||
|
||||
public static Image fakeSubsampling(final Image pImage, final IIOParam pParam) {
|
||||
if (pImage == null) {
|
||||
return null;
|
||||
@@ -134,4 +120,30 @@ public final class IIOUtil {
|
||||
|
||||
return pImage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ProviderInfo} instance for the given service provider.
|
||||
*
|
||||
* @param pProviderClass the provider class to get info for.
|
||||
* @return the newly created {@link ProviderInfo}.
|
||||
*/
|
||||
public static ProviderInfo getProviderInfo(final Class<? extends IIOServiceProvider> pProviderClass) {
|
||||
return new ProviderInfo(pProviderClass.getPackage());
|
||||
}
|
||||
|
||||
/**
|
||||
* THIS METHOD WILL ME MOVED/RENAMED, DO NOT USE.
|
||||
*
|
||||
* @param pRegistry the registry to unregister from
|
||||
* @param pProvider the provider to unregister
|
||||
* @param pCategory the category to unregister from
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public static <T> void deregisterProvider(final ServiceRegistry pRegistry, final IIOServiceProvider pProvider, final Class<T> pCategory) {
|
||||
// http://www.ibm.com/developerworks/java/library/j-jtp04298.html
|
||||
// TODO: Consider placing this method in a ImageReaderSpiBase class or similar
|
||||
pRegistry.deregisterServiceProvider(pCategory.cast(pProvider), pCategory);
|
||||
}
|
||||
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides various common utilities for reading and writing images.
|
||||
*/
|
||||
package com.twelvemonkeys.imageio.util;
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
package com.twelvemonkeys.imageio.spi;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
/**
|
||||
* ProviderInfoTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: ProviderInfoTestCase.java,v 1.0 Oct 31, 2009 3:51:22 PM haraldk Exp$
|
||||
*/
|
||||
public class ProviderInfoTestCase extends TestCase {
|
||||
public void testCreateNorma() {
|
||||
new ProviderInfo(Package.getPackage("java.util"));
|
||||
}
|
||||
|
||||
public void testCreateNullPackage() {
|
||||
try {
|
||||
new ProviderInfo(null);
|
||||
fail("IllegalArgumentException expected for null package");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().toLowerCase().contains("package"));
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetVendorUnknownNonJARPackage() {
|
||||
ProviderInfo info = new ProviderInfo(mockNonJARPackage("org.foo"));
|
||||
|
||||
String vendor = info.getVendorName();
|
||||
assertNotNull(vendor);
|
||||
assertEquals("org.foo", vendor);
|
||||
|
||||
String version = info.getVersion();
|
||||
assertNotNull(version);
|
||||
assertEquals("Unspecified", version);
|
||||
}
|
||||
|
||||
public void testGetVendorNonJARTMPackage() {
|
||||
ProviderInfo info = new ProviderInfo(mockNonJARPackage("com.twelvemonkeys"));
|
||||
|
||||
String vendor = info.getVendorName();
|
||||
assertNotNull(vendor);
|
||||
assertEquals("TwelveMonkeys", vendor);
|
||||
|
||||
String version = info.getVersion();
|
||||
assertNotNull(version);
|
||||
assertEquals("DEV", version);
|
||||
}
|
||||
|
||||
public void testGetVendorKnownJARPackage() {
|
||||
ProviderInfo info = new ProviderInfo(mockJARPackage("com.acme", "1.7u4-BETA-b39", "Acme"));
|
||||
|
||||
String vendor = info.getVendorName();
|
||||
assertNotNull(vendor);
|
||||
assertEquals("Acme", vendor);
|
||||
|
||||
String version = info.getVersion();
|
||||
assertNotNull(version);
|
||||
assertEquals("1.7u4-BETA-b39", version);
|
||||
}
|
||||
|
||||
private Package mockNonJARPackage(final String pName) {
|
||||
return new MockClassLoader().mockPackage(
|
||||
pName,
|
||||
null, null, null,
|
||||
null, null, null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private Package mockJARPackage(final String pName, final String pImplVersion, final String pImplVendor) {
|
||||
return new MockClassLoader().mockPackage(
|
||||
pName,
|
||||
"The almighty specification", "1.0", "Acme Inc",
|
||||
"The buggy implementation", pImplVersion, pImplVendor,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private static class MockClassLoader extends ClassLoader {
|
||||
protected MockClassLoader() {
|
||||
super(null);
|
||||
}
|
||||
|
||||
public Package mockPackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException {
|
||||
return definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Package getPackage(String name) {
|
||||
return null; // Allow re-createing packages
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package com.twelvemonkeys.imageio.stream;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageInputStreamImpl;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* SubImageInputStreamTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: SubImageInputStreamTestCase.java,v 1.0 Nov 8, 2009 3:03:32 PM haraldk Exp$
|
||||
*/
|
||||
public class SubImageInputStreamTestCase extends TestCase {
|
||||
// TODO: Extract super test case for all stream tests
|
||||
private final Random mRandom = new Random(837468l);
|
||||
|
||||
private ImageInputStream createStream(final int pSize) {
|
||||
byte[] bytes = new byte[pSize];
|
||||
|
||||
mRandom.nextBytes(bytes);
|
||||
|
||||
return new MemoryCacheImageInputStream(new ByteArrayInputStream(bytes)) {
|
||||
@Override
|
||||
public long length() {
|
||||
return pSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void testCreateNullStream() throws IOException {
|
||||
try {
|
||||
new SubImageInputStream(null, 1);
|
||||
fail("Expected IllegalArgumentException with null stream");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreateNegativeLength() throws IOException {
|
||||
try {
|
||||
new SubImageInputStream(createStream(0), -1);
|
||||
fail("Expected IllegalArgumentException with negative length");
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testCreate() throws IOException {
|
||||
ImageInputStream stream = new SubImageInputStream(createStream(11), 7);
|
||||
|
||||
assertEquals(0, stream.getStreamPosition());
|
||||
assertEquals(7, stream.length());
|
||||
}
|
||||
|
||||
public void testWraphBeyondWrappedLength() throws IOException {
|
||||
SubImageInputStream stream = new SubImageInputStream(createStream(5), 6);
|
||||
assertEquals(5, stream.length());
|
||||
}
|
||||
|
||||
public void testWrapUnknownLength() throws IOException {
|
||||
SubImageInputStream stream = new SubImageInputStream(new ImageInputStreamImpl() {
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
throw new UnsupportedOperationException("Method read not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
throw new UnsupportedOperationException("Method read not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return -1;
|
||||
}
|
||||
}, 6);
|
||||
|
||||
assertEquals(-1, stream.length());
|
||||
}
|
||||
|
||||
public void testRead() throws IOException {
|
||||
ImageInputStream wrapped = createStream(42);
|
||||
|
||||
wrapped.skipBytes(13);
|
||||
|
||||
ImageInputStream stream = new SubImageInputStream(wrapped, 27);
|
||||
|
||||
assertEquals(0, stream.getStreamPosition());
|
||||
assertEquals(27, stream.length());
|
||||
|
||||
stream.read();
|
||||
assertEquals(1, stream.getStreamPosition());
|
||||
assertEquals(27, stream.length());
|
||||
|
||||
stream.readFully(new byte[11]);
|
||||
assertEquals(12, stream.getStreamPosition());
|
||||
assertEquals(27, stream.length());
|
||||
|
||||
assertEquals(25, wrapped.getStreamPosition());
|
||||
}
|
||||
|
||||
public void testReadResetRead() throws IOException {
|
||||
ImageInputStream stream = new SubImageInputStream(createStream(32), 16);
|
||||
stream.mark();
|
||||
|
||||
byte[] first = new byte[16];
|
||||
stream.readFully(first);
|
||||
|
||||
stream.reset();
|
||||
|
||||
byte[] second = new byte[16];
|
||||
stream.readFully(second);
|
||||
|
||||
assertTrue(Arrays.equals(first, second));
|
||||
}
|
||||
|
||||
public void testSeekNegative() throws IOException {
|
||||
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||
try {
|
||||
stream.seek(-2);
|
||||
fail("Expected IndexOutOfBoundsException");
|
||||
}
|
||||
catch (IndexOutOfBoundsException expected) {
|
||||
}
|
||||
|
||||
assertEquals(0, stream.getStreamPosition());
|
||||
}
|
||||
|
||||
public void testSeekBeforeFlushedPos() throws IOException {
|
||||
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||
stream.seek(3);
|
||||
stream.flushBefore(3);
|
||||
|
||||
assertEquals(3, stream.getStreamPosition());
|
||||
|
||||
try {
|
||||
stream.seek(0);
|
||||
fail("Expected IndexOutOfBoundsException");
|
||||
}
|
||||
catch (IndexOutOfBoundsException expected) {
|
||||
}
|
||||
|
||||
assertEquals(3, stream.getStreamPosition());
|
||||
}
|
||||
|
||||
public void testSeekAfterEOF() throws IOException {
|
||||
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||
stream.seek(6);
|
||||
|
||||
assertEquals(-1, stream.read());
|
||||
}
|
||||
|
||||
public void testSeek() throws IOException {
|
||||
ImageInputStream stream = new SubImageInputStream(createStream(7), 5);
|
||||
stream.seek(5);
|
||||
assertEquals(5, stream.getStreamPosition());
|
||||
|
||||
stream.seek(1);
|
||||
assertEquals(1, stream.getStreamPosition());
|
||||
}
|
||||
}
|
||||
+22
-5
@@ -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);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-ico</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO ICO plugin</name>
|
||||
<description>ImageIO plugin for Windows Icon (ICO) and Cursor (CUR) format.</description>
|
||||
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+2
-2
@@ -53,10 +53,10 @@ public class CURImageReader extends ICOImageReader {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotspot location for the cursor.
|
||||
* Returns the hot spot location for the cursor.
|
||||
*
|
||||
* @param pImageIndex the index of the cursor in the current input.
|
||||
* @return the hotspot location for the cursor
|
||||
* @return the hot spot location for the cursor
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs during reading of image meta data
|
||||
* @throws IndexOutOfBoundsException if {@code pImageIndex} is less than {@code 0} or greater than/equal to
|
||||
|
||||
+9
-2
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.ico;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -43,9 +46,13 @@ import java.util.Locale;
|
||||
public class CURImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
public CURImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(CURImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private CURImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"2.1",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"cur", "CUR"},
|
||||
new String[]{"cur"},
|
||||
new String[]{
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ import java.util.List;
|
||||
/**
|
||||
* ImageReader for Microsoft Windows ICO (icon) format.
|
||||
* 1, 4, 8 bit palette support with bitmask transparency, and 16, 24 and 32 bit
|
||||
* true color support with alpha. Also supports Windows Vista PNG ecoded icons.
|
||||
* true color support with alpha. Also supports Windows Vista PNG encoded icons.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
|
||||
+9
-2
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.ico;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -43,9 +46,13 @@ import java.util.Locale;
|
||||
public class ICOImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
public ICOImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(ICOImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private ICOImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"2.1",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"ico", "ICO"},
|
||||
new String[]{"ico"},
|
||||
new String[]{
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
- Support all DIB formats?
|
||||
- Use Sun's BMP metadata format for image metadata + separate stream metadata?
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-iff</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO IFF plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for Amiga/Electronic Arts Interchange Filed Format (IFF)
|
||||
@@ -15,7 +15,7 @@
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+1
@@ -42,6 +42,7 @@ import java.io.IOException;
|
||||
*/
|
||||
class CAMGChunk extends IFFChunk {
|
||||
|
||||
// HIRES=0x8000, LACE=0x4
|
||||
// #define CAMG_HAM 0x800 /* hold and modify */
|
||||
// #define CAMG_EHB 0x80 /* extra halfbrite */
|
||||
|
||||
|
||||
+4
-2
@@ -86,7 +86,7 @@ class CMAPChunk extends IFFChunk {
|
||||
if (numColors == 32) {
|
||||
paletteSize = 64;
|
||||
}
|
||||
else {
|
||||
else if (numColors != 64) {
|
||||
throw new IIOException("Unknown number of colors for EHB: " + numColors);
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,9 @@ class CMAPChunk extends IFFChunk {
|
||||
mGreens[i] = pInput.readByte();
|
||||
mBlues[i] = pInput.readByte();
|
||||
}
|
||||
if (isEHB) {
|
||||
|
||||
if (isEHB && numColors == 32) {
|
||||
// Create the half-brite colors
|
||||
for (int i = 0; i < numColors; i++) {
|
||||
mReds[i + numColors] = (byte) ((mReds[i] & 0xff) / 2);
|
||||
mGreens[i + numColors] = (byte) ((mGreens[i] & 0xff) / 2);
|
||||
|
||||
+57
-40
@@ -91,6 +91,10 @@ import java.util.List;
|
||||
* @see <a href="http://en.wikipedia.org/wiki/ILBM">Wikipedia: IFF ILBM</a>
|
||||
*/
|
||||
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
|
||||
// http://amigan.1emu.net/reg/iff.html
|
||||
|
||||
private BMHDChunk mHeader;
|
||||
private CMAPChunk mColorMap;
|
||||
@@ -154,7 +158,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
remaining -= 8;
|
||||
remaining -= length % 2 == 0 ? length : length + 1;
|
||||
|
||||
//System.out.println("Next chunk: " + toChunkStr(chunkId) + " lenght: " + length);
|
||||
//System.out.println("Next chunk: " + toChunkStr(chunkId) + " length: " + length);
|
||||
//System.out.println("Remaining bytes after chunk: " + remaining);
|
||||
|
||||
switch (chunkId) {
|
||||
@@ -203,8 +207,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
mBody = new BODYChunk(length);
|
||||
mBodyStart = mImageInput.getStreamPosition();
|
||||
|
||||
// NOTE: We don't read the body here, it's done later in the
|
||||
// read(int, ImageReadParam) method
|
||||
// NOTE: We don't read the body here, it's done later in the read(int, ImageReadParam) method
|
||||
|
||||
// Done reading meta
|
||||
return;
|
||||
@@ -232,6 +235,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
readBody(pParam);
|
||||
}
|
||||
else {
|
||||
// TODO: Remove this hack when we have metadata
|
||||
// In the rare case of an ILBM containing nothing but a CMAP
|
||||
//System.out.println(mColorMap);
|
||||
if (mColorMap != null) {
|
||||
@@ -244,7 +248,6 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
processImageComplete();
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -294,8 +297,8 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// 8 bit
|
||||
// May be HAM8
|
||||
if (!isHAM()) {
|
||||
IndexColorModel cm = mColorMap.getIndexColorModel();
|
||||
if (cm != null) {
|
||||
if (mColorMap != null) {
|
||||
IndexColorModel cm = mColorMap.getIndexColorModel();
|
||||
specifier = IndexedImageTypeSpecifier.createFromIndexColorModel(cm);
|
||||
break;
|
||||
}
|
||||
@@ -304,6 +307,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// NOTE: HAM modes falls through, as they are converted to RGB
|
||||
case 24:
|
||||
// 24 bit RGB
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_3BYTE_BGR);
|
||||
@@ -313,13 +317,13 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
specifier = ImageTypeSpecifier.createFromBufferedImageType(BufferedImage.TYPE_4BYTE_ABGR);
|
||||
break;
|
||||
default:
|
||||
throw new IIOException("Bit depth not implemented: " + mHeader.mBitplanes);
|
||||
throw new IIOException(String.format("Bit depth not implemented: %d", mHeader.mBitplanes));
|
||||
}
|
||||
return specifier;
|
||||
}
|
||||
|
||||
private void readBody(ImageReadParam pParam) throws IOException {
|
||||
mImageInput.seek(mBodyStart); // 8 for the header before length in stream
|
||||
private void readBody(final ImageReadParam pParam) throws IOException {
|
||||
mImageInput.seek(mBodyStart);
|
||||
mByteRunStream = null;
|
||||
|
||||
// NOTE: mColorMap may be null for 8 bit (gray), 24 bit or 32 bit only
|
||||
@@ -333,7 +337,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
}
|
||||
|
||||
private void readIndexed(ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
|
||||
private void readIndexed(final ImageReadParam pParam, final ImageInputStream pInput, final IndexColorModel pModel) throws IOException {
|
||||
final int width = mHeader.mWidth;
|
||||
final int height = mHeader.mHeight;
|
||||
|
||||
@@ -363,7 +367,9 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
}
|
||||
|
||||
int planeWidth = (width + 7) / 8;
|
||||
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
|
||||
// The number of words per row is words=((w+15)/16)
|
||||
int planeWidth = 2 * ((width + 15) / 16);
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
|
||||
ColorModel cm;
|
||||
@@ -373,10 +379,14 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// TODO: If HAM6, use type USHORT_444_RGB or 2BYTE_444_RGB?
|
||||
// Or create a HAMColorModel, if at all possible?
|
||||
// TYPE_3BYTE_BGR
|
||||
cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{8, 8, 8},
|
||||
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
|
||||
cm = new ComponentColorModel(
|
||||
ColorSpace.getInstance(ColorSpace.CS_sRGB), new int[]{8, 8, 8},
|
||||
false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE
|
||||
);
|
||||
// Create a byte raster with BGR order
|
||||
raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[]{2, 1, 0}, null);
|
||||
raster = Raster.createInterleavedRaster(
|
||||
DataBuffer.TYPE_BYTE, width, 1, width * 3, 3, new int[]{2, 1, 0}, null
|
||||
);
|
||||
}
|
||||
else {
|
||||
// TYPE_BYTE_BINARY or TYPE_BYTE_INDEXED
|
||||
@@ -400,14 +410,8 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
ColorConvertOp converter = null;
|
||||
|
||||
for (int srcY = 0; srcY < height; srcY++) {
|
||||
|
||||
for (int p = 0; p < planes; p++) {
|
||||
try {
|
||||
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// TODO: Add warning? Probbably a bug somewhere, should not catch!
|
||||
}
|
||||
readPlaneData(pInput, planeData, p * planeWidth, planeWidth);
|
||||
}
|
||||
|
||||
// Skip rows outside AOI
|
||||
@@ -420,11 +424,9 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
|
||||
if (mFormType == IFF.TYPE_ILBM) {
|
||||
int pixelPos = 0;
|
||||
int planePos = 0;
|
||||
for (int i = 0; i < planeWidth; i++) {
|
||||
for (int planePos = 0; planePos < planeWidth; planePos++) {
|
||||
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, row, pixelPos, 1);
|
||||
pixelPos += 8;
|
||||
planePos++;
|
||||
}
|
||||
|
||||
if (isHAM()) {
|
||||
@@ -434,11 +436,14 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
raster.setDataElements(0, 0, width, 1, row);
|
||||
}
|
||||
}
|
||||
else /*if (mType == IFFImageReader.TYPE_PBM)*/ {
|
||||
// TODO: Arraycopy might not be neccessary, if it's okay with row larger than width
|
||||
else if (mFormType == IFF.TYPE_PBM) {
|
||||
// TODO: Arraycopy might not be necessary, if it's okay with row larger than width
|
||||
System.arraycopy(planeData, 0, row, 0, mHeader.mBitplanes * planeWidth);
|
||||
raster.setDataElements(0, 0, width, 1, row);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
|
||||
}
|
||||
|
||||
int dstY = (srcY - aoi.y) / sourceYSubsampling;
|
||||
// Handle non-converting raster as special case for performance
|
||||
@@ -521,15 +526,17 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
// Ensure band settings from param are compatible with images
|
||||
checkReadParamBandSettings(pParam, mHeader.mBitplanes / 8, mImage.getSampleModel().getNumBands());
|
||||
|
||||
int planeWidth = (width + 7) / 8;
|
||||
|
||||
// NOTE: Each row of the image is stored in an integral number of 16 bit words.
|
||||
// The number of words per row is words=((w+15)/16)
|
||||
int planeWidth = 2 * ((width + 15) / 16);
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
|
||||
WritableRaster destination = mImage.getRaster();
|
||||
if (destinationBands != null || offset.x != 0 || offset.y != 0) {
|
||||
destination = destination.createWritableChild(0, 0, destination.getWidth(), destination.getHeight(), offset.x, offset.y, destinationBands);
|
||||
}
|
||||
WritableRaster raster = mImage.getRaster().createCompatibleWritableRaster(width, 1);
|
||||
// WritableRaster raster = mImage.getRaster().createCompatibleWritableRaster(width, 1);
|
||||
WritableRaster raster = mImage.getRaster().createCompatibleWritableRaster(8 * planeWidth, 1);
|
||||
Raster sourceRow = raster.createChild(aoi.x, 0, aoi.width, 1, 0, 0, sourceBands);
|
||||
|
||||
final byte[] data = ((DataBufferByte) raster.getDataBuffer()).getData();
|
||||
@@ -558,16 +565,17 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
int off = (channels - c - 1);
|
||||
|
||||
int pixelPos = 0;
|
||||
int planePos = 0;
|
||||
for (int i = 0; i < planeWidth; i++) {
|
||||
for (int planePos = 0; planePos < planeWidth; planePos++) {
|
||||
IFFUtil.bitRotateCW(planeData, planePos, planeWidth, data, off + pixelPos * channels, channels);
|
||||
pixelPos += 8;
|
||||
planePos++;
|
||||
}
|
||||
}
|
||||
else /*if (mType == IFFImageReader.TYPE_PBM)*/ {
|
||||
else if (mFormType == IFF.TYPE_PBM) {
|
||||
System.arraycopy(planeData, 0, data, srcY * 8 * planeWidth, planeWidth);
|
||||
}
|
||||
else {
|
||||
throw new AssertionError(String.format("Unsupported FORM type: %s", mFormType));
|
||||
}
|
||||
}
|
||||
|
||||
int dstY = (srcY - aoi.y) / sourceYSubsampling;
|
||||
@@ -607,16 +615,25 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
break;
|
||||
|
||||
case BMHDChunk.COMPRESSION_BYTE_RUN:
|
||||
// TODO: How do we know if the last byte in the body is a pad byte or not?!
|
||||
// The body consists of byte-run (PackBits) compressed rows of bit plane data.
|
||||
// However, we don't know how long each compressed row is, without decoding it...
|
||||
// The workaround below, is to use a decode buffer size of pPlaneWidth,
|
||||
// to make sure we don't decode anything we don't have to (shouldn't).
|
||||
if (mByteRunStream == null) {
|
||||
mByteRunStream = new DataInputStream(new DecoderStream(
|
||||
IIOUtil.createStreamAdapter(pInput, mBody.mChunkLength),
|
||||
new PackBitsDecoder(true)
|
||||
));
|
||||
mByteRunStream = new DataInputStream(
|
||||
new DecoderStream(
|
||||
IIOUtil.createStreamAdapter(pInput, mBody.mChunkLength),
|
||||
new PackBitsDecoder(true),
|
||||
pPlaneWidth * mHeader.mBitplanes
|
||||
)
|
||||
);
|
||||
}
|
||||
mByteRunStream.readFully(pData, pOffset, pPlaneWidth);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IIOException("Unknown compression type: " + mHeader.mCompressionType);
|
||||
throw new IIOException(String.format("Unknown compression type: %d", mHeader.mCompressionType));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,7 +683,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
ImageReader reader = new IFFImageReader(new IFFImageReaderSpi());
|
||||
ImageReader reader = new IFFImageReader();
|
||||
|
||||
// ImageInputStream input = ImageIO.createImageInputStream(new File(pArgs[0]));
|
||||
ImageInputStream input = new BufferedImageInputStream(ImageIO.createImageInputStream(new File(pArgs[0])));
|
||||
@@ -687,7 +704,7 @@ public class IFFImageReader extends ImageReaderBase {
|
||||
BufferedImage image = reader.read(0, param);
|
||||
System.out.println("image = " + image);
|
||||
|
||||
showIt(image, "");
|
||||
showIt(image, pArgs[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -46,12 +49,16 @@ public class IFFImageReaderSpi extends ImageReaderSpi {
|
||||
static IFFImageReaderSpi mSharedInstance;
|
||||
|
||||
/**
|
||||
* Creates an IFFImageReaderSpi
|
||||
* Creates an {@code IFFImageReaderSpi}.
|
||||
*/
|
||||
public IFFImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(IFFImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private IFFImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"2.0",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"iff", "IFF"},
|
||||
new String[]{"iff", "lbm", "ham", "ham8", "ilbm"},
|
||||
new String[]{"image/iff", "image/x-iff"},
|
||||
|
||||
+7
-5
@@ -47,7 +47,7 @@ import java.io.OutputStream;
|
||||
/**
|
||||
* Writer for Amiga (Electronic Arts) IFF ILBM (InterLeaved BitMap) format.
|
||||
* The IFF format (Interchange File Format) is the standard file format
|
||||
* supported by allmost all image software for the Amiga computer.
|
||||
* supported by almost all image software for the Amiga computer.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
@@ -58,8 +58,6 @@ import java.io.OutputStream;
|
||||
*/
|
||||
public class IFFImageWriter extends ImageWriterBase {
|
||||
|
||||
private static final byte[] ANNO_DATA = "Written by TwelveMonkeys IFFImageWriter 1.0 for Java (javax.imageio).".getBytes();
|
||||
|
||||
public IFFImageWriter() {
|
||||
this(null);
|
||||
}
|
||||
@@ -121,6 +119,7 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
|
||||
private void packImageData(OutputStream pOutput, RenderedImage pImage, ImageWriteParam pParam) throws IOException {
|
||||
// TODO: Allow param to dictate uncompressed
|
||||
// TODO: Allow param to dictate type PBM?
|
||||
// TODO: Subsample/AOI
|
||||
final boolean compress = shouldCompress(pImage);
|
||||
final OutputStream output = compress ? new EncoderStream(pOutput, new PackBitsEncoder(), true) : pOutput;
|
||||
@@ -136,12 +135,14 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
// 2. Perform byteRun1 compression for each plane separately
|
||||
// 3. Write the plane data for each plane
|
||||
|
||||
final int planeWidth = (width + 7) / 8;
|
||||
//final int planeWidth = (width + 7) / 8;
|
||||
final int planeWidth = 2 * ((width + 15) / 16);
|
||||
final byte[] planeData = new byte[8 * planeWidth];
|
||||
final int channels = (model.getPixelSize() + 7) / 8;
|
||||
final int planesPerChannel = channels == 1 ? model.getPixelSize() : 8;
|
||||
int[] pixels = new int[8 * planeWidth];
|
||||
|
||||
// TODO: The spec says "Do not compress across rows!".. I think we currently do.
|
||||
// NOTE: I'm a little unsure if this is correct for 4 channel (RGBA)
|
||||
// data, but it is at least consistent with the IFFImageReader for now...
|
||||
for (int y = 0; y < height; y++) {
|
||||
@@ -174,7 +175,8 @@ public class IFFImageWriter extends ImageWriterBase {
|
||||
|
||||
private void writeMeta(RenderedImage pImage, int pBodyLength) throws IOException {
|
||||
// Annotation ANNO chunk, 8 + annoData.length bytes
|
||||
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), ANNO_DATA);
|
||||
String annotation = "Written by " + getOriginatingProvider().getDescription(null) + " by " + getOriginatingProvider().getVendorName();
|
||||
GenericChunk anno = new GenericChunk(IFFUtil.toInt("ANNO".getBytes()), annotation.getBytes());
|
||||
|
||||
ColorModel cm = pImage.getColorModel();
|
||||
IndexColorModel icm = null;
|
||||
|
||||
+13
-4
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.iff;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
@@ -44,12 +47,17 @@ import java.util.Locale;
|
||||
public class IFFImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
/**
|
||||
* Creates an IFFImageWriterSpi
|
||||
* Creates an {@code IFFImageWriterSpi}.
|
||||
*/
|
||||
public IFFImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(IFFImageWriterSpi.class));
|
||||
|
||||
}
|
||||
|
||||
private IFFImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"$Revision: 1.0 $",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"iff", "IFF"},
|
||||
new String[]{"iff", "lbm", "ham", "ham8", "ilbm"},
|
||||
new String[]{"image/iff", "image/x-iff"},
|
||||
@@ -61,8 +69,9 @@ public class IFFImageWriterSpi extends ImageWriterSpi {
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canEncodeImage(ImageTypeSpecifier pType) {
|
||||
public boolean canEncodeImage(final ImageTypeSpecifier pType) {
|
||||
// TODO: Probably can't store 16 bit types etc...
|
||||
// TODO: Can't store CMYK (well.. it does, but they can't be read back)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -48,7 +48,7 @@ class IFFUtil {
|
||||
|
||||
/**
|
||||
* Creates a rotation table
|
||||
* @param n
|
||||
* @param n number of bits -1
|
||||
*
|
||||
* @return the rotation table
|
||||
*/
|
||||
@@ -57,7 +57,8 @@ class IFFUtil {
|
||||
0x00000000l << n, 0x00000001l << n, 0x00000100l << n, 0x00000101l << n,
|
||||
0x00010000l << n, 0x00010001l << n, 0x00010100l << n, 0x00010101l << n,
|
||||
0x01000000l << n, 0x01000001l << n, 0x01000100l << n, 0x01000101l << n,
|
||||
0x01010000l << n, 0x01010001l << n, 0x01010100l << n, 0x01010101l << n};
|
||||
0x01010000l << n, 0x01010001l << n, 0x01010100l << n, 0x01010101l << n
|
||||
};
|
||||
}
|
||||
|
||||
static private final long[][] RTABLE = {
|
||||
@@ -243,7 +244,7 @@ class IFFUtil {
|
||||
/**
|
||||
* Converts an int to a four letter String.
|
||||
*
|
||||
* @param pChunkId
|
||||
* @param pChunkId the chunk identifier
|
||||
* @return a String
|
||||
*/
|
||||
static String toChunkStr(int pChunkId) {
|
||||
|
||||
+3
-3
@@ -52,13 +52,13 @@ public class IFFImageReaderTestCase extends ImageReaderAbstractTestCase<IFFImage
|
||||
new TestData(getClassLoaderResource("/iff/survivor.iff"), new Dimension(800, 600)), // 24 bit
|
||||
// HAM6 - Ok (a lot of visual "fringe", would be interesting to see on a real HAM display)
|
||||
new TestData(getClassLoaderResource("/iff/A4000T_HAM6.IFF"), new Dimension(320, 512)), // ham6
|
||||
// HAM8 - Passes tests, but visuals are trashed. Have other HAM8 files that are ok
|
||||
// HAM8 - Ok (PackBits decoder chokes on padding byte)
|
||||
new TestData(getClassLoaderResource("/iff/A4000T_HAM8.IFF"), new Dimension(628, 512)), // ham8
|
||||
// 8 color indexed - Passes tests, but trashed. Must be something special with these images
|
||||
// 8 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/AmigaBig.iff"), new Dimension(300, 200)), // 8 color
|
||||
// 8 color indexed - Ok
|
||||
new TestData(getClassLoaderResource("/iff/AmigaAmiga.iff"), new Dimension(200, 150)), // 8 color
|
||||
// Breaks completely... Could be bug in the packbits decoder?
|
||||
// Ok (PackBits decoder chokes on padding byte)
|
||||
new TestData(getClassLoaderResource("/iff/Abyss.iff"), new Dimension(320, 400))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-metadata</artifactId>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO Metadata</name>
|
||||
<description>
|
||||
ImageIO metadata module.
|
||||
</description>
|
||||
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AbstractDirectory
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractDirectory.java,v 1.0 Nov 11, 2009 5:31:04 PM haraldk Exp$
|
||||
*/
|
||||
public abstract class AbstractDirectory implements Directory {
|
||||
private final List<Entry> mEntries = new ArrayList<Entry>();
|
||||
|
||||
protected AbstractDirectory(final Collection<? extends Entry> pEntries) {
|
||||
if (pEntries != null) {
|
||||
mEntries.addAll(pEntries);
|
||||
}
|
||||
}
|
||||
|
||||
public Entry getEntryById(final Object pIdentifier) {
|
||||
for (Entry entry : this) {
|
||||
if (entry.getIdentifier().equals(pIdentifier)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Entry getEntryByFieldName(final String pFieldName) {
|
||||
for (Entry entry : this) {
|
||||
if (entry.getFieldName() != null && entry.getFieldName().equals(pFieldName)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Iterator<Entry> iterator() {
|
||||
return mEntries.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws {@code UnsupportedOperationException} if this directory is read-only.
|
||||
*
|
||||
* @throws UnsupportedOperationException if this directory is read-only.
|
||||
* @see #isReadOnly()
|
||||
*/
|
||||
protected final void assertMutable() {
|
||||
if (isReadOnly()) {
|
||||
throw new UnsupportedOperationException("Directory is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean add(final Entry pEntry) {
|
||||
assertMutable();
|
||||
|
||||
// TODO: Replace if entry is already present?
|
||||
// Some directories may need special ordering, or may/may not support multiple entries for certain ids...
|
||||
return mEntries.add(pEntry);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"SuspiciousMethodCalls"})
|
||||
public boolean remove(final Object pEntry) {
|
||||
assertMutable();
|
||||
|
||||
return mEntries.remove(pEntry);
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return mEntries.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns {@code true}.
|
||||
* Subclasses should override this method, if the directory is mutable.
|
||||
*
|
||||
* @return {@code true}
|
||||
*/
|
||||
public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Standard object support
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mEntries.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
if (this == pOther) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != pOther.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Safe cast, as it must be a subclass for the classes to be equal
|
||||
AbstractDirectory other = (AbstractDirectory) pOther;
|
||||
|
||||
return mEntries.equals(other.mEntries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s%s", getClass().getSimpleName(), mEntries.toString());
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
/**
|
||||
* AbstractEntry
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractEntry.java,v 1.0 Nov 12, 2009 12:43:13 AM haraldk Exp$
|
||||
*/
|
||||
public abstract class AbstractEntry implements Entry {
|
||||
|
||||
private final Object mIdentifier;
|
||||
private final Object mValue; // TODO: Might need to be mutable..
|
||||
|
||||
protected AbstractEntry(final Object pIdentifier, final Object pValue) {
|
||||
Validate.notNull(pIdentifier, "identifier");
|
||||
|
||||
mIdentifier = pIdentifier;
|
||||
mValue = pValue;
|
||||
}
|
||||
|
||||
public final Object getIdentifier() {
|
||||
return mIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code null}, meaning unknown or undefined.
|
||||
*
|
||||
* @return {@code null}.
|
||||
*/
|
||||
public String getFieldName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
|
||||
public String getTypeName() {
|
||||
if (mValue == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mValue.getClass().getSimpleName();
|
||||
}
|
||||
|
||||
public int valueCount() {
|
||||
// TODO: Collection support?
|
||||
if (mValue != null && mValue.getClass().isArray()) {
|
||||
return Array.getLength(mValue);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/// Object
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return mIdentifier.hashCode() + 31 * mValue.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
if (this == pOther) {
|
||||
return true;
|
||||
}
|
||||
if (!(pOther instanceof AbstractEntry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AbstractEntry other = (AbstractEntry) pOther;
|
||||
|
||||
return mIdentifier.equals(other.mIdentifier) && (
|
||||
mValue == null && other.mValue == null || mValue != null && mValue.equals(other.mValue)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String name = getFieldName();
|
||||
String nameStr = name != null ? "/" + name + "" : "";
|
||||
|
||||
String type = getTypeName();
|
||||
String typeStr = type != null ? " (" + type + ")" : "";
|
||||
|
||||
return String.format("%s%s: %s%s", getIdentifier(), nameStr, getValueAsString(), typeStr);
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
/**
|
||||
* Directory
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Directory.java,v 1.0 Nov 11, 2009 4:20:58 PM haraldk Exp$
|
||||
*/
|
||||
public interface Directory extends Iterable<Entry> {
|
||||
// TODO: Spec when more entries exist? Or make Entry support multi-values!?
|
||||
// For multiple entries with same id in directory, the first entry (using the order from the stream) will be returned
|
||||
Entry getEntryById(Object pIdentifier);
|
||||
|
||||
Entry getEntryByFieldName(String pName);
|
||||
|
||||
// Iterator containing the entries in
|
||||
//Iterator<Entry> getBestEntries(Object pIdentifier, Object pQualifier, String pLanguage);
|
||||
|
||||
|
||||
/// Collection-like API
|
||||
// TODO: addOrReplaceIfPresent... (trouble for multi-values) Or mutable entries?
|
||||
// boolean replace(Entry pEntry)??
|
||||
// boolean contains(Object pIdentifier)?
|
||||
|
||||
boolean add(Entry pEntry);
|
||||
|
||||
boolean remove(Object pEntry); // Object in case we retro-fit Collection/Map..
|
||||
|
||||
int size();
|
||||
|
||||
boolean isReadOnly();
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
/**
|
||||
* Entry
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Entry.java,v 1.0 Nov 11, 2009 4:21:08 PM haraldk Exp$
|
||||
*/
|
||||
public interface Entry {
|
||||
// "tag" identifier from spec
|
||||
Object getIdentifier();
|
||||
|
||||
// Human readable "tag" (field) name from sepc
|
||||
String getFieldName();
|
||||
|
||||
// The internal "tag" value as stored in the stream, may be a Directory
|
||||
Object getValue();
|
||||
|
||||
// Human readable "tag" value
|
||||
String getValueAsString();
|
||||
|
||||
//void setValue(Object pValue); // TODO: qualifiers...
|
||||
|
||||
// Optional, implementation/spec specific type, describing the object returned from getValue
|
||||
String getTypeName();
|
||||
|
||||
// TODO: Or something like getValue(qualifierType, qualifierValue) + getQualifiers()/getQualifierValues
|
||||
// TODO: The problem with current model is getEntry() which only has single value support
|
||||
|
||||
// Optional, xml:lang-support
|
||||
//String getLanguage();
|
||||
|
||||
// Optional, XMP alt-support. TODO: Do we need both?
|
||||
//Object getQualifier();
|
||||
|
||||
// For arrays only
|
||||
int valueCount();
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* MetadataReader
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: MetadataReader.java,v 1.0 Nov 13, 2009 8:38:11 PM haraldk Exp$
|
||||
*/
|
||||
public abstract class MetadataReader {
|
||||
public abstract Directory read(ImageInputStream pInput) throws IOException;
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
/**
|
||||
* EXIF
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
||||
*/
|
||||
public interface EXIF {
|
||||
int TAG_COLOR_SPACE = 40961;
|
||||
int TAG_PIXEL_X_DIMENSION = 40962;
|
||||
int TAG_PIXEL_Y_DIMENSION = 40963;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* EXIFDirectory
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$
|
||||
*/
|
||||
final class EXIFDirectory extends AbstractDirectory {
|
||||
EXIFDirectory(final Collection<? extends Entry> pEntries) {
|
||||
super(pEntries);
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
|
||||
/**
|
||||
* EXIFEntry
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFEntry.java,v 1.0 Nov 13, 2009 5:47:35 PM haraldk Exp$
|
||||
*/
|
||||
final class EXIFEntry extends AbstractEntry {
|
||||
final private short mType;
|
||||
|
||||
EXIFEntry(final int pIdentifier, final Object pValue, final short pType) {
|
||||
super(pIdentifier, pValue);
|
||||
|
||||
if (pType < 1 || pType > TIFF.TYPE_NAMES.length) {
|
||||
throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType));
|
||||
}
|
||||
|
||||
mType = pType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
switch ((Integer) getIdentifier()) {
|
||||
case TIFF.TAG_COMPRESSION:
|
||||
return "Compression";
|
||||
case TIFF.TAG_ORIENTATION:
|
||||
return "Orientation";
|
||||
case TIFF.TAG_X_RESOLUTION:
|
||||
return "XResolution";
|
||||
case TIFF.TAG_Y_RESOLUTION:
|
||||
return "YResolution";
|
||||
case TIFF.TAG_RESOLUTION_UNIT:
|
||||
return "ResolutionUnit";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT:
|
||||
return "JPEGInterchangeFormat";
|
||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||
return "JPEGInterchangeFormatLength";
|
||||
case TIFF.TAG_SOFTWARE:
|
||||
return "Software";
|
||||
case TIFF.TAG_DATE_TIME:
|
||||
return "DateTime";
|
||||
case TIFF.TAG_ARTIST:
|
||||
return "Artist";
|
||||
case TIFF.TAG_COPYRIGHT:
|
||||
return "Copyright";
|
||||
|
||||
case EXIF.TAG_COLOR_SPACE:
|
||||
return "ColorSpace";
|
||||
case EXIF.TAG_PIXEL_X_DIMENSION:
|
||||
return "PixelXDimension";
|
||||
case EXIF.TAG_PIXEL_Y_DIMENSION:
|
||||
return "PixelYDimension";
|
||||
// TODO: More field names
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return TIFF.TYPE_NAMES[mType - 1];
|
||||
}
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* EXIFReader
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFReader.java,v 1.0 Nov 13, 2009 5:42:51 PM haraldk Exp$
|
||||
*/
|
||||
public final class EXIFReader extends MetadataReader {
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||
byte[] bom = new byte[2];
|
||||
pInput.readFully(bom);
|
||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
||||
throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
}
|
||||
|
||||
int magic = pInput.readUnsignedShort();
|
||||
if (magic != TIFF.TIFF_MAGIC) {
|
||||
throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC));
|
||||
}
|
||||
|
||||
long directoryOffset = pInput.readUnsignedInt();
|
||||
|
||||
return readDirectory(pInput, directoryOffset);
|
||||
}
|
||||
|
||||
private EXIFDirectory readDirectory(final ImageInputStream pInput, final long pOffset) throws IOException {
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
int entryCount = pInput.readUnsignedShort();
|
||||
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
entries.add(readEntry(pInput));
|
||||
}
|
||||
|
||||
long nextOffset = pInput.readUnsignedInt();
|
||||
|
||||
if (nextOffset != 0) {
|
||||
EXIFDirectory next = readDirectory(pInput, nextOffset);
|
||||
|
||||
for (Entry entry : next) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return new EXIFDirectory(entries);
|
||||
}
|
||||
|
||||
private EXIFEntry readEntry(final ImageInputStream pInput) throws IOException {
|
||||
int tagId = pInput.readUnsignedShort();
|
||||
|
||||
short type = pInput.readShort();
|
||||
int count = pInput.readInt(); // Number of values
|
||||
|
||||
Object value;
|
||||
|
||||
if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) {
|
||||
// Parse sub IFDs
|
||||
long offset = pInput.readUnsignedInt();
|
||||
pInput.mark();
|
||||
|
||||
try {
|
||||
value = readDirectory(pInput, offset);
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
int valueLength = getValueLength(type, count);
|
||||
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
value = readValueInLine(pInput, type, count);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
}
|
||||
else {
|
||||
long valueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
value = readValue(pInput, valueOffset, type, count);
|
||||
}
|
||||
}
|
||||
|
||||
return new EXIFEntry(tagId, value, type);
|
||||
}
|
||||
|
||||
private Object readValue(final ImageInputStream pInput, final long pOffset, final short pType, final int pCount) throws IOException {
|
||||
long pos = pInput.getStreamPosition();
|
||||
try {
|
||||
pInput.seek(pOffset);
|
||||
return readValueInLine(pInput, pType, pCount);
|
||||
}
|
||||
finally {
|
||||
pInput.seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private Object readValueInLine(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
return readValueDirect(pInput, pType, pCount);
|
||||
}
|
||||
|
||||
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
switch (pType) {
|
||||
case 2:
|
||||
// TODO: This might be UTF-8 or ISO-8859-1, even though spec says ASCII
|
||||
byte[] ascii = new byte[pCount];
|
||||
pInput.readFully(ascii);
|
||||
return StringUtil.decode(ascii, 0, ascii.length, "UTF-8"); // UTF-8 is ASCII compatible
|
||||
case 1:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedByte();
|
||||
}
|
||||
case 6:
|
||||
if (pCount == 1) {
|
||||
return pInput.readByte();
|
||||
}
|
||||
case 7:
|
||||
byte[] bytes = new byte[pCount];
|
||||
pInput.readFully(bytes);
|
||||
return bytes;
|
||||
case 3:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedShort();
|
||||
}
|
||||
case 8:
|
||||
if (pCount == 1) {
|
||||
return pInput.readShort();
|
||||
}
|
||||
|
||||
short[] shorts = new short[pCount];
|
||||
pInput.readFully(shorts, 0, shorts.length);
|
||||
return shorts;
|
||||
case 4:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt();
|
||||
}
|
||||
case 9:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt();
|
||||
}
|
||||
|
||||
int[] ints = new int[pCount];
|
||||
pInput.readFully(ints, 0, ints.length);
|
||||
return ints;
|
||||
case 11:
|
||||
if (pCount == 1) {
|
||||
return pInput.readFloat();
|
||||
}
|
||||
|
||||
float[] floats = new float[pCount];
|
||||
pInput.readFully(floats, 0, floats.length);
|
||||
return floats;
|
||||
case 12:
|
||||
if (pCount == 1) {
|
||||
return pInput.readDouble();
|
||||
}
|
||||
|
||||
double[] doubles = new double[pCount];
|
||||
pInput.readFully(doubles, 0, doubles.length);
|
||||
return doubles;
|
||||
|
||||
case 5:
|
||||
if (pCount == 1) {
|
||||
return new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
Rational[] rationals = new Rational[pCount];
|
||||
for (int i = 0; i < rationals.length; i++) {
|
||||
rationals[i] = new Rational(pInput.readUnsignedInt(), pInput.readUnsignedInt());
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case 10:
|
||||
if (pCount == 1) {
|
||||
return new Rational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
Rational[] srationals = new Rational[pCount];
|
||||
for (int i = 0; i < srationals.length; i++) {
|
||||
srationals[i] = new Rational(pInput.readInt(), pInput.readInt());
|
||||
}
|
||||
|
||||
return srationals;
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
||||
}
|
||||
}
|
||||
|
||||
private int getValueLength(final int pType, final int pCount) {
|
||||
if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) {
|
||||
return TIFF.TYPE_LENGTHS[pType - 1] * pCount;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Adapted from sample code featured in
|
||||
* "Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)
|
||||
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
/**
|
||||
* Represents a rational number with a {@code long} numerator and {@code long} denominator.
|
||||
* Rational numbers are stored in reduced form with the sign stored with the numerator.
|
||||
* Rationals are immutable.
|
||||
* <p/>
|
||||
* Adapted from sample code featured in
|
||||
* <a href="http://www.cs.princeton.edu/introcs/home/">"Intro to Programming in Java: An Interdisciplinary Approach" (Addison Wesley)</a>
|
||||
* by Robert Sedgewick and Kevin Wayne. Permission granted to redistribute under BSD license.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author <a href="http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html">Robert Sedgewick and Kevin Wayne (original version)</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: Rational.java,v 1.0 Nov 18, 2009 1:12:00 AM haraldk Exp$
|
||||
*/
|
||||
public final class Rational extends Number implements Comparable<Rational> {
|
||||
// TODO: Document public API
|
||||
// TODO: Move to com.tm.lang?
|
||||
// Inspired by http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html and java.lang.Integer
|
||||
static final Rational ZERO = new Rational(0, 1);
|
||||
|
||||
private final long mNumerator;
|
||||
private final long mDenominator;
|
||||
|
||||
public Rational(final long pNumber) {
|
||||
this(pNumber, 1);
|
||||
}
|
||||
|
||||
public Rational(final long pNumerator, final long pDenominator) {
|
||||
if (pDenominator == 0) {
|
||||
throw new IllegalArgumentException("denominator == 0");
|
||||
}
|
||||
if (pNumerator == Long.MIN_VALUE || pDenominator == Long.MIN_VALUE) {
|
||||
throw new IllegalArgumentException("value == Long.MIN_VALUE");
|
||||
}
|
||||
|
||||
// Reduce fractions
|
||||
long gcd = gcd(pNumerator, pDenominator);
|
||||
long num = pNumerator / gcd;
|
||||
long den = pDenominator / gcd;
|
||||
|
||||
mNumerator = pDenominator >= 0 ? num : -num;
|
||||
mDenominator = pDenominator >= 0 ? den : -den;
|
||||
}
|
||||
|
||||
private static long gcd(final long m, final long n) {
|
||||
if (m < 0) {
|
||||
return gcd(n, -m);
|
||||
}
|
||||
|
||||
return n == 0 ? m : gcd(n, m % n);
|
||||
}
|
||||
|
||||
private static long lcm(final long m, final long n) {
|
||||
if (m < 0) {
|
||||
return lcm(n, -m);
|
||||
}
|
||||
|
||||
return m * (n / gcd(m, n)); // parentheses important to avoid overflow
|
||||
}
|
||||
|
||||
public long numerator() {
|
||||
return mNumerator;
|
||||
}
|
||||
|
||||
public long denominator() {
|
||||
return mDenominator;
|
||||
}
|
||||
|
||||
/// Number implementation
|
||||
|
||||
@Override
|
||||
public int intValue() {
|
||||
return (int) doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long longValue() {
|
||||
return (long) doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float floatValue() {
|
||||
return (float) doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double doubleValue() {
|
||||
return mNumerator / (double) mDenominator;
|
||||
}
|
||||
|
||||
/// Comparable implementation
|
||||
|
||||
public int compareTo(final Rational pOther) {
|
||||
double thisVal = doubleValue();
|
||||
double otherVal = pOther.doubleValue();
|
||||
|
||||
return thisVal < otherVal ? -1 : thisVal == otherVal ? 0 : 1;
|
||||
}
|
||||
|
||||
/// Object overrides
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Float.floatToIntBits(floatValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
return pOther == this || pOther instanceof Rational && compareTo((Rational) pOther) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mDenominator == 1 ? Long.toString(mNumerator) : String.format("%s/%s", mNumerator, mDenominator);
|
||||
}
|
||||
|
||||
/// Operations (adapted from http://www.cs.princeton.edu/introcs/92symbolic/Rational.java.html)
|
||||
// TODO: Naming! multiply/divide/add/subtract or times/divides/plus/minus
|
||||
|
||||
// return a * b, staving off overflow as much as possible by cross-cancellation
|
||||
public Rational times(final Rational pOther) {
|
||||
// special cases
|
||||
if (equals(ZERO) || pOther.equals(ZERO)) {
|
||||
return ZERO;
|
||||
}
|
||||
|
||||
// reduce p1/q2 and p2/q1, then multiply, where a = p1/q1 and b = p2/q2
|
||||
Rational c = new Rational(mNumerator, pOther.mDenominator);
|
||||
Rational d = new Rational(pOther.mNumerator, mDenominator);
|
||||
|
||||
return new Rational(c.mNumerator * d.mNumerator, c.mDenominator * d.mDenominator);
|
||||
}
|
||||
|
||||
// return a + b, staving off overflow
|
||||
public Rational plus(final Rational pOther) {
|
||||
// special cases
|
||||
if (equals(ZERO)) {
|
||||
return pOther;
|
||||
}
|
||||
if (pOther.equals(ZERO)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
// Find gcd of numerators and denominators
|
||||
long f = gcd(mNumerator, pOther.mNumerator);
|
||||
long g = gcd(mDenominator, pOther.mDenominator);
|
||||
|
||||
// add cross-product terms for numerator
|
||||
// multiply back in
|
||||
return new Rational(
|
||||
((mNumerator / f) * (pOther.mDenominator / g) + (pOther.mNumerator / f) * (mDenominator / g)) * f,
|
||||
lcm(mDenominator, pOther.mDenominator)
|
||||
);
|
||||
}
|
||||
|
||||
// return -a
|
||||
public Rational negate() {
|
||||
return new Rational(-mNumerator, mDenominator);
|
||||
}
|
||||
|
||||
// return a - b
|
||||
public Rational minus(final Rational pOther) {
|
||||
return plus(pOther.negate());
|
||||
}
|
||||
|
||||
public Rational reciprocal() {
|
||||
return new Rational(mDenominator, mNumerator);
|
||||
}
|
||||
|
||||
// return a / b
|
||||
public Rational divides(final Rational pOther) {
|
||||
if (pOther.equals(ZERO)) {
|
||||
throw new ArithmeticException("/ by zero");
|
||||
}
|
||||
|
||||
return times(pOther.reciprocal());
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
/**
|
||||
* TIFF
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$
|
||||
*/
|
||||
public interface TIFF {
|
||||
int TIFF_MAGIC = 42;
|
||||
|
||||
/*
|
||||
1 = BYTE 8-bit unsigned integer.
|
||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||
must be NUL (binary zero).
|
||||
3 = SHORT 16-bit (2-byte) unsigned integer.
|
||||
4 = LONG 32-bit (4-byte) unsigned integer.
|
||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||
fraction; the second, the denominator.
|
||||
|
||||
TIFF 6.0 and above:
|
||||
6 = SBYTE An 8-bit signed (twos-complement) integer.
|
||||
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
|
||||
the definition of the field.
|
||||
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
||||
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
||||
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
||||
fraction, the second the denominator.
|
||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||
*/
|
||||
String[] TYPE_NAMES = {
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
};
|
||||
int[] TYPE_LENGTHS = {
|
||||
1, 1, 2, 4, 8,
|
||||
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
};
|
||||
|
||||
int IFD_EXIF = 0x8769;
|
||||
int IFD_GPS = 0x8825;
|
||||
int IFD_INTEROP = 0xA005;
|
||||
|
||||
/// A. Tags relating to image data structure:
|
||||
|
||||
int TAG_IMAGE_WIDTH = 256;
|
||||
int TAG_IMAGE_HEIGHT = 257;
|
||||
int TAG_BITS_PER_SAMPLE = 258;
|
||||
int TAG_COMPRESSION = 259;
|
||||
int TAG_PHOTOMETRIC_INTERPRETATION = 262;
|
||||
int TAG_ORIENTATION = 274;
|
||||
int TAG_SAMPLES_PER_PIXELS = 277;
|
||||
int TAG_PLANAR_CONFIGURATION = 284;
|
||||
int TAG_YCBCR_SUB_SAMPLING = 530;
|
||||
int TAG_YCBCR_POSITIONING = 531;
|
||||
int TAG_X_RESOLUTION = 282;
|
||||
int TAG_Y_RESOLUTION = 283;
|
||||
int TAG_RESOLUTION_UNIT = 296;
|
||||
|
||||
/// B. Tags relating to recording offset
|
||||
|
||||
int TAG_STRIP_OFFSETS = 273;
|
||||
int TAG_ROWS_PER_STRIP = 278;
|
||||
int TAG_STRIP_BYTE_COUNTS = 279;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT = 513;
|
||||
int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 514;
|
||||
|
||||
/// C. Tags relating to image data characteristics
|
||||
|
||||
int TAG_TRANSFER_FUNCTION = 301;
|
||||
int TAG_WHITE_POINT = 318;
|
||||
int TAG_PRIMARY_CHROMATICITIES = 319;
|
||||
int TAG_YCBCR_COEFFICIENTS = 529;
|
||||
int TAG_REFERENCE_BLACK_WHITE = 532;
|
||||
|
||||
/// D. Other tags
|
||||
|
||||
int TAG_DATE_TIME = 306;
|
||||
int TAG_IMAGE_DESCRIPTION = 270;
|
||||
int TAG_MAKE = 271;
|
||||
int TAG_MODEL = 272;
|
||||
int TAG_SOFTWARE = 305;
|
||||
int TAG_ARTIST = 315;
|
||||
int TAG_COPYRIGHT = 33432;
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
/**
|
||||
* IPTC
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$
|
||||
*/
|
||||
public 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: 2:150-2:154 Audio
|
||||
|
||||
// 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_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
|
||||
|
||||
// TODO: Other custom fields in 155-200 range?
|
||||
|
||||
// TODO: 2:200-2:202 Object Preview Data
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* IPTCDirectory
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IPTCDirectory.java,v 1.0 Nov 11, 2009 5:02:59 PM haraldk Exp$
|
||||
*/
|
||||
final class IPTCDirectory extends AbstractDirectory {
|
||||
IPTCDirectory(final Collection<? extends Entry> pEntries) {
|
||||
super(pEntries);
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
|
||||
/**
|
||||
* IPTCEntry
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IPTCEntry.java,v 1.0 Nov 13, 2009 8:57:04 PM haraldk Exp$
|
||||
*/
|
||||
class IPTCEntry extends AbstractEntry {
|
||||
public IPTCEntry(final int pTagId, final Object pValue) {
|
||||
super(pTagId, pValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
switch ((Integer) getIdentifier()) {
|
||||
case IPTC.TAG_SOURCE:
|
||||
return "Source";
|
||||
// TODO: More tags...
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.iptc;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
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.CharacterCodingException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.CharsetDecoder;
|
||||
import java.nio.charset.CodingErrorAction;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IPTCReader
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: IPTCReader.java,v 1.0 Nov 13, 2009 8:37:23 PM haraldk Exp$
|
||||
*/
|
||||
public final class IPTCReader extends MetadataReader {
|
||||
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;
|
||||
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||
final List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
// 0x1c identifies start of a tag
|
||||
while (pInput.read() == 0x1c) {
|
||||
short tagId = pInput.readShort();
|
||||
int tagByteCount = pInput.readUnsignedShort();
|
||||
Entry entry = readEntry(pInput, tagId, tagByteCount);
|
||||
|
||||
if (entry != null) {
|
||||
entries.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCDirectory(entries);
|
||||
}
|
||||
|
||||
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException {
|
||||
Object value = null;
|
||||
|
||||
switch (pTagId) {
|
||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
||||
// TODO: Mapping from ISO 646 to Java supported character sets?
|
||||
// TODO: Move somewhere else?
|
||||
mEncoding = parseEncoding(pInput, pLength);
|
||||
return null;
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
// A single unsigned short value
|
||||
value = pInput.readUnsignedShort();
|
||||
break;
|
||||
default:
|
||||
// Skip non-Application fields, as they are typically not human readable
|
||||
if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) {
|
||||
pInput.skipBytes(pLength);
|
||||
return null;
|
||||
}
|
||||
|
||||
// fall through
|
||||
}
|
||||
|
||||
// If we don't have a value, treat it as a string
|
||||
if (value == null) {
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
return new IPTCEntry(pTagId, value);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// TODO: Pass encoding as parameter? Use if specified
|
||||
private String parseString(final ImageInputStream pInput, final int pLength) throws IOException {
|
||||
byte[] data = new byte[pLength];
|
||||
pInput.readFully(data);
|
||||
|
||||
// NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding.
|
||||
// UTF-8 contains all 646 characters, but not 2022.
|
||||
// This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html
|
||||
Charset charset = Charset.forName("UTF-8");
|
||||
CharsetDecoder decoder = charset.newDecoder();
|
||||
|
||||
try {
|
||||
// First try to decode using UTF-8 (which seems to be the de-facto standard)
|
||||
// Will fail fast on illegal UTF-8-sequences
|
||||
CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
|
||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
||||
.decode(ByteBuffer.wrap(data));
|
||||
return 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
|
||||
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* XMP
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMP.java,v 1.0 Nov 12, 2009 12:19:32 AM haraldk Exp$
|
||||
*
|
||||
* @see <a href="http://www.adobe.com/products/xmp/">Extensible Metadata Platform (XMP)</a>
|
||||
*/
|
||||
public interface XMP {
|
||||
/** W3C Resource Description Format namespace */
|
||||
String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
|
||||
|
||||
/** Dublin Core Metadata Initiative namespace */
|
||||
String NS_DC = "http://purl.org/dc/elements/1.1/";
|
||||
|
||||
String NS_EXIF = "http://ns.adobe.com/exif/1.0/";
|
||||
|
||||
String NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/";
|
||||
|
||||
String NS_ST_REF = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#";
|
||||
|
||||
String NS_TIFF = "http://ns.adobe.com/tiff/1.0/";
|
||||
|
||||
String NS_XAP = "http://ns.adobe.com/xap/1.0/";
|
||||
|
||||
String NS_XAP_MM = "http://ns.adobe.com/xap/1.0/mm/";
|
||||
|
||||
/** Contains the mapping from URI to default namespace prefix. */
|
||||
Map<String, String> DEFAULT_NS_MAPPING = Collections.unmodifiableMap(new XMPNamespaceMapping());
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* XMPDirectory
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMPDirectory.java,v 1.0 Nov 17, 2009 9:38:58 PM haraldk Exp$
|
||||
*/
|
||||
final class XMPDirectory extends AbstractDirectory {
|
||||
// TODO: Store size of root directory, to allow serializing
|
||||
// TODO: XMPDirectory, maybe not even an AbstractDirectory
|
||||
// - Keeping the Document would allow for easier serialization
|
||||
// TODO: Or use direct SAX parsing
|
||||
public XMPDirectory(List<Entry> pEntries) {
|
||||
super(pEntries);
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
|
||||
/**
|
||||
* XMPEntry
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMPEntry.java,v 1.0 Nov 17, 2009 9:38:39 PM haraldk Exp$
|
||||
*/
|
||||
final class XMPEntry extends AbstractEntry {
|
||||
private final String mFieldName;
|
||||
|
||||
public XMPEntry(final String pIdentifier, final Object pValue) {
|
||||
this(pIdentifier, null, pValue);
|
||||
}
|
||||
|
||||
public XMPEntry(final String pIdentifier, final String pFieldName, final Object pValue) {
|
||||
super(pIdentifier, pValue);
|
||||
mFieldName = pFieldName;
|
||||
}
|
||||
|
||||
@SuppressWarnings({"SuspiciousMethodCalls"})
|
||||
@Override
|
||||
public String getFieldName() {
|
||||
return mFieldName != null ? mFieldName : XMP.DEFAULT_NS_MAPPING.get(getIdentifier());
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* XMPNamespaceMapping
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMPNamespaceMapping.java,v 1.0 Nov 17, 2009 6:35:21 PM haraldk Exp$
|
||||
*/
|
||||
final class XMPNamespaceMapping extends HashMap<String, String> {
|
||||
public XMPNamespaceMapping() {
|
||||
put(XMP.NS_RDF, "rdf");
|
||||
put(XMP.NS_DC, "dc");
|
||||
put(XMP.NS_EXIF, "exif");
|
||||
put(XMP.NS_PHOTOSHOP, "photoshop");
|
||||
put(XMP.NS_ST_REF, "stRef");
|
||||
put(XMP.NS_TIFF, "tiff");
|
||||
put(XMP.NS_XAP, "xap");
|
||||
put(XMP.NS_XAP_MM, "xapMM");
|
||||
}
|
||||
}
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataReader;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* XMPReader
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMPReader.java,v 1.0 Nov 14, 2009 11:04:30 PM haraldk Exp$
|
||||
*/
|
||||
public final class XMPReader extends MetadataReader {
|
||||
@Override
|
||||
public Directory read(final ImageInputStream pInput) throws IOException {
|
||||
// pInput.mark();
|
||||
//
|
||||
// BufferedReader reader = new BufferedReader(new InputStreamReader(IIOUtil.createStreamAdapter(pInput), Charset.forName("UTF-8")));
|
||||
// String line;
|
||||
// while ((line = reader.readLine()) != null) {
|
||||
// System.out.println(line);
|
||||
// }
|
||||
//
|
||||
// pInput.reset();
|
||||
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
|
||||
try {
|
||||
// TODO: Consider parsing using SAX?
|
||||
// TODO: Determine encoding and parse using a Reader...
|
||||
// TODO: Refactor scanner to return inputstream?
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(pInput)));
|
||||
|
||||
// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding"));
|
||||
// serializer.serialize(document);
|
||||
|
||||
|
||||
// Each rdf:Description is a Directory (but we can't really rely on that structure.. it's only convention)
|
||||
// - Each element inside the rdf:Desc is an Entry
|
||||
|
||||
Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0);
|
||||
NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description");
|
||||
|
||||
return parseDirectories(rdfRoot, descriptions);
|
||||
}
|
||||
catch (SAXException e) {
|
||||
throw new IIOException(e.getMessage(), e);
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
throw new RuntimeException(e); // TODO: Or IOException?
|
||||
}
|
||||
}
|
||||
|
||||
private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes) {
|
||||
Map<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
|
||||
|
||||
for (Node desc : asIterable(pNodes)) {
|
||||
if (desc.getParentNode() != pParentNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Node node : asIterable(desc.getChildNodes())) {
|
||||
if (node.getNodeType() != Node.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lookup
|
||||
List<Entry> dir = subdirs.get(node.getNamespaceURI());
|
||||
if (dir == null) {
|
||||
dir = new ArrayList<Entry>();
|
||||
subdirs.put(node.getNamespaceURI(), dir);
|
||||
}
|
||||
|
||||
Object value;
|
||||
|
||||
Node parseType = node.getAttributes().getNamedItemNS(XMP.NS_RDF, "parseType");
|
||||
if (parseType != null && "Resource".equals(parseType.getNodeValue())) {
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
for (Node child : asIterable(node.getChildNodes())) {
|
||||
if (child.getNodeType() != Node.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
entries.add(new XMPEntry(child.getNamespaceURI() + child.getLocalName(), child.getLocalName(), getChildTextValue(child)));
|
||||
}
|
||||
value = new XMPDirectory(entries);
|
||||
}
|
||||
else {
|
||||
// TODO: Support alternative RDF syntax (short-form), using attributes on desc
|
||||
// NamedNodeMap attributes = node.getAttributes();
|
||||
//
|
||||
// for (Node attr : asIterable(attributes)) {
|
||||
// System.out.println("attr.getNodeName(): " + attr.getNodeName());
|
||||
// System.out.println("attr.getNodeValue(): " + attr.getNodeValue());
|
||||
// }
|
||||
|
||||
value = getChildTextValue(node);
|
||||
}
|
||||
|
||||
XMPEntry entry = new XMPEntry(node.getNamespaceURI() + node.getLocalName(), node.getLocalName(), value);
|
||||
dir.add(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Consider flattening the somewhat artificial directory structure
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
for (Map.Entry<String, List<Entry>> entry : subdirs.entrySet()) {
|
||||
entries.add(new XMPEntry(entry.getKey(), new XMPDirectory(entry.getValue())));
|
||||
}
|
||||
|
||||
return new XMPDirectory(entries);
|
||||
}
|
||||
|
||||
private Object getChildTextValue(Node node) {
|
||||
Object value;
|
||||
Node child = node.getFirstChild();
|
||||
|
||||
String strVal = null;
|
||||
if (child != null) {
|
||||
strVal = child.getNodeValue();
|
||||
}
|
||||
|
||||
value = strVal != null ? strVal.trim() : "";
|
||||
return value;
|
||||
}
|
||||
|
||||
private Iterable<? extends Node> asIterable(final NamedNodeMap pNodeList) {
|
||||
return new Iterable<Node>() {
|
||||
public Iterator<Node> iterator() {
|
||||
return new Iterator<Node>() {
|
||||
private int mIndex;
|
||||
|
||||
public boolean hasNext() {
|
||||
return pNodeList != null && pNodeList.getLength() > mIndex;
|
||||
}
|
||||
|
||||
public Node next() {
|
||||
return pNodeList.item(mIndex++);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Method remove not supported");
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Iterable<? extends Node> asIterable(final NodeList pNodeList) {
|
||||
return new Iterable<Node>() {
|
||||
public Iterator<Node> iterator() {
|
||||
return new Iterator<Node>() {
|
||||
private int mIndex;
|
||||
|
||||
public boolean hasNext() {
|
||||
return pNodeList != null && pNodeList.getLength() > mIndex;
|
||||
}
|
||||
|
||||
public Node next() {
|
||||
return pNodeList.item(mIndex++);
|
||||
}
|
||||
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Method remove not supported");
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (c) 2009, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.BufferedImageInputStream;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* XMPScanner
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMPScanner.java,v 1.0 Nov 11, 2009 4:49:00 PM haraldk Exp$
|
||||
*/
|
||||
public final class XMPScanner {
|
||||
/**
|
||||
* {@code <?xpacket begin=}
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>
|
||||
* 8-bit (UTF-8):
|
||||
* 0x3C 0x3F 0x78 0x70 0x61 0x63 0x6B 0x65 0x74 0x20
|
||||
* 0x62 0x65 0x67 0x69 0x6E 0x3D
|
||||
* </li>
|
||||
* <li>16-bit encoding (UCS-2, UTF-16): (either big- or little-endian order)
|
||||
* 0x3C 0x00 0x3F 0x00 0x78 0x00 0x70 0x00 0x61 0x00
|
||||
* 0x63 0x00 0x6B 0x00 0x65 0x00 0x74 0x00 0x20 0x00 0x62 0x00
|
||||
* 0x65 0x00 0x67 0x00 0x69 0x00 0x6E 0x00 0x3D [0x00]
|
||||
* </li>
|
||||
* <li>32-bit encoding (UCS-4):
|
||||
* As 16 bit UCS2, with three 0x00 instead of one.</li>
|
||||
* </ul>
|
||||
*/
|
||||
private static final byte[] XMP_PACKET_BEGIN = {
|
||||
0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20,
|
||||
0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D
|
||||
};
|
||||
|
||||
/**
|
||||
* {@code <?xpacket end=}
|
||||
*/
|
||||
private static final byte[] XMP_PACKET_END = {
|
||||
0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20,
|
||||
0x65, 0x6E, 0x64, 0x3D
|
||||
};
|
||||
|
||||
/**
|
||||
* Scans the given input for an XML metadata packet.
|
||||
* The scanning process involves reading every byte in the file, while searching for an XMP packet.
|
||||
* This process is very inefficient, compared to reading a known file format.
|
||||
* <p/>
|
||||
* <em>NOTE: The XMP Specification says this method of reading an XMP packet
|
||||
* should be considered a last resort.</em><br/>
|
||||
* This is because files may contain multiple XMP packets, some which may be related to embedded resources,
|
||||
* some which may be obsolete (or even incomplete).
|
||||
*
|
||||
* @param pInput the input to scan. The input may be an {@link javax.imageio.stream.ImageInputStream} or
|
||||
* any object that can be passed to {@link ImageIO#createImageInputStream(Object)}.
|
||||
* Typically this may be a {@link File}, {@link InputStream} or {@link java.io.RandomAccessFile}.
|
||||
*
|
||||
* @return a character Reader
|
||||
*
|
||||
* @throws java.nio.charset.UnsupportedCharsetException if the encoding specified within the BOM is not supported
|
||||
* by the JRE.
|
||||
* @throws IOException if an I/O exception occurs reading from {@code pInput}.
|
||||
* @see ImageIO#createImageInputStream(Object)
|
||||
*/
|
||||
static public Reader scanForXMPPacket(final Object pInput) throws IOException {
|
||||
ImageInputStream stream = pInput instanceof ImageInputStream ? (ImageInputStream) pInput : ImageIO.createImageInputStream(pInput);
|
||||
|
||||
// TODO: Consider if BufferedIIS is a good idea
|
||||
if (!(stream instanceof BufferedImageInputStream)) {
|
||||
stream = new BufferedImageInputStream(stream);
|
||||
}
|
||||
|
||||
// TODO: Might be more than one XMP block per file (it's possible to re-start for now)..
|
||||
long pos;
|
||||
pos = scanForSequence(stream, XMP_PACKET_BEGIN);
|
||||
|
||||
if (pos >= 0) {
|
||||
// Skip ' OR " (plus possible nulls for 16/32 bit)
|
||||
byte quote = stream.readByte();
|
||||
|
||||
if (quote == '\'' || quote == '"') {
|
||||
Charset cs = null;
|
||||
|
||||
// Read BOM
|
||||
byte[] bom = new byte[4];
|
||||
stream.readFully(bom);
|
||||
|
||||
// NOTE: Empty string should be treated as UTF-8 for backwards compatibility
|
||||
if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF && bom[3] == quote ||
|
||||
bom[0] == quote) {
|
||||
// UTF-8
|
||||
cs = Charset.forName("UTF-8");
|
||||
}
|
||||
else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF && bom[2] == 0x00 && bom[3] == quote) {
|
||||
// UTF-16 BIG endian
|
||||
cs = Charset.forName("UTF-16BE");
|
||||
}
|
||||
else if (bom[0] == 0x00 && bom[1] == (byte) 0xFF && bom[2] == (byte) 0xFE && bom[3] == quote) {
|
||||
stream.skipBytes(1); // Alignment
|
||||
|
||||
// UTF-16 little endian
|
||||
cs = Charset.forName("UTF-16LE");
|
||||
}
|
||||
else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF) {
|
||||
// NOTE: 32-bit character set not supported by default
|
||||
// UTF 32 BIG endian
|
||||
cs = Charset.forName("UTF-32BE");
|
||||
}
|
||||
else if (bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) {
|
||||
// TODO: FixMe..
|
||||
// NOTE: 32-bit character set not supported by default
|
||||
// UTF 32 little endian
|
||||
cs = Charset.forName("UTF-32LE");
|
||||
}
|
||||
|
||||
if (cs != null) {
|
||||
// Read all bytes until <?xpacket end= up-front or filter stream
|
||||
stream.mark();
|
||||
long end = scanForSequence(stream, XMP_PACKET_END);
|
||||
stream.reset();
|
||||
|
||||
long length = end - stream.getStreamPosition();
|
||||
Reader reader = new InputStreamReader(IIOUtil.createStreamAdapter(stream, length), cs);
|
||||
|
||||
// Skip until ?>
|
||||
while (reader.read() != '>') {
|
||||
}
|
||||
|
||||
// Return reader?
|
||||
// How to decide between w or r?!
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans for a given ASCII sequence.
|
||||
*
|
||||
* @param pStream the stream to scan
|
||||
* @param pSequence the byte sequence to search for
|
||||
*
|
||||
* @return the start position of the given sequence.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs during scanning
|
||||
*/
|
||||
private static long scanForSequence(final ImageInputStream pStream, final byte[] pSequence) throws IOException {
|
||||
long start = -1l;
|
||||
|
||||
int index = 0;
|
||||
int nullBytes = 0;
|
||||
|
||||
for (int read; (read = pStream.read()) >= 0;) {
|
||||
if (pSequence[index] == (byte) read) {
|
||||
// If this is the first byte in the sequence, store position
|
||||
if (start == -1) {
|
||||
start = pStream.getStreamPosition() - 1;
|
||||
}
|
||||
|
||||
// Inside the sequence, there might be 1 or 3 null bytes, depending on 16/32 byte encoding
|
||||
if (nullBytes == 1 || nullBytes == 3) {
|
||||
pStream.skipBytes(nullBytes);
|
||||
}
|
||||
|
||||
index++;
|
||||
|
||||
// If we found the entire sequence, we're done, return start position
|
||||
if (index == pSequence.length) {
|
||||
return start;
|
||||
}
|
||||
}
|
||||
else if (index == 1 && read == 0 && nullBytes < 3) {
|
||||
// Skip 1 or 3 null bytes for 16/32 bit encoding
|
||||
nullBytes++;
|
||||
}
|
||||
else if (index != 0) {
|
||||
// Start over
|
||||
index = 0;
|
||||
start = -1;
|
||||
nullBytes = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1l;
|
||||
}
|
||||
|
||||
//static public XMPDirectory parse(input);
|
||||
|
||||
public static void main(final String[] pArgs) throws IOException {
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(new File(pArgs[0]));
|
||||
|
||||
Reader xmp;
|
||||
while ((xmp = scanForXMPPacket(stream)) != null) {
|
||||
BufferedReader reader = new BufferedReader(xmp);
|
||||
String line;
|
||||
|
||||
while ((line = reader.readLine()) != null) {
|
||||
System.out.println(line);
|
||||
}
|
||||
}
|
||||
|
||||
stream.close();
|
||||
// else {
|
||||
// System.err.println("XMP not found");
|
||||
// }
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* RationalTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: RationalTestCase.java,v 1.0 Nov 18, 2009 3:23:17 PM haraldk Exp$
|
||||
*/
|
||||
public class RationalTestCase extends TestCase {
|
||||
public void testZeroDenominator() {
|
||||
try {
|
||||
new Rational(1, 0);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Find a solution to this problem, as we should be able to work with it...
|
||||
public void testLongMinValue() {
|
||||
try {
|
||||
new Rational(Long.MIN_VALUE, 1);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
}
|
||||
|
||||
try {
|
||||
new Rational(1, Long.MIN_VALUE);
|
||||
fail("IllegalArgumentException expected");
|
||||
}
|
||||
catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
|
||||
public void testEquals() {
|
||||
assertEquals(new Rational(0, 1), new Rational(0, 999));
|
||||
assertEquals(new Rational(0, 1), new Rational(0, -1));
|
||||
assertEquals(new Rational(1, 2), new Rational(1000000, 2000000));
|
||||
assertEquals(new Rational(1, -2), new Rational(-1, 2));
|
||||
|
||||
Rational x = new Rational(1, -2);
|
||||
Rational y = new Rational(-1000000, 2000000);
|
||||
assertEquals(x, y);
|
||||
assertEquals(x.numerator(), y.numerator());
|
||||
assertEquals(x.denominator(), y.denominator());
|
||||
|
||||
}
|
||||
|
||||
public void testEqualsBoundaries() {
|
||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MAX_VALUE), new Rational(1, 1));
|
||||
|
||||
// NOTE: Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE... :-P
|
||||
assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MIN_VALUE + 1), new Rational(1, 1));
|
||||
assertEquals(new Rational(Long.MIN_VALUE + 1, Long.MAX_VALUE), new Rational(-1, 1));
|
||||
assertEquals(new Rational(Long.MAX_VALUE, Long.MIN_VALUE + 1), new Rational(-1, 1));
|
||||
}
|
||||
|
||||
public void testReciprocal() {
|
||||
assertEquals(new Rational(1, 99), new Rational(99, 1).reciprocal());
|
||||
assertEquals(new Rational(-1, 1234567), new Rational(-1234567, 1).reciprocal());
|
||||
}
|
||||
|
||||
public void testNegate() {
|
||||
assertEquals(new Rational(-1, 99), new Rational(1, 99).negate());
|
||||
assertEquals(new Rational(1, 1234567), new Rational(1, -1234567).negate());
|
||||
}
|
||||
|
||||
public void testPlus() {
|
||||
Rational x, y;
|
||||
|
||||
// 1/2 + 1/3 = 5/6
|
||||
x = new Rational(1, 2);
|
||||
y = new Rational(1, 3);
|
||||
assertEquals(new Rational(5, 6), x.plus(y));
|
||||
|
||||
// 8/9 + 1/9 = 1
|
||||
x = new Rational(8, 9);
|
||||
y = new Rational(1, 9);
|
||||
assertEquals(new Rational(1, 1), x.plus(y));
|
||||
|
||||
// 1/200000000 + 1/300000000 = 1/120000000
|
||||
x = new Rational(1, 200000000);
|
||||
y = new Rational(1, 300000000);
|
||||
assertEquals(new Rational(1, 120000000), x.plus(y));
|
||||
|
||||
// 1073741789/20 + 1073741789/30 = 1073741789/12
|
||||
x = new Rational(1073741789, 20);
|
||||
y = new Rational(1073741789, 30);
|
||||
assertEquals(new Rational(1073741789, 12), x.plus(y));
|
||||
|
||||
// x + 0 = x
|
||||
assertEquals(x, x.plus(Rational.ZERO));
|
||||
}
|
||||
|
||||
public void testTimes() {
|
||||
Rational x, y;
|
||||
|
||||
// 4/17 * 17/4 = 1
|
||||
x = new Rational(4, 17);
|
||||
y = new Rational(17, 4);
|
||||
assertEquals(new Rational(1, 1), x.times(y));
|
||||
|
||||
// 3037141/3247033 * 3037547/3246599 = 841/961
|
||||
x = new Rational(3037141, 3247033);
|
||||
y = new Rational(3037547, 3246599);
|
||||
assertEquals(new Rational(841, 961), x.times(y));
|
||||
|
||||
// x * 0 = 0
|
||||
assertEquals(Rational.ZERO, x.times(Rational.ZERO));
|
||||
}
|
||||
|
||||
public void testMinus() {
|
||||
// 1/6 - -4/-8 = -1/3
|
||||
Rational x = new Rational(1, 6);
|
||||
Rational y = new Rational(-4, -8);
|
||||
assertEquals(new Rational(-1, 3), x.minus(y));
|
||||
|
||||
// x - 0 = x
|
||||
assertEquals(x, x.minus(Rational.ZERO));
|
||||
}
|
||||
|
||||
public void testDivides() {
|
||||
// 3037141/3247033 / 3246599/3037547 = 841/961
|
||||
Rational x = new Rational(3037141, 3247033);
|
||||
Rational y = new Rational(3246599, 3037547);
|
||||
assertEquals(new Rational(841, 961), x.divides(y));
|
||||
|
||||
// 0 / x = 0
|
||||
assertEquals(Rational.ZERO, new Rational(0, 386).divides(x));
|
||||
}
|
||||
|
||||
public void testDivideZero() {
|
||||
try {
|
||||
new Rational(3037141, 3247033).divides(new Rational(0, 1));
|
||||
}
|
||||
catch (ArithmeticException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.UnsupportedCharsetException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* XMPScannerTestCase
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMPScannerTestCase.java,v 1.0 Nov 13, 2009 3:59:43 PM haraldk Exp$
|
||||
*/
|
||||
public class XMPScannerTestCase extends TestCase {
|
||||
|
||||
static final String XMP =
|
||||
"<?xpacket begin=\"\uFEFF\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>" +
|
||||
"<x:xmpmeta xmlns:x=\"adobe:ns:meta/\" x:xmptk=\"Adobe XMP Core 4.1-c036 46.276720, Fri Nov 13 2009 15:59:43 \">\n"+
|
||||
" <rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n"+
|
||||
" <rdf:Description rdf:about=\"\"\n"+
|
||||
" xmlns:photoshop=\"http://ns.adobe.com/photoshop/1.0/\">\n"+
|
||||
" <photoshop:Source>twelvemonkeys.com</photoshop:Source>\n"+
|
||||
" </rdf:Description>\n"+
|
||||
" <rdf:Description rdf:about=\"\"\n"+
|
||||
" xmlns:dc=\"http://purl.org/dc/elements/1.1/\">\n"+
|
||||
" <dc:format>application/vnd.adobe.photoshop</dc:format>\n"+
|
||||
" </rdf:Description>\n"+
|
||||
" </rdf:RDF>\n"+
|
||||
"</x:xmpmeta>" +
|
||||
"<?xpacket end=\"w\"?>";
|
||||
|
||||
final Random mRandom = new Random(4934638567l);
|
||||
|
||||
private InputStream createRandomStream(final int pLength) {
|
||||
byte[] bytes = new byte[pLength];
|
||||
mRandom.nextBytes(bytes);
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
private InputStream createXMPStream(final String pXMP, final String pCharsetName) {
|
||||
try {
|
||||
return new SequenceInputStream(
|
||||
Collections.enumeration(
|
||||
Arrays.asList(
|
||||
createRandomStream(79),
|
||||
new ByteArrayInputStream(pXMP.getBytes(pCharsetName)),
|
||||
createRandomStream(31)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
catch (UnsupportedEncodingException e) {
|
||||
UnsupportedCharsetException uce = new UnsupportedCharsetException(pCharsetName);
|
||||
uce.initCause(e);
|
||||
throw uce;
|
||||
}
|
||||
}
|
||||
|
||||
public void testScanForUTF8() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-8");
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
assertNotNull(reader);
|
||||
}
|
||||
|
||||
public void testScanForUTF8singleQuote() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-8".replace("\"", "'"));
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
assertNotNull(reader);
|
||||
}
|
||||
|
||||
public void testScanForUTF16BE() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-16BE");
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
assertNotNull(reader);
|
||||
}
|
||||
|
||||
public void testScanForUTF16BEsingleQuote() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-16BE".replace("\"", "'"));
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
assertNotNull(reader);
|
||||
}
|
||||
|
||||
public void testScanForUTF16LE() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-16LE");
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
assertNotNull(reader);
|
||||
}
|
||||
|
||||
public void testScanForUTF16LEsingleQuote() throws IOException {
|
||||
InputStream stream = createXMPStream(XMP, "UTF-16LE".replace("\"", "'"));
|
||||
|
||||
Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
|
||||
assertNotNull(reader);
|
||||
}
|
||||
|
||||
// TODO: Default Java installation on OS X don't seem to have UTF-32 installed. Hmmm..
|
||||
// public void testUTF32BE() throws IOException {
|
||||
// InputStream stream = createXMPStream("UTF-32BE");
|
||||
//
|
||||
// Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
//
|
||||
// assertNotNull(reader);
|
||||
// }
|
||||
//
|
||||
// public void testUTF32LE() throws IOException {
|
||||
// InputStream stream = createXMPStream("UTF-32LE");
|
||||
//
|
||||
// Reader reader = XMPScanner.scanForXMPPacket(stream);
|
||||
//
|
||||
// assertNotNull(reader);
|
||||
// }
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-pict</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO PICT plugin</name>
|
||||
<description>ImageIO plugin for Apple Mac Paint Picture (PICT) format.</description>
|
||||
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+10
-3
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.pict;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -45,12 +48,16 @@ import java.util.Locale;
|
||||
public class PICTImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
/**
|
||||
* Creates an PICTImageReaderSpi
|
||||
* Creates a {@code PICTImageReaderSpi}.
|
||||
*/
|
||||
public PICTImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(PICTImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private PICTImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"2.2",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"pct", "PCT", "pict", "PICT"},
|
||||
new String[]{"pct", "pict"},
|
||||
new String[]{"image/pict", "image/x-pict"},
|
||||
|
||||
+10
-3
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.pict;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.ImageWriter;
|
||||
import javax.imageio.spi.ImageWriterSpi;
|
||||
@@ -44,12 +47,16 @@ import java.util.Locale;
|
||||
public class PICTImageWriterSpi extends ImageWriterSpi {
|
||||
|
||||
/**
|
||||
* Creates an PICTImageWriterSpi
|
||||
* Creates a {@code PICTImageWriterSpi}.
|
||||
*/
|
||||
public PICTImageWriterSpi() {
|
||||
this(IIOUtil.getProviderInfo(PICTImageWriterSpi.class));
|
||||
}
|
||||
|
||||
private PICTImageWriterSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"2.0",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"pct", "PCT",
|
||||
"pict", "PICT"},
|
||||
new String[]{"pct", "pict"},
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>TwelveMonkeys ImageIO</name>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<modules>
|
||||
<!-- Support -->
|
||||
<module>core</module>
|
||||
<module>metadata</module>
|
||||
|
||||
<!-- Stand-alone readers/writers -->
|
||||
<module>ico</module>
|
||||
@@ -46,8 +47,8 @@
|
||||
</modules>
|
||||
|
||||
<properties>
|
||||
<core.version>2.2</core.version>
|
||||
<imageio.core.version>2.2</imageio.core.version>
|
||||
<core.version>2.3</core.version>
|
||||
<imageio.core.version>2.3</imageio.core.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -96,6 +97,13 @@
|
||||
<classifier>tests</classifier>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-metadata</artifactId>
|
||||
<version>${imageio.core.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
@@ -108,7 +116,23 @@
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<configuration>
|
||||
<encoding>UTF-8</encoding>
|
||||
<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>https://twelvemonkeys-imageio.dev.java.net/</Implementation-URL>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-psd</artifactId>
|
||||
<version>2.2</version>
|
||||
<name>TwelveMonkeys ImageIO PSD plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for Adobe Photoshop Document (PSD).
|
||||
</description>
|
||||
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
<?xml version="1.0"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-psd</artifactId>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO PSD plugin</name>
|
||||
<description>
|
||||
ImageIO plugin for Adobe Photoshop Document (PSD).
|
||||
</description>
|
||||
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-metadata</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOInvalidTreeException;
|
||||
import javax.imageio.metadata.IIOMetadata;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* AbstractMetadata
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$
|
||||
*/
|
||||
abstract class AbstractMetadata extends IIOMetadata implements Cloneable {
|
||||
// TODO: Move to core...
|
||||
|
||||
protected AbstractMetadata(final boolean pStandardFormatSupported,
|
||||
final String pNativeFormatName, final String pNativeFormatClassName,
|
||||
final String[] pExtraFormatNames, final String[] pExtraFormatClassNames) {
|
||||
super(pStandardFormatSupported, pNativeFormatName, pNativeFormatClassName, pExtraFormatNames, pExtraFormatClassNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation returns {@code true}.
|
||||
* Mutable subclasses should override this method.
|
||||
*
|
||||
* @return {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node getAsTree(final String pFormatName) {
|
||||
validateFormatName(pFormatName);
|
||||
|
||||
if (pFormatName.equals(nativeMetadataFormatName)) {
|
||||
return getNativeTree();
|
||||
}
|
||||
else if (pFormatName.equals(IIOMetadataFormatImpl.standardMetadataFormatName)) {
|
||||
return getStandardTree();
|
||||
}
|
||||
|
||||
// TODO: What about extra formats??
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mergeTree(final String pFormatName, final Node pRoot) throws IIOInvalidTreeException {
|
||||
assertMutable();
|
||||
|
||||
validateFormatName(pFormatName);
|
||||
|
||||
if (!pRoot.getNodeName().equals(nativeMetadataFormatName)) {
|
||||
throw new IIOInvalidTreeException("Root must be " + nativeMetadataFormatName, pRoot);
|
||||
}
|
||||
|
||||
Node node = pRoot.getFirstChild();
|
||||
while (node != null) {
|
||||
// TODO: Merge values from node into this
|
||||
|
||||
// Move to the next sibling
|
||||
node = node.getNextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
assertMutable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that this meta data is mutable.
|
||||
*
|
||||
* @throws IllegalStateException if {@link #isReadOnly()} returns {@code true}.
|
||||
*/
|
||||
protected final void assertMutable() {
|
||||
if (isReadOnly()) {
|
||||
throw new IllegalStateException("Metadata is read-only");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Node getNativeTree();
|
||||
|
||||
protected final void validateFormatName(final String pFormatName) {
|
||||
String[] metadataFormatNames = getMetadataFormatNames();
|
||||
|
||||
if (metadataFormatNames != null) {
|
||||
for (String metadataFormatName : metadataFormatNames) {
|
||||
if (metadataFormatName.equals(pFormatName)) {
|
||||
return; // Found, we're ok!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Bad format name: \"%s\". Expected one of %s", pFormatName, Arrays.toString(metadataFormatNames))
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -103,10 +103,10 @@ Easier to calculate if K' is calculated first, because K' = min(C,M,Y):
|
||||
}
|
||||
|
||||
public float[] toCIEXYZ(float[] colorvalue) {
|
||||
throw new UnsupportedOperationException("Method toCIEXYZ not implemented"); // TODO: Implement
|
||||
return sRGB.toCIEXYZ(toRGB(colorvalue));
|
||||
}
|
||||
|
||||
public float[] fromCIEXYZ(float[] colorvalue) {
|
||||
throw new UnsupportedOperationException("Method fromCIEXYZ not implemented"); // TODO: Implement
|
||||
return sRGB.fromCIEXYZ(fromRGB(colorvalue));
|
||||
}
|
||||
}
|
||||
|
||||
+4
-4
@@ -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
|
||||
/**
|
||||
|
||||
+5
-4
@@ -34,11 +34,11 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* PSDAlhpaChannelInfo
|
||||
* PSDAlphaChannelInfo
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: PSDAlhpaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
|
||||
* @version $Id: PSDAlphaChannelInfo.java,v 1.0 May 2, 2008 5:33:40 PM haraldk Exp$
|
||||
*/
|
||||
class PSDAlphaChannelInfo extends PSDImageResource {
|
||||
List<String> mNames;
|
||||
@@ -48,11 +48,12 @@ 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;
|
||||
}
|
||||
|
||||
+2
-2
@@ -36,8 +36,8 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
* @version $Id: PSDChannelInfo.java,v 1.0 May 6, 2008 2:46:23 PM haraldk Exp$
|
||||
*/
|
||||
class PSDChannelInfo {
|
||||
private short mChannelId;
|
||||
long mLength;
|
||||
final short mChannelId;
|
||||
final long mLength;
|
||||
|
||||
// typedef struct _CLI
|
||||
// {
|
||||
|
||||
+2
-2
@@ -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++) {
|
||||
|
||||
+29
-14
@@ -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);
|
||||
|
||||
+4
-289
@@ -1,16 +1,10 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.MemoryCacheImageInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* EXIF metadata.
|
||||
@@ -24,7 +18,6 @@ import java.util.List;
|
||||
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
||||
*/
|
||||
final class PSDEXIF1Data extends PSDImageResource {
|
||||
// protected byte[] mData;
|
||||
protected Directory mDirectory;
|
||||
|
||||
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
@@ -34,294 +27,16 @@ final class PSDEXIF1Data extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// This is in essence an embedded TIFF file.
|
||||
// TODO: Extract TIFF parsing to more general purpose package
|
||||
// TODO: Instead, read the byte data, store for later parsing
|
||||
MemoryCacheImageInputStream stream = new MemoryCacheImageInputStream(IIOUtil.createStreamAdapter(pInput, mSize));
|
||||
|
||||
byte[] bom = new byte[2];
|
||||
stream.readFully(bom);
|
||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||
stream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
else if (!(bom[0] == 'M' && bom[1] == 'M')) {
|
||||
throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII")));
|
||||
}
|
||||
|
||||
if (stream.readUnsignedShort() != 42) {
|
||||
throw new IIOException("Wrong TIFF magic in EXIF data.");
|
||||
}
|
||||
|
||||
long directoryOffset = stream.readUnsignedInt();
|
||||
mDirectory = Directory.read(stream, directoryOffset);
|
||||
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
||||
mDirectory = new EXIFReader().read(pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
|
||||
builder.append(", ").append(mDirectory);
|
||||
|
||||
builder.append("]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// TIFF Image file directory (IFD)
|
||||
private static class Directory {
|
||||
List<Entry> mEntries = new ArrayList<Entry>();
|
||||
|
||||
private Directory() {}
|
||||
|
||||
public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException {
|
||||
Directory directory = new Directory();
|
||||
|
||||
pInput.seek(pOffset);
|
||||
int entryCount = pInput.readUnsignedShort();
|
||||
for (int i = 0; i < entryCount; i++) {
|
||||
directory.mEntries.add(Entry.read(pInput));
|
||||
}
|
||||
|
||||
long nextOffset = pInput.readUnsignedInt();
|
||||
if (nextOffset != 0) {
|
||||
Directory next = Directory.read(pInput, nextOffset);
|
||||
directory.mEntries.addAll(next.mEntries);
|
||||
}
|
||||
|
||||
return directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Directory%s", mEntries);
|
||||
}
|
||||
}
|
||||
|
||||
// TIFF IFD Entry
|
||||
private static class Entry {
|
||||
private static final int EXIF_IFD = 0x8769;
|
||||
|
||||
private final static String[] TYPE_NAMES = {
|
||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
||||
|
||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
||||
};
|
||||
|
||||
private final static int[] TYPE_LENGTHS = {
|
||||
1, 1, 2, 4, 8,
|
||||
|
||||
1, 1, 2, 4, 8, 4, 8,
|
||||
};
|
||||
|
||||
private int mTag;
|
||||
/*
|
||||
1 = BYTE 8-bit unsigned integer.
|
||||
2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte
|
||||
must be NUL (binary zero).
|
||||
3 = SHORT 16-bit (2-byte) unsigned integer.
|
||||
4 = LONG 32-bit (4-byte) unsigned integer.
|
||||
5 = RATIONAL Two LONGs: the first represents the numerator of a
|
||||
fraction; the second, the denominator.
|
||||
|
||||
TIFF 6.0 and above:
|
||||
6 = SBYTE An 8-bit signed (twos-complement) integer.
|
||||
7 = UNDEFINED An 8-bit byte that may contain anything, depending on
|
||||
the definition of the field.
|
||||
8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer.
|
||||
9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer.
|
||||
10 = SRATIONAL Two SLONGs: the first represents the numerator of a
|
||||
fraction, the second the denominator.
|
||||
11 = FLOAT Single precision (4-byte) IEEE format.
|
||||
12 = DOUBLE Double precision (8-byte) IEEE format.
|
||||
*/
|
||||
private short mType;
|
||||
private int mCount;
|
||||
private long mValueOffset;
|
||||
private Object mValue;
|
||||
|
||||
private Entry() {}
|
||||
|
||||
public static Entry read(final ImageInputStream pInput) throws IOException {
|
||||
Entry entry = new Entry();
|
||||
|
||||
entry.mTag = pInput.readUnsignedShort();
|
||||
entry.mType = pInput.readShort();
|
||||
entry.mCount = pInput.readInt(); // Number of values
|
||||
|
||||
// TODO: Handle other sub-IFDs
|
||||
if (entry.mTag == EXIF_IFD) {
|
||||
long offset = pInput.readUnsignedInt();
|
||||
pInput.mark();
|
||||
try {
|
||||
entry.mValue = Directory.read(pInput, offset);
|
||||
}
|
||||
finally {
|
||||
pInput.reset();
|
||||
}
|
||||
}
|
||||
else {
|
||||
int valueLength = entry.getValueLength();
|
||||
if (valueLength > 0 && valueLength <= 4) {
|
||||
entry.readValueInLine(pInput);
|
||||
pInput.skipBytes(4 - valueLength);
|
||||
}
|
||||
else {
|
||||
entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
||||
entry.readValue(pInput);
|
||||
}
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
private void readValue(final ImageInputStream pInput) throws IOException {
|
||||
long pos = pInput.getStreamPosition();
|
||||
try {
|
||||
pInput.seek(mValueOffset);
|
||||
readValueInLine(pInput);
|
||||
}
|
||||
finally {
|
||||
pInput.seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void readValueInLine(ImageInputStream pInput) throws IOException {
|
||||
mValue = readValueDirect(pInput, mType, mCount);
|
||||
}
|
||||
|
||||
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
||||
switch (pType) {
|
||||
case 2:
|
||||
// TODO: This might be UTF-8 or ISO-8859-1, even though against the spec
|
||||
byte[] ascii = new byte[pCount];
|
||||
pInput.readFully(ascii);
|
||||
return StringUtil.decode(ascii, 0, ascii.length, "ASCII");
|
||||
case 1:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedByte();
|
||||
}
|
||||
case 6:
|
||||
if (pCount == 1) {
|
||||
return pInput.readByte();
|
||||
}
|
||||
case 7:
|
||||
byte[] bytes = new byte[pCount];
|
||||
pInput.readFully(bytes);
|
||||
return bytes;
|
||||
case 3:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedShort();
|
||||
}
|
||||
case 8:
|
||||
if (pCount == 1) {
|
||||
return pInput.readShort();
|
||||
}
|
||||
|
||||
short[] shorts = new short[pCount];
|
||||
pInput.readFully(shorts, 0, shorts.length);
|
||||
return shorts;
|
||||
case 4:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt();
|
||||
}
|
||||
case 9:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt();
|
||||
}
|
||||
|
||||
int[] ints = new int[pCount];
|
||||
pInput.readFully(ints, 0, ints.length);
|
||||
return ints;
|
||||
case 11:
|
||||
if (pCount == 1) {
|
||||
return pInput.readFloat();
|
||||
}
|
||||
|
||||
float[] floats = new float[pCount];
|
||||
pInput.readFully(floats, 0, floats.length);
|
||||
return floats;
|
||||
case 12:
|
||||
if (pCount == 1) {
|
||||
return pInput.readDouble();
|
||||
}
|
||||
|
||||
double[] doubles = new double[pCount];
|
||||
pInput.readFully(doubles, 0, doubles.length);
|
||||
return doubles;
|
||||
|
||||
// TODO: Consider using a Rational class
|
||||
case 5:
|
||||
if (pCount == 1) {
|
||||
return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
||||
}
|
||||
|
||||
double[] rationals = new double[pCount];
|
||||
for (int i = 0; i < rationals.length; i++) {
|
||||
rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
||||
}
|
||||
|
||||
return rationals;
|
||||
case 10:
|
||||
if (pCount == 1) {
|
||||
return pInput.readInt() / (double) pInput.readInt();
|
||||
}
|
||||
|
||||
double[] srationals = new double[pCount];
|
||||
for (int i = 0; i < srationals.length; i++) {
|
||||
srationals[i] = pInput.readInt() / (double) pInput.readInt();
|
||||
}
|
||||
|
||||
return srationals;
|
||||
|
||||
default:
|
||||
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
||||
}
|
||||
}
|
||||
|
||||
private int getValueLength() {
|
||||
if (mType > 0 && mType <= TYPE_LENGTHS.length) {
|
||||
return TYPE_LENGTHS[mType - 1] * mCount;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String getTypeName() {
|
||||
if (mType > 0 && mType <= TYPE_NAMES.length) {
|
||||
return TYPE_NAMES[mType - 1];
|
||||
}
|
||||
return "Unknown type";
|
||||
}
|
||||
|
||||
// TODO: Tag names!
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("0x%04x: %s (%s, %d)", mTag, getValueAsString(), getTypeName(), mCount);
|
||||
}
|
||||
|
||||
public String getValueAsString() {
|
||||
if (mValue instanceof String) {
|
||||
return String.format("\"%s\"", mValue);
|
||||
}
|
||||
|
||||
if (mValue != null && mValue.getClass().isArray()) {
|
||||
Class<?> type = mValue.getClass().getComponentType();
|
||||
if (byte.class == type) {
|
||||
return Arrays.toString((byte[]) mValue);
|
||||
}
|
||||
if (short.class == type) {
|
||||
return Arrays.toString((short[]) mValue);
|
||||
}
|
||||
if (int.class == type) {
|
||||
return Arrays.toString((int[]) mValue);
|
||||
}
|
||||
if (float.class == type) {
|
||||
return Arrays.toString((float[]) mValue);
|
||||
}
|
||||
if (double.class == type) {
|
||||
return Arrays.toString((double[]) mValue);
|
||||
}
|
||||
}
|
||||
|
||||
return String.valueOf(mValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-12
@@ -39,26 +39,28 @@ import java.io.IOException;
|
||||
* @version $Id: PSDGlobalLayerMask.java,v 1.0 May 8, 2008 5:33:48 PM haraldk Exp$
|
||||
*/
|
||||
class PSDGlobalLayerMask {
|
||||
private int mColorSpace;
|
||||
private int mColor1;
|
||||
private int mColor2;
|
||||
private int mColor3;
|
||||
private int mColor4;
|
||||
private int mOpacity;
|
||||
private int mKind;
|
||||
final int mColorSpace;
|
||||
final int mColor1;
|
||||
final int mColor2;
|
||||
final int mColor3;
|
||||
final int mColor4;
|
||||
final int mOpacity;
|
||||
final int mKind;
|
||||
|
||||
PSDGlobalLayerMask(ImageInputStream pInput) throws IOException {
|
||||
mColorSpace = pInput.readUnsignedShort();
|
||||
PSDGlobalLayerMask(final ImageInputStream pInput) throws IOException {
|
||||
mColorSpace = pInput.readUnsignedShort(); // Undocumented
|
||||
|
||||
mColor1 = pInput.readUnsignedShort();
|
||||
mColor2 = pInput.readUnsignedShort();
|
||||
mColor3 = pInput.readUnsignedShort();
|
||||
mColor4 = pInput.readUnsignedShort();
|
||||
|
||||
mOpacity = pInput.readUnsignedShort();
|
||||
mOpacity = pInput.readUnsignedShort(); // 0-100
|
||||
|
||||
mKind = pInput.readUnsignedByte(); // 0: Selected (ie inverted), 1: Color protected, 128: Use value stored per layer
|
||||
|
||||
// TODO: Variable: Filler zeros
|
||||
|
||||
mKind = pInput.readUnsignedByte();
|
||||
|
||||
pInput.readByte(); // Pad
|
||||
}
|
||||
|
||||
|
||||
+58
@@ -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
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -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) + ")");
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
Directory mDirectory;
|
||||
|
||||
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// Read IPTC directory
|
||||
mDirectory = new IPTCReader().read(pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
builder.append(", ").append(mDirectory);
|
||||
builder.append("]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
+321
-115
@@ -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,19 +62,21 @@ 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: Implement layer reading
|
||||
// TODO: Allow reading separate (or some?) layers
|
||||
// TODO: Allow reading the extra alpha channels (index after composite data)
|
||||
// TODO: Support for PSDVersionInfo hasRealMergedData=false (no real composite data, layers will be in index 0)
|
||||
// TODO: Support for API for reading separate layers (index after composite data, and optional alpha channels)
|
||||
// TODO: Consider Romain Guy's Java 2D implementation of PS filters for the blending modes in layers
|
||||
// http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/
|
||||
// See http://www.codeproject.com/KB/graphics/PSDParser.aspx
|
||||
// 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);
|
||||
@@ -77,8 +84,9 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
protected void resetMembers() {
|
||||
mHeader = null;
|
||||
mColorData = null;
|
||||
mImageResources = null;
|
||||
// mColorData = null;
|
||||
// mImageResources = null;
|
||||
mMetadata = null;
|
||||
mColorSpace = null;
|
||||
}
|
||||
|
||||
@@ -116,9 +124,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(
|
||||
@@ -185,6 +193,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)
|
||||
@@ -255,7 +268,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;
|
||||
@@ -322,22 +335,24 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
|
||||
processImageStarted(pIndex);
|
||||
|
||||
int[] offsets = null;
|
||||
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:
|
||||
break;
|
||||
case PSD.COMPRESSION_RLE:
|
||||
// NOTE: Offsets will allow us to easily skip rows before AOI
|
||||
offsets = new int[mHeader.mChannels * mHeader.mHeight];
|
||||
for (int i = 0; i < offsets.length; i++) {
|
||||
offsets[i] = mImageInput.readUnsignedShort();
|
||||
// NOTE: Byte counts will allow us to easily skip rows before AOI
|
||||
byteCounts = new int[mHeader.mChannels * mHeader.mHeight];
|
||||
for (int i = 0; i < byteCounts.length; i++) {
|
||||
byteCounts[i] = mImageInput.readUnsignedShort();
|
||||
}
|
||||
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");
|
||||
@@ -351,7 +366,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
|
||||
// What we read here is the "composite layer" of the PSD file
|
||||
readImageData(image, rawType.getColorModel(), source, dest, xSub, ySub, offsets, compression);
|
||||
readImageData(image, rawType.getColorModel(), source, dest, xSub, ySub, byteCounts, compression);
|
||||
|
||||
if (abortRequested()) {
|
||||
processReadAborted();
|
||||
@@ -366,7 +381,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
private void readImageData(final BufferedImage pImage,
|
||||
final ColorModel pSourceCM, final Rectangle pSource, final Rectangle pDest,
|
||||
final int pXSub, final int pYSub,
|
||||
final int[] pOffsets, final int pCompression) throws IOException {
|
||||
final int[] pByteCounts, final int pCompression) throws IOException {
|
||||
|
||||
final WritableRaster raster = pImage.getRaster();
|
||||
// TODO: Conversion if destination cm is not compatible
|
||||
@@ -388,24 +403,24 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer();
|
||||
byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData();
|
||||
|
||||
read1bitChannel(c, data1, interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
|
||||
read1bitChannel(c, mHeader.mChannels, data1, interleavedBands, bandOffset, pSourceCM, row1, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 8:
|
||||
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer();
|
||||
byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData();
|
||||
|
||||
read8bitChannel(c, data8, interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
|
||||
read8bitChannel(c, mHeader.mChannels, data8, interleavedBands, bandOffset, pSourceCM, row8, pSource, pDest, pXSub, pYSub, mHeader.mWidth, mHeader.mHeight, pByteCounts, c * mHeader.mHeight, pCompression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 16:
|
||||
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer();
|
||||
short[] data16 = banded ? buffer16.getData(c) : buffer16.getData();
|
||||
|
||||
read16bitChannel(c, data16, interleavedBands, bandOffset, pSourceCM, row16, pSource, pDest, pXSub, pYSub, pOffsets, pCompression == PSD.COMPRESSION_RLE);
|
||||
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()) {
|
||||
@@ -419,19 +434,22 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
|
||||
private void read16bitChannel(final int pChannel,
|
||||
private void read16bitChannel(final int pChannel, final int pChannelCount,
|
||||
final short[] pData, final int pBands, final int pBandOffset,
|
||||
final ColorModel pSourceColorModel,
|
||||
final short[] pRow,
|
||||
final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub,
|
||||
final int[] pRowOffsets, final boolean pRLECompressed) throws IOException {
|
||||
final Rectangle pSource, final Rectangle pDest,
|
||||
final int pXSub, final int pYSub,
|
||||
final int pChannelWidth, final int pChannelHeight,
|
||||
final int[] pRowByteCounts, final int pRowOffset,
|
||||
final boolean pRLECompressed) throws IOException {
|
||||
|
||||
final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
|
||||
for (int y = 0; y < mHeader.mHeight; y++) {
|
||||
for (int y = 0; y < pChannelHeight; y++) {
|
||||
// NOTE: Length is in *16 bit values* (shorts)
|
||||
int length = 2 * (pRLECompressed ? pRowOffsets[pChannel * mHeader.mHeight + y] : mHeader.mWidth);
|
||||
int length = 2 * (pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth);
|
||||
|
||||
// TODO: Sometimes need to read the line y == source.y + source.height...
|
||||
// Read entire line, if within source region and sampling
|
||||
@@ -439,7 +457,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
if (pRLECompressed) {
|
||||
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
|
||||
try {
|
||||
for (int x = 0; x < mHeader.mWidth; x++) {
|
||||
for (int x = 0; x < pChannelWidth; x++) {
|
||||
pRow[x] = input.readShort();
|
||||
}
|
||||
}
|
||||
@@ -448,7 +466,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
}
|
||||
else {
|
||||
mImageInput.readFully(pRow, 0, mHeader.mWidth);
|
||||
mImageInput.readFully(pRow, 0, pChannelWidth);
|
||||
}
|
||||
|
||||
// TODO: Destination offset...??
|
||||
@@ -472,22 +490,25 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
processImageProgress((pChannel * y * 100) / mHeader.mChannels * mHeader.mHeight);
|
||||
processImageProgress((pChannel * y * 100) / pChannelCount * pChannelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void read8bitChannel(final int pChannel,
|
||||
private void read8bitChannel(final int pChannel, final int pChannelCount,
|
||||
final byte[] pData, final int pBands, final int pBandOffset,
|
||||
final ColorModel pSourceColorModel,
|
||||
final byte[] pRow,
|
||||
final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub,
|
||||
final int[] pRowOffsets, final boolean pRLECompressed) throws IOException {
|
||||
final Rectangle pSource, final Rectangle pDest,
|
||||
final int pXSub, final int pYSub,
|
||||
final int pChannelWidth, final int pChannelHeight,
|
||||
final int[] pRowByteCounts, final int pRowOffset,
|
||||
final boolean pRLECompressed) throws IOException {
|
||||
|
||||
final boolean isCMYK = pSourceColorModel.getColorSpace().getType() == ColorSpace.TYPE_CMYK;
|
||||
final int colorComponents = pSourceColorModel.getColorSpace().getNumComponents();
|
||||
|
||||
for (int y = 0; y < mHeader.mHeight; y++) {
|
||||
int length = pRLECompressed ? pRowOffsets[pChannel * mHeader.mHeight + y] : mHeader.mWidth;
|
||||
for (int y = 0; y < pChannelHeight; y++) {
|
||||
int length = pRLECompressed ? pRowByteCounts[pRowOffset + y] : pChannelWidth;
|
||||
|
||||
// TODO: Sometimes need to read the line y == source.y + source.height...
|
||||
// Read entire line, if within source region and sampling
|
||||
@@ -495,14 +516,14 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
if (pRLECompressed) {
|
||||
DataInputStream input = PSDUtil.createPackBitsStream(mImageInput, length);
|
||||
try {
|
||||
input.readFully(pRow, 0, mHeader.mWidth);
|
||||
input.readFully(pRow, 0, pChannelWidth);
|
||||
}
|
||||
finally {
|
||||
input.close();
|
||||
}
|
||||
}
|
||||
else {
|
||||
mImageInput.readFully(pRow, 0, mHeader.mWidth);
|
||||
mImageInput.readFully(pRow, 0, pChannelWidth);
|
||||
}
|
||||
|
||||
// TODO: If banded and not sub sampling/cmyk, we could just copy using System.arraycopy
|
||||
@@ -527,22 +548,25 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
processImageProgress((pChannel * y * 100) / mHeader.mChannels * mHeader.mHeight);
|
||||
processImageProgress((pChannel * y * 100) / pChannelCount * pChannelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void read1bitChannel(final int pChannel,
|
||||
@SuppressWarnings({"UnusedDeclaration"})
|
||||
private void read1bitChannel(final int pChannel, final int pChannelCount,
|
||||
final byte[] pData, final int pBands, final int pBandOffset,
|
||||
final ColorModel pSourceColorModel,
|
||||
final byte[] pRow,
|
||||
final Rectangle pSource, final Rectangle pDest, final int pXSub, final int pYSub,
|
||||
final int[] pRowOffsets, boolean pRLECompressed) throws IOException {
|
||||
final Rectangle pSource, final Rectangle pDest,
|
||||
final int pXSub, final int pYSub,
|
||||
final int pChannelWidth, final int pChannelHeight,
|
||||
final int[] pRowByteCounts, boolean pRLECompressed) throws IOException {
|
||||
// NOTE: 1 bit channels only occurs once
|
||||
|
||||
final int destWidth = (pDest.width + 7) / 8;
|
||||
|
||||
for (int y = 0; y < mHeader.mHeight; y++) {
|
||||
int length = pRLECompressed ? pRowOffsets[y] : mHeader.mWidth;
|
||||
for (int y = 0; y < pChannelHeight; y++) {
|
||||
int length = pRLECompressed ? pRowByteCounts[y] : pChannelWidth;
|
||||
|
||||
// TODO: Sometimes need to read the line y == source.y + source.height...
|
||||
// Read entire line, if within source region and sampling
|
||||
@@ -602,7 +626,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
processImageProgress((pChannel * y * 100) / mHeader.mChannels * mHeader.mHeight);
|
||||
processImageProgress((pChannel * y * 100) / pChannelCount * pChannelHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,6 +699,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.
|
||||
|
||||
@@ -687,9 +714,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);
|
||||
@@ -701,6 +729,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();
|
||||
@@ -709,14 +738,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) {
|
||||
@@ -728,6 +757,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();
|
||||
@@ -743,63 +774,32 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
*/
|
||||
// TODO: Figure out what the last part of that sentence means in practice...
|
||||
int layers = mImageInput.readShort();
|
||||
// System.out.println("layers: " + layers);
|
||||
|
||||
PSDLayerInfo[] layerInfo = new PSDLayerInfo[Math.abs(layers)];
|
||||
for (int i = 0; i < layerInfo.length; i++) {
|
||||
layerInfo[i] = new PSDLayerInfo(mImageInput);
|
||||
// System.out.println("layerInfo[" + i + "]: " + layerInfo[i]);
|
||||
PSDLayerInfo[] layerInfos = new PSDLayerInfo[Math.abs(layers)];
|
||||
for (int i = 0; i < layerInfos.length; i++) {
|
||||
layerInfos[i] = new PSDLayerInfo(mImageInput);
|
||||
}
|
||||
mLayerInfo = Arrays.asList(layerInfo);
|
||||
mMetadata.mLayerInfo = Arrays.asList(layerInfos);
|
||||
|
||||
for (PSDLayerInfo info : layerInfo) {
|
||||
for (PSDChannelInfo channelInfo : info.mChannelInfo) {
|
||||
int compression = mImageInput.readUnsignedShort();
|
||||
// 0: None, 1: PackBits RLE, 2: Zip, 3: Zip w/prediction
|
||||
switch (compression) {
|
||||
case PSD.COMPRESSION_NONE:
|
||||
// System.out.println("Compression: None");
|
||||
break;
|
||||
case PSD.COMPRESSION_RLE:
|
||||
// System.out.println("Compression: PackBits RLE");
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
// System.out.println("Compression: ZIP");
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP_PREDICTON:
|
||||
// System.out.println("Compression: ZIP with prediction");
|
||||
break;
|
||||
default:
|
||||
// TODO: Do we care, as we can just skip the data?
|
||||
// We could issue a warning to the warning listener
|
||||
throw new IIOException(String.format(
|
||||
"Unknown PSD compression: %d. Expected 0 (none), 1 (RLE), 2 (ZIP) or 3 (ZIP w/prediction).",
|
||||
compression
|
||||
));
|
||||
}
|
||||
// TODO: Clean-up
|
||||
mImageInput.mark();
|
||||
ImageTypeSpecifier raw = getRawImageTypeInternal(0);
|
||||
ImageTypeSpecifier imageType = getImageTypes(0).next();
|
||||
mImageInput.reset();
|
||||
|
||||
// TODO: If RLE, the the image data starts with the byte counts
|
||||
// for all the scan lines in the channel (LayerBottom*LayerTop), with
|
||||
// each count stored as a two*byte value.
|
||||
// if (compression == 1) {
|
||||
// mImageInput.skipBytes(channelInfo.mLength);
|
||||
// }
|
||||
for (PSDLayerInfo layerInfo : layerInfos) {
|
||||
// TODO: If not explicitly needed, skip layers...
|
||||
BufferedImage layer = readLayerData(layerInfo, raw, imageType);
|
||||
|
||||
// TODO: Read channel image data (same format as composite image channel data)
|
||||
mImageInput.skipBytes(channelInfo.mLength - 2);
|
||||
// if (channelInfo.mLength % 2 != 0) {
|
||||
// mImageInput.readByte();
|
||||
// }
|
||||
}
|
||||
// TODO: Don't show! Store in meta data somehow...
|
||||
// if (layer != null) {
|
||||
// showIt(layer, layerInfo.mLayerName + " " + layerInfo.mBlendMode.toString());
|
||||
// }
|
||||
}
|
||||
|
||||
// TODO: We seem to have some alignment issues here...
|
||||
// I'm always reading two bytes off..
|
||||
|
||||
long read = mImageInput.getStreamPosition() - pos;
|
||||
// System.out.println("layerInfoLength: " + layerInfoLength);
|
||||
// System.out.println("layer info read: " + (read - 4)); // - 4 for the layerInfoLength field itself
|
||||
long diff = layerInfoLength - (read - 4);
|
||||
|
||||
long diff = layerInfoLength - (read - 4); // - 4 for the layerInfoLength field itself
|
||||
// System.out.println("diff: " + diff);
|
||||
mImageInput.skipBytes(diff);
|
||||
|
||||
@@ -808,7 +808,8 @@ 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);
|
||||
}
|
||||
|
||||
read = mImageInput.getStreamPosition() - pos;
|
||||
@@ -818,10 +819,197 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
mImageInput.skipBytes(toSkip);
|
||||
}
|
||||
else {
|
||||
// Skip entire layer and mask section
|
||||
mImageInput.skipBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage readLayerData(final PSDLayerInfo pLayerInfo, final ImageTypeSpecifier pRawType, final ImageTypeSpecifier pImageType) throws IOException {
|
||||
final int width = pLayerInfo.mRight - pLayerInfo.mLeft;
|
||||
final int height = pLayerInfo.mBottom - pLayerInfo.mTop;
|
||||
|
||||
// Even if raw/imageType has no alpha, the layers may still have alpha...
|
||||
ImageTypeSpecifier imageType = getImageTypeForLayer(pImageType, pLayerInfo);
|
||||
|
||||
// Create image (or dummy, if h/w are <= 0)
|
||||
BufferedImage layer = imageType.createBufferedImage(Math.max(1, width), Math.max(1, height));
|
||||
|
||||
// Source/destination area
|
||||
Rectangle area = new Rectangle(width, height);
|
||||
|
||||
final int xsub = 1;
|
||||
final int ysub = 1;
|
||||
|
||||
final WritableRaster raster = layer.getRaster();
|
||||
// TODO: Conversion if destination cm is not compatible
|
||||
final ColorModel destCM = layer.getColorModel();
|
||||
|
||||
// TODO: This raster is 3-5 times longer than needed, depending on number of channels...
|
||||
ColorModel sourceCM = pRawType.getColorModel();
|
||||
final WritableRaster rowRaster = width > 0 ? sourceCM.createCompatibleWritableRaster(width, 1) : null;
|
||||
|
||||
// final int channels = rowRaster.getNumBands();
|
||||
final boolean banded = raster.getDataBuffer().getNumBanks() > 1;
|
||||
final int interleavedBands = banded ? 1 : raster.getNumBands();
|
||||
|
||||
for (PSDChannelInfo channelInfo : pLayerInfo.mChannelInfo) {
|
||||
int compression = mImageInput.readUnsignedShort();
|
||||
|
||||
// Skip layer if we can't read it
|
||||
// channelId == -2 means "user supplied layer mask", whatever that is...
|
||||
if (width <= 0 || height <= 0 || channelInfo.mChannelId == -2 ||
|
||||
(compression != PSD.COMPRESSION_NONE && compression != PSD.COMPRESSION_RLE)) {
|
||||
mImageInput.skipBytes(channelInfo.mLength - 2);
|
||||
}
|
||||
else {
|
||||
// 0 = red, 1 = green, etc
|
||||
// -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
|
||||
// byte counts are written for all channels before the image data.
|
||||
// This is the reason for the current code duplication
|
||||
int[] byteCounts = null;
|
||||
|
||||
// 0: None, 1: PackBits RLE, 2: Zip, 3: Zip w/prediction
|
||||
switch (compression) {
|
||||
case PSD.COMPRESSION_NONE:
|
||||
break;
|
||||
case PSD.COMPRESSION_RLE:
|
||||
// If RLE, the the image data starts with the byte counts
|
||||
// for all the scan lines in the channel (LayerBottom-LayerTop), with
|
||||
// each count stored as a two*byte value.
|
||||
byteCounts = new int[pLayerInfo.mBottom - pLayerInfo.mTop];
|
||||
for (int i = 0; i < byteCounts.length; i++) {
|
||||
byteCounts[i] = mImageInput.readUnsignedShort();
|
||||
}
|
||||
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||
default:
|
||||
// Explicitly skipped above
|
||||
throw new AssertionError(String.format("Unsupported layer data. Compression: %d", compression));
|
||||
}
|
||||
|
||||
int bandOffset = banded ? 0 : interleavedBands - 1 - c;
|
||||
|
||||
switch (mHeader.mBits) {
|
||||
case 1:
|
||||
byte[] row1 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
DataBufferByte buffer1 = (DataBufferByte) raster.getDataBuffer();
|
||||
byte[] data1 = banded ? buffer1.getData(c) : buffer1.getData();
|
||||
|
||||
read1bitChannel(c, imageType.getNumBands(), data1, interleavedBands, bandOffset, sourceCM, row1, area, area, xsub, ysub, width, height, byteCounts, compression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 8:
|
||||
byte[] row8 = ((DataBufferByte) rowRaster.getDataBuffer()).getData();
|
||||
DataBufferByte buffer8 = (DataBufferByte) raster.getDataBuffer();
|
||||
byte[] data8 = banded ? buffer8.getData(c) : buffer8.getData();
|
||||
|
||||
read8bitChannel(c, imageType.getNumBands(), data8, interleavedBands, bandOffset, sourceCM, row8, area, area, xsub, ysub, width, height, byteCounts, 0, compression == PSD.COMPRESSION_RLE);
|
||||
break;
|
||||
case 16:
|
||||
short[] row16 = ((DataBufferUShort) rowRaster.getDataBuffer()).getData();
|
||||
DataBufferUShort buffer16 = (DataBufferUShort) raster.getDataBuffer();
|
||||
short[] data16 = banded ? buffer16.getData(c) : buffer16.getData();
|
||||
|
||||
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(String.format("Unknown PSD bit depth: %s", mHeader.mBits));
|
||||
}
|
||||
|
||||
if (abortRequested()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
private ImageTypeSpecifier getImageTypeForLayer(final ImageTypeSpecifier pOriginal, final PSDLayerInfo pLayerInfo) {
|
||||
// If layer has more channels than composite data, it's normally extra alpha...
|
||||
if (pLayerInfo.mChannelInfo.length > pOriginal.getNumBands()) {
|
||||
// ...but, it could also be just the user mask...
|
||||
boolean userMask = false;
|
||||
for (PSDChannelInfo channelInfo : pLayerInfo.mChannelInfo) {
|
||||
if (channelInfo.mChannelId == -2) {
|
||||
userMask = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int newBandNum = pLayerInfo.mChannelInfo.length - (userMask ? 1 : 0);
|
||||
|
||||
// If there really is more channels, then create new imageTypeSpec
|
||||
if (newBandNum > pOriginal.getNumBands()) {
|
||||
int[] offs = new int[newBandNum];
|
||||
for (int i = 0, offsLength = offs.length; i < offsLength; i++) {
|
||||
offs[i] = offsLength - i;
|
||||
}
|
||||
|
||||
return ImageTypeSpecifier.createInterleaved(pOriginal.getColorModel().getColorSpace(), offs, pOriginal.getSampleModel().getDataType(), true, false);
|
||||
}
|
||||
}
|
||||
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() {
|
||||
@@ -835,14 +1023,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>();
|
||||
@@ -856,13 +1044,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) {
|
||||
@@ -873,17 +1061,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);
|
||||
|
||||
@@ -892,6 +1080,7 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
processThumbnailStarted(pImageIndex, pThumbnailIndex);
|
||||
processThumbnailComplete();
|
||||
|
||||
// TODO: Returning a cached mutable thumbnail is not really safe...
|
||||
return thumbnail.getThumbnail();
|
||||
}
|
||||
|
||||
@@ -938,15 +1127,32 @@ public class PSDImageReader extends ImageReaderBase {
|
||||
File file = new File(pArgs[idx]);
|
||||
ImageInputStream stream = ImageIO.createImageInputStream(file);
|
||||
imageReader.setInput(stream);
|
||||
|
||||
imageReader.readHeader();
|
||||
System.out.println("imageReader.mHeader: " + imageReader.mHeader);
|
||||
// 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.mGlobalLayerMask: " + imageReader.mGlobalLayerMask);
|
||||
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.setIndentation(" ");
|
||||
serializer.serialize(node, true);
|
||||
System.out.println();
|
||||
|
||||
node = metadata.getAsTree(PSDMetadata.NATIVE_METADATA_FORMAT_NAME);
|
||||
// serializer = new XMLSerializer(System.out, System.getProperty("file.encoding"));
|
||||
serializer.serialize(node, true);
|
||||
|
||||
if (imageReader.hasThumbnails(0)) {
|
||||
int thumbnails = imageReader.getNumThumbnails(0);
|
||||
|
||||
+21
-10
@@ -28,9 +28,12 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.spi.ProviderInfo;
|
||||
import com.twelvemonkeys.imageio.util.IIOUtil;
|
||||
|
||||
import javax.imageio.ImageReader;
|
||||
import javax.imageio.spi.ImageReaderSpi;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.ImageReader;
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -44,28 +47,36 @@ import java.util.Locale;
|
||||
public class PSDImageReaderSpi extends ImageReaderSpi {
|
||||
|
||||
/**
|
||||
* Creates an PSDImageReaderSpi
|
||||
* Creates a {@code PSDImageReaderSpi}.
|
||||
*/
|
||||
public PSDImageReaderSpi() {
|
||||
this(IIOUtil.getProviderInfo(PSDImageReaderSpi.class));
|
||||
}
|
||||
|
||||
private PSDImageReaderSpi(final ProviderInfo pProviderInfo) {
|
||||
super(
|
||||
"TwelveMonkeys",
|
||||
"2.0",
|
||||
pProviderInfo.getVendorName(),
|
||||
pProviderInfo.getVersion(),
|
||||
new String[]{"psd", "PSD"},
|
||||
new String[]{"psd"},
|
||||
new String[]{
|
||||
"application/vnd.adobe.photoshop", // This one seems official, used in XMP
|
||||
"application/vnd.adobe.photoshop", // This one seems official, used in XMP
|
||||
"image/x-psd", "application/x-photoshop", "image/x-photoshop"
|
||||
},
|
||||
"com.twelvemkonkeys.imageio.plugins.psd.PSDImageReader",
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
public boolean canDecodeInput(Object pSource) throws IOException {
|
||||
public boolean canDecodeInput(final Object pSource) throws IOException {
|
||||
if (!(pSource instanceof ImageInputStream)) {
|
||||
return false;
|
||||
}
|
||||
@@ -82,11 +93,11 @@ public class PSDImageReaderSpi extends ImageReaderSpi {
|
||||
}
|
||||
}
|
||||
|
||||
public ImageReader createReaderInstance(Object pExtension) throws IOException {
|
||||
public ImageReader createReaderInstance(final Object pExtension) throws IOException {
|
||||
return new PSDImageReader(this);
|
||||
}
|
||||
|
||||
public String getDescription(Locale pLocale) {
|
||||
public String getDescription(final Locale pLocale) {
|
||||
return "Adobe Photoshop Document (PSD) image reader";
|
||||
}
|
||||
}
|
||||
|
||||
+47
-7
@@ -28,6 +28,9 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.stream.SubImageInputStream;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.IIOException;
|
||||
import java.io.IOException;
|
||||
@@ -41,6 +44,9 @@ import java.lang.reflect.Field;
|
||||
* @version $Id: PSDImageResource.java,v 1.0 Apr 29, 2008 5:49:06 PM haraldk Exp$
|
||||
*/
|
||||
class PSDImageResource {
|
||||
// TODO: Refactor image resources to separate package
|
||||
// TODO: Change constructor to store stream offset and length only (+ possibly the name), defer reading
|
||||
|
||||
final short mId;
|
||||
final String mName;
|
||||
final long mSize;
|
||||
@@ -50,10 +56,23 @@ class PSDImageResource {
|
||||
|
||||
mName = PSDUtil.readPascalString(pInput);
|
||||
|
||||
mSize = pInput.readUnsignedInt();
|
||||
readData(pInput);
|
||||
// Skip pad
|
||||
int nameSize = mName.length() + 1;
|
||||
if (nameSize % 2 != 0) {
|
||||
pInput.readByte();
|
||||
}
|
||||
|
||||
// Data is even-padded
|
||||
mSize = pInput.readUnsignedInt();
|
||||
long startPos = pInput.getStreamPosition();
|
||||
|
||||
readData(new SubImageInputStream(pInput, mSize));
|
||||
|
||||
// NOTE: This should never happen, however it's safer to keep it here to
|
||||
if (pInput.getStreamPosition() != startPos + mSize) {
|
||||
pInput.seek(startPos + mSize);
|
||||
}
|
||||
|
||||
// Data is even-padded (word aligned)
|
||||
if (mSize % 2 != 0) {
|
||||
pInput.read();
|
||||
}
|
||||
@@ -84,7 +103,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 +125,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 +172,27 @@ class PSDImageResource {
|
||||
return new PSDDisplayInfo(id, pInput);
|
||||
case PSD.RES_PRINT_FLAGS:
|
||||
return new PSDPrintFlags(id, pInput);
|
||||
case PSD.RES_IPTC_NAA:
|
||||
return new PSDIPTCData(id, pInput);
|
||||
case PSD.RES_GRID_AND_GUIDES_INFO:
|
||||
return new PSDGridAndGuideInfo(id, pInput);
|
||||
case PSD.RES_THUMBNAIL_PS4:
|
||||
case PSD.RES_THUMBNAIL:
|
||||
return new PSDThumbnail(id, pInput);
|
||||
case PSD.RES_ICC_PROFILE:
|
||||
return new ICCProfile(id, pInput);
|
||||
case PSD.RES_UNICODE_ALPHA_NAMES:
|
||||
return new PSDUnicodeAlphaNames(id, pInput);
|
||||
case PSD.RES_VERSION_INFO:
|
||||
return new PSDVersionInfo(id, pInput);
|
||||
case PSD.RES_EXIF_DATA_1:
|
||||
return new PSDEXIF1Data(id, pInput);
|
||||
case PSD.RES_XMP_DATA:
|
||||
return new PSDXMPData(id, pInput);
|
||||
case PSD.RES_PRINT_SCALE:
|
||||
return new PSDPrintScale(id, pInput);
|
||||
case PSD.RES_PIXEL_ASPECT_RATIO:
|
||||
return new PSDPixelAspectRatio(id, pInput);
|
||||
case PSD.RES_PRINT_FLAGS_INFORMATION:
|
||||
return new PSDPrintFlagsInformation(id, pInput);
|
||||
default:
|
||||
|
||||
+31
-17
@@ -45,7 +45,7 @@ class PSDLayerBlendMode {
|
||||
final int mClipping; // 0: base, 1: non-base
|
||||
final int mFlags;
|
||||
|
||||
public PSDLayerBlendMode(ImageInputStream pInput) throws IOException {
|
||||
public PSDLayerBlendMode(final ImageInputStream pInput) throws IOException {
|
||||
int blendModeSig = pInput.readInt();
|
||||
if (blendModeSig != PSD.RESOURCE_TYPE) { // TODO: Is this really just a resource?
|
||||
throw new IIOException("Illegal PSD Blend Mode signature, expected 8BIM: " + PSDUtil.intToStr(blendModeSig));
|
||||
@@ -68,40 +68,54 @@ class PSDLayerBlendMode {
|
||||
builder.append("mode: \"").append(PSDUtil.intToStr(mBlendMode));
|
||||
builder.append("\", opacity: ").append(mOpacity);
|
||||
builder.append(", clipping: ").append(mClipping);
|
||||
switch (mClipping) {
|
||||
case 0:
|
||||
builder.append(" (base)");
|
||||
break;
|
||||
case 1:
|
||||
builder.append(" (non-base)");
|
||||
break;
|
||||
default:
|
||||
builder.append(" (unknown)");
|
||||
break;
|
||||
}
|
||||
builder.append(", flags: ").append(byteToBinary(mFlags));
|
||||
|
||||
// TODO: Maybe the flag bits have oposite order?
|
||||
/*
|
||||
bit 0 = transparency protected; bit 1 = visible; bit 2 = obsolete;
|
||||
bit 3 = 1 for Photoshop 5.0 and later, tells if bit 4 has useful information;
|
||||
bit 4 = pixel data irrelevant to appearance of document
|
||||
*/
|
||||
builder.append(" (");
|
||||
if ((mFlags & 0x01) != 0) {
|
||||
builder.append("Transp. protected ");
|
||||
}
|
||||
else {
|
||||
builder.append("Transp. open");
|
||||
builder.append("Transp. protected, ");
|
||||
}
|
||||
if ((mFlags & 0x02) != 0) {
|
||||
builder.append(", Visible");
|
||||
}
|
||||
else {
|
||||
builder.append(", Hidden");
|
||||
builder.append("Hidden, ");
|
||||
}
|
||||
if ((mFlags & 0x04) != 0) {
|
||||
builder.append(", Obsolete bit");
|
||||
builder.append("Obsolete bit, ");
|
||||
}
|
||||
if ((mFlags & 0x08) != 0) {
|
||||
builder.append(", Photoshop 5 data");
|
||||
builder.append("PS 5.0 data present, "); // "tells if next bit has useful information"...
|
||||
}
|
||||
if ((mFlags & 0x10) != 0) {
|
||||
builder.append(", Pixel data irrelevant");
|
||||
builder.append("Pixel data irrelevant, ");
|
||||
}
|
||||
if ((mFlags & 0x20) != 0) {
|
||||
builder.append(", Unknown bit 5");
|
||||
builder.append("Unknown bit 5, ");
|
||||
}
|
||||
if ((mFlags & 0x40) != 0) {
|
||||
builder.append(", Unknown bit 6");
|
||||
builder.append("Unknown bit 6, ");
|
||||
}
|
||||
if ((mFlags & 0x80) != 0) {
|
||||
builder.append(", Unknown bit 7");
|
||||
builder.append("Unknown bit 7, ");
|
||||
}
|
||||
|
||||
// Stupidity...
|
||||
if (mFlags != 0) {
|
||||
builder.delete(builder.length() - 2, builder.length());
|
||||
}
|
||||
|
||||
builder.append(")");
|
||||
|
||||
builder.append("]");
|
||||
|
||||
+17
-16
@@ -41,16 +41,16 @@ import java.util.Arrays;
|
||||
* @version $Id: PSDLayerInfo.java,v 1.0 Apr 29, 2008 6:01:12 PM haraldk Exp$
|
||||
*/
|
||||
class PSDLayerInfo {
|
||||
private int mTop;
|
||||
private int mLeft;
|
||||
private int mBottom;
|
||||
private int mRight;
|
||||
final int mTop;
|
||||
final int mLeft;
|
||||
final int mBottom;
|
||||
final int mRight;
|
||||
|
||||
PSDChannelInfo[] mChannelInfo;
|
||||
private PSDLayerBlendMode mBlendMode;
|
||||
private PSDLayerMaskData mLayerMaskData;
|
||||
private PSDChannelSourceDestinationRange[] mRanges;
|
||||
private String mLayerName;
|
||||
final PSDChannelInfo[] mChannelInfo;
|
||||
final PSDLayerBlendMode mBlendMode;
|
||||
final PSDLayerMaskData mLayerMaskData;
|
||||
final PSDChannelSourceDestinationRange[] mRanges;
|
||||
final String mLayerName;
|
||||
|
||||
PSDLayerInfo(ImageInputStream pInput) throws IOException {
|
||||
mTop = pInput.readInt();
|
||||
@@ -80,6 +80,9 @@ class PSDLayerInfo {
|
||||
if (layerMaskDataSize != 0) {
|
||||
mLayerMaskData = new PSDLayerMaskData(pInput, layerMaskDataSize);
|
||||
}
|
||||
else {
|
||||
mLayerMaskData = null;
|
||||
}
|
||||
|
||||
int layerBlendingDataSize = pInput.readInt();
|
||||
if (layerBlendingDataSize % 8 != 0) {
|
||||
@@ -95,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...
|
||||
|
||||
+759
@@ -0,0 +1,759 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.exif.TIFF;
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTC;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.util.FilterIterator;
|
||||
import org.w3c.dom.Node;
|
||||
|
||||
import javax.imageio.metadata.IIOMetadataNode;
|
||||
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 AbstractMetadata {
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// Native format support
|
||||
|
||||
@Override
|
||||
protected 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()]);
|
||||
//
|
||||
// FastByteArrayOutputStream data = new FastByteArrayOutputStream(0);
|
||||
// EncoderStream base64 = new EncoderStream(data, new Base64Encoder(), true);
|
||||
//
|
||||
// try {
|
||||
// base64.write(profile.getProfile().getData());
|
||||
// }
|
||||
// catch (IOException ignore) {
|
||||
// }
|
||||
//
|
||||
// byte[] bytes = data.toByteArray();
|
||||
// node.setAttribute("data", StringUtil.decode(bytes, 0, bytes.length, "ASCII"));
|
||||
node.setUserObject(profile.getProfile());
|
||||
}
|
||||
else if (imageResource instanceof PSDAlphaChannelInfo) {
|
||||
PSDAlphaChannelInfo alphaChannelInfo = (PSDAlphaChannelInfo) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("AlphaChannelInfo");
|
||||
|
||||
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...
|
||||
PSDIPTCData iptc = (PSDIPTCData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "IPTC");
|
||||
node.setUserObject(iptc.mDirectory);
|
||||
|
||||
appendEntries(node, "IPTC", iptc.mDirectory);
|
||||
}
|
||||
else if (imageResource instanceof PSDEXIF1Data) {
|
||||
// TODO: Revise/rethink this...
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "EXIF");
|
||||
// TODO: Set byte[] data instead
|
||||
node.setUserObject(exif.mDirectory);
|
||||
|
||||
appendEntries(node, "EXIF", exif.mDirectory);
|
||||
}
|
||||
else if (imageResource instanceof PSDXMPData) {
|
||||
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
||||
// Or maybe use the Directory approach used by IPTC and EXIF..
|
||||
PSDXMPData xmp = (PSDXMPData) imageResource;
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "XMP");
|
||||
appendEntries(node, "XMP", xmp.mDirectory);
|
||||
|
||||
// Set the entire XMP document as user data
|
||||
node.setUserObject(xmp.mData);
|
||||
}
|
||||
else {
|
||||
// Generic resource..
|
||||
node = new IIOMetadataNode("ImageResource");
|
||||
String value = PSDImageResource.resourceTypeForId(imageResource.mId);
|
||||
if (!"UnknownResource".equals(value)) {
|
||||
node.setAttribute("name", value);
|
||||
}
|
||||
node.setAttribute("length", String.valueOf(imageResource.mSize));
|
||||
// TODO: Set user object: byte array
|
||||
}
|
||||
|
||||
// TODO: More resources
|
||||
|
||||
node.setAttribute("resourceId", String.format("0x%04x", imageResource.mId));
|
||||
resource.appendChild(node);
|
||||
}
|
||||
|
||||
// TODO: Layers and layer info
|
||||
|
||||
// TODO: Global mask etc..
|
||||
|
||||
return resource;
|
||||
}
|
||||
|
||||
private void appendEntries(final IIOMetadataNode pNode, final String pType, final Directory pDirectory) {
|
||||
for (Entry entry : pDirectory) {
|
||||
Object tagId = entry.getIdentifier();
|
||||
|
||||
IIOMetadataNode tag = new IIOMetadataNode("Entry");
|
||||
tag.setAttribute("tag", String.format("%s", tagId));
|
||||
|
||||
String field = entry.getFieldName();
|
||||
if (field != null) {
|
||||
tag.setAttribute("field", String.format("%s", field));
|
||||
}
|
||||
else {
|
||||
if ("IPTC".equals(pType)) {
|
||||
tag.setAttribute("field", String.format("%s:%s", (Integer) tagId >> 8, (Integer) tagId & 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
appendEntries(tag, pType, (Directory) entry.getValue());
|
||||
tag.setAttribute("type", "Directory");
|
||||
}
|
||||
else {
|
||||
tag.setAttribute("value", entry.getValueAsString());
|
||||
tag.setAttribute("type", entry.getTypeName());
|
||||
}
|
||||
|
||||
pNode.appendChild(tag);
|
||||
}
|
||||
}
|
||||
|
||||
/// Standard format support
|
||||
|
||||
@Override
|
||||
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 String.format("%xCLR", pChannels);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Standard meta data format does not support more than 15 channels");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardCompressionNode() {
|
||||
IIOMetadataNode compressionNode = new IIOMetadataNode("Compression");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("CompressionTypeName");
|
||||
String compression;
|
||||
|
||||
switch (mCompression) {
|
||||
case PSD.COMPRESSION_NONE:
|
||||
compression = "none";
|
||||
break;
|
||||
case PSD.COMPRESSION_RLE:
|
||||
compression = "PackBits";
|
||||
break;
|
||||
case PSD.COMPRESSION_ZIP:
|
||||
case PSD.COMPRESSION_ZIP_PREDICTION:
|
||||
compression = "Deflate"; // TODO: ZLib? (TIFF native metadata format specifies both.. :-P)
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("Unreachable");
|
||||
}
|
||||
|
||||
node.setAttribute("value", compression);
|
||||
compressionNode.appendChild(node);
|
||||
|
||||
// TODO: Does it make sense to specify lossless for compression "none"?
|
||||
node = new IIOMetadataNode("Lossless");
|
||||
node.setAttribute("value", "true");
|
||||
compressionNode.appendChild(node);
|
||||
|
||||
return compressionNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDataNode() {
|
||||
IIOMetadataNode dataNode = new IIOMetadataNode("Data");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("PlanarConfiguration");
|
||||
node.setAttribute("value", "PlaneInterleaved"); // TODO: Check with spec
|
||||
dataNode.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("SampleFormat");
|
||||
node.setAttribute("value", mHeader.mMode == PSD.COLOR_MODE_INDEXED ? "Index" : "UnsignedIntegral");
|
||||
dataNode.appendChild(node);
|
||||
|
||||
String bitDepth = Integer.toString(mHeader.mBits); // bits per plane
|
||||
|
||||
// TODO: Channels might be 5 for RGB + A + Mask...
|
||||
String[] bps = new String[mHeader.mChannels];
|
||||
Arrays.fill(bps, bitDepth);
|
||||
|
||||
node = new IIOMetadataNode("BitsPerSample");
|
||||
node.setAttribute("value", StringUtil.toCSVString(bps, " "));
|
||||
dataNode.appendChild(node);
|
||||
|
||||
// TODO: SampleMSB? Or is network (aka Motorola/big endian) byte order assumed?
|
||||
|
||||
return dataNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardDimensionNode() {
|
||||
IIOMetadataNode dimensionNode = new IIOMetadataNode("Dimension");
|
||||
IIOMetadataNode node; // scratch node
|
||||
|
||||
node = new IIOMetadataNode("PixelAspectRatio");
|
||||
|
||||
// TODO: This is not correct wrt resolution info
|
||||
float aspect = 1f;
|
||||
|
||||
Iterator<PSDPixelAspectRatio> ratios = getResources(PSDPixelAspectRatio.class);
|
||||
if (ratios.hasNext()) {
|
||||
PSDPixelAspectRatio ratio = ratios.next();
|
||||
aspect = (float) ratio.mAspect;
|
||||
}
|
||||
|
||||
node.setAttribute("value", Float.toString(aspect));
|
||||
dimensionNode.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("ImageOrientation");
|
||||
node.setAttribute("value", "Normal");
|
||||
dimensionNode.appendChild(node);
|
||||
|
||||
// TODO: If no PSDResolutionInfo, this might still be available in the EXIF data...
|
||||
Iterator<PSDResolutionInfo> resolutionInfos = getResources(PSDResolutionInfo.class);
|
||||
if (!resolutionInfos.hasNext()) {
|
||||
PSDResolutionInfo resolutionInfo = resolutionInfos.next();
|
||||
|
||||
node = new IIOMetadataNode("HorizontalPixelSize");
|
||||
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mHResUnit, resolutionInfo.mHRes)));
|
||||
dimensionNode.appendChild(node);
|
||||
|
||||
node = new IIOMetadataNode("VerticalPixelSize");
|
||||
node.setAttribute("value", Float.toString(asMM(resolutionInfo.mVResUnit, resolutionInfo.mVRes)));
|
||||
dimensionNode.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 dimensionNode;
|
||||
}
|
||||
|
||||
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
|
||||
Entry dateTime = data.mDirectory.getEntryById(TIFF.TAG_DATE_TIME);
|
||||
if (dateTime != null) {
|
||||
node = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime
|
||||
// Format: "YYYY:MM:DD hh:mm:ss"
|
||||
String value = dateTime.getValueAsString();
|
||||
|
||||
node.setAttribute("year", value.substring(0, 4));
|
||||
node.setAttribute("month", value.substring(5, 7));
|
||||
node.setAttribute("day", value.substring(8, 10));
|
||||
node.setAttribute("hour", value.substring(11, 13));
|
||||
node.setAttribute("minute", value.substring(14, 16));
|
||||
node.setAttribute("second", value.substring(17, 19));
|
||||
|
||||
document_node.appendChild(node);
|
||||
}
|
||||
}
|
||||
|
||||
return document_node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected IIOMetadataNode getStandardTextNode() {
|
||||
// TODO: TIFF uses
|
||||
// DocumentName, ImageDescription, Make, Model, PageName, Software, Artist, HostComputer, InkNames, Copyright:
|
||||
// /Text/TextEntry@keyword = field name, /Text/TextEntry@value = field value.
|
||||
// Example: TIFF Software field => /Text/TextEntry@keyword = "Software",
|
||||
// /Text/TextEntry@value = Name and version number of the software package(s) used to create the image.
|
||||
|
||||
Iterator<PSDImageResource> textResources = getResources(PSD.RES_IPTC_NAA, PSD.RES_EXIF_DATA_1, PSD.RES_XMP_DATA);
|
||||
|
||||
if (!textResources.hasNext()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IIOMetadataNode text = new IIOMetadataNode("Text");
|
||||
IIOMetadataNode node;
|
||||
|
||||
// TODO: Alpha channel names? (PSDAlphaChannelInfo/PSDUnicodeAlphaNames)
|
||||
// TODO: Reader/writer (PSDVersionInfo)
|
||||
|
||||
while (textResources.hasNext()) {
|
||||
PSDImageResource textResource = textResources.next();
|
||||
|
||||
if (textResource instanceof PSDIPTCData) {
|
||||
PSDIPTCData iptc = (PSDIPTCData) textResource;
|
||||
|
||||
appendTextEntriesFlat(text, iptc.mDirectory, new FilterIterator.Filter<Entry>() {
|
||||
public boolean accept(final Entry pEntry) {
|
||||
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||
|
||||
switch (tagId) {
|
||||
case IPTC.TAG_SOURCE:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (textResource instanceof PSDEXIF1Data) {
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) textResource;
|
||||
|
||||
appendTextEntriesFlat(text, exif.mDirectory, new FilterIterator.Filter<Entry>() {
|
||||
public boolean accept(final Entry pEntry) {
|
||||
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||
|
||||
switch (tagId) {
|
||||
case TIFF.TAG_SOFTWARE:
|
||||
case TIFF.TAG_ARTIST:
|
||||
case TIFF.TAG_COPYRIGHT:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (textResource instanceof PSDXMPData) {
|
||||
// TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF?
|
||||
// TODO: Use XMP IPTC/EXIF/TIFFF NativeDigest field to validate if the values are in sync...
|
||||
PSDXMPData xmp = (PSDXMPData) textResource;
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
private void appendTextEntriesFlat(final IIOMetadataNode pNode, final Directory pDirectory, final FilterIterator.Filter<Entry> pFilter) {
|
||||
FilterIterator<Entry> pEntries = new FilterIterator<Entry>(pDirectory.iterator(), pFilter);
|
||||
while (pEntries.hasNext()) {
|
||||
Entry entry = pEntries.next();
|
||||
|
||||
if (entry.getValue() instanceof Directory) {
|
||||
appendTextEntriesFlat(pNode, (Directory) entry.getValue(), pFilter);
|
||||
}
|
||||
else if (entry.getValue() instanceof String) {
|
||||
IIOMetadataNode tag = new IIOMetadataNode("TextEntry");
|
||||
String fieldName = entry.getFieldName();
|
||||
|
||||
if (fieldName != null) {
|
||||
tag.setAttribute("keyword", String.format("%s", fieldName));
|
||||
}
|
||||
else {
|
||||
// TODO: This should never happen, as we filter out only specific nodes
|
||||
tag.setAttribute("keyword", String.format("%s", entry.getIdentifier()));
|
||||
}
|
||||
|
||||
tag.setAttribute("value", entry.getValueAsString());
|
||||
pNode.appendChild(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
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() ? "nonpremultiplied" : "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 int... pResourceTypes) {
|
||||
Iterator<PSDImageResource> iterator = mImageResources.iterator();
|
||||
|
||||
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||
public boolean accept(final PSDImageResource pResource) {
|
||||
for (int type : pResourceTypes) {
|
||||
if (type == pResource.mId) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
// TODO: Make it a deep clone
|
||||
try {
|
||||
return super.clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
import javax.imageio.ImageTypeSpecifier;
|
||||
import javax.imageio.metadata.IIOMetadataFormatImpl;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 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"));
|
||||
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", Directory.class, true, null);
|
||||
// TODO: Incorporate EXIF / TIFF metadata here somehow... (or treat as opaque bytes?)
|
||||
|
||||
// root -> ImageResources -> GridAndGuideInfo
|
||||
addElement("GridAndGuideInfo", "ImageResources", 0, Integer.MAX_VALUE);
|
||||
addAttribute("GridAndGuideInfo", "version", DATATYPE_INTEGER, false, "1");
|
||||
addAttribute("GridAndGuideInfo", "verticalGridCycle", DATATYPE_INTEGER, false, "576");
|
||||
addAttribute("GridAndGuideInfo", "horizontalGridCycle", DATATYPE_INTEGER, false, "576");
|
||||
addElement("Guide", "GridAndGuideInfo", CHILD_POLICY_EMPTY);
|
||||
addAttribute("Guide", "location", DATATYPE_INTEGER, true, null, "0", Integer.toString(Integer.MAX_VALUE), true, true);
|
||||
addAttribute("Guide", "orientation", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.GUIDE_ORIENTATIONS));
|
||||
|
||||
// root -> ImageResources -> ICCProfile
|
||||
addElement("ICCProfile", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addAttribute("ICCProfile", "colorSpaceType", DATATYPE_STRING, true, null, Arrays.asList(PSDMetadata.JAVA_CS));
|
||||
|
||||
// root -> ImageResources -> IPTC
|
||||
addElement("IPTC", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addObjectValue("IPTC", Directory.class, true, null);
|
||||
// TODO: Incorporate IPTC metadata here somehow... (or treat as opaque bytes?)
|
||||
|
||||
// root -> ImageResources -> PixelAspectRatio
|
||||
addElement("PixelAspectRatio", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addAttribute("PixelAspectRatio", "version", DATATYPE_STRING, false, "1");
|
||||
addAttribute("PixelAspectRatio", "aspectRatio", DATATYPE_DOUBLE, true, null, "0", Double.toString(Double.POSITIVE_INFINITY), true, false);
|
||||
|
||||
// root -> ImageResources -> PrintFlags
|
||||
addElement("PrintFlags", "ImageResources", CHILD_POLICY_EMPTY);
|
||||
addBooleanAttribute("PrintFlags", "labels", false, false);
|
||||
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;
|
||||
}
|
||||
}
|
||||
+27
@@ -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();
|
||||
}
|
||||
}
|
||||
+16
-16
@@ -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);
|
||||
}
|
||||
|
||||
+7
-7
@@ -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();
|
||||
|
||||
|
||||
+35
@@ -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();
|
||||
}
|
||||
}
|
||||
-555
@@ -1,555 +0,0 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.*;
|
||||
import java.net.*;
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Class PSDReader - Decodes a PhotoShop (.psd) file into one or more frames.
|
||||
* Supports uncompressed or RLE-compressed RGB files only. Each layer may be
|
||||
* retrieved as a full frame BufferedImage, or as a smaller image with an
|
||||
* offset if the layer does not occupy the full frame size. Transparency
|
||||
* in the original psd file is preserved in the returned BufferedImage's.
|
||||
* Does not support additional features in PS versions higher than 3.0.
|
||||
* Example:
|
||||
* <br><pre>
|
||||
* PSDReader r = new PSDReader();
|
||||
* r.readData("sample.psd");
|
||||
* int n = r.getFrameCount();
|
||||
* for (int i = 0; i < n; i++) {
|
||||
* BufferedImage image = r.getLayer(i);
|
||||
* Point offset = r.getLayerOffset(i);
|
||||
* // do something with image
|
||||
* }
|
||||
* </pre>
|
||||
* No copyright asserted on the source code of this class. May be used for
|
||||
* any purpose. Please forward any corrections to kweiner@fmsware.com.
|
||||
*
|
||||
* @author Kevin Weiner, FM Software.
|
||||
* @version 1.1 January 2004 [bug fix; add RLE support]
|
||||
*
|
||||
*/
|
||||
// SEE: http://www.fileformat.info/format/psd/egff.htm#ADOBEPHO-DMYID.4
|
||||
class PSDReader {
|
||||
|
||||
/**
|
||||
* File readData status: No errors.
|
||||
*/
|
||||
public static final int STATUS_OK = 0;
|
||||
|
||||
/**
|
||||
* File readData status: Error decoding file (may be partially decoded)
|
||||
*/
|
||||
public static final int STATUS_FORMAT_ERROR = 1;
|
||||
|
||||
/**
|
||||
* File readData status: Unable to open source.
|
||||
*/
|
||||
public static final int STATUS_OPEN_ERROR = 2;
|
||||
|
||||
/**
|
||||
* File readData status: Unsupported format
|
||||
*/
|
||||
public static final int STATUS_UNSUPPORTED = 3;
|
||||
|
||||
public static int ImageType = BufferedImage.TYPE_INT_ARGB;
|
||||
|
||||
protected BufferedInputStream input;
|
||||
protected int frameCount;
|
||||
protected BufferedImage[] frames;
|
||||
protected int status = 0;
|
||||
protected int nChan;
|
||||
protected int width;
|
||||
protected int height;
|
||||
protected int nLayers;
|
||||
protected int miscLen;
|
||||
protected boolean hasLayers;
|
||||
protected LayerInfo[] layers;
|
||||
protected short[] lineLengths;
|
||||
protected int lineIndex;
|
||||
protected boolean rleEncoded;
|
||||
|
||||
protected class LayerInfo {
|
||||
int x, y, w, h;
|
||||
int nChan;
|
||||
int[] chanID;
|
||||
int alpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of layers readData from file.
|
||||
* @return frame count
|
||||
*/
|
||||
public int getFrameCount() {
|
||||
return frameCount;
|
||||
}
|
||||
|
||||
protected void setInput(InputStream stream) {
|
||||
// open input stream
|
||||
init();
|
||||
if (stream == null) {
|
||||
status = STATUS_OPEN_ERROR;
|
||||
} else {
|
||||
if (stream instanceof BufferedInputStream)
|
||||
input = (BufferedInputStream) stream;
|
||||
else
|
||||
input = new BufferedInputStream(stream);
|
||||
}
|
||||
}
|
||||
|
||||
protected void setInput(String name) {
|
||||
// open input file
|
||||
init();
|
||||
try {
|
||||
name = name.trim();
|
||||
if (name.startsWith("file:")) {
|
||||
name = name.substring(5);
|
||||
while (name.startsWith("/"))
|
||||
name = name.substring(1);
|
||||
}
|
||||
if (name.indexOf("://") > 0) {
|
||||
URL url = new URL(name);
|
||||
input = new BufferedInputStream(url.openStream());
|
||||
} else {
|
||||
input = new BufferedInputStream(new FileInputStream(name));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
status = STATUS_OPEN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets display duration for specified frame. Always returns 0.
|
||||
*
|
||||
*/
|
||||
public int getDelay(int forFrame) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image contents of frame n. Note that this expands the image
|
||||
* to the full frame size (if the layer was smaller) and any subsequent
|
||||
* use of getLayer() will return the full image.
|
||||
*
|
||||
* @return BufferedImage representation of frame, or null if n is invalid.
|
||||
*/
|
||||
public BufferedImage getFrame(int n) {
|
||||
BufferedImage im = null;
|
||||
if ((n >= 0) && (n < nLayers)) {
|
||||
im = frames[n];
|
||||
LayerInfo info = layers[n];
|
||||
if ((info.w != width) || (info.h != height)) {
|
||||
BufferedImage temp =
|
||||
new BufferedImage(width, height, ImageType);
|
||||
Graphics2D gc = temp.createGraphics();
|
||||
gc.drawImage(im, info.x, info.y, null);
|
||||
gc.dispose();
|
||||
im = temp;
|
||||
frames[n] = im;
|
||||
}
|
||||
}
|
||||
return im;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets maximum image size. Individual layers may be smaller.
|
||||
*
|
||||
* @return maximum image dimensions
|
||||
*/
|
||||
public Dimension getFrameSize() {
|
||||
return new Dimension(width, height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first (or only) image readData.
|
||||
*
|
||||
* @return BufferedImage containing first frame, or null if none.
|
||||
*/
|
||||
public BufferedImage getImage() {
|
||||
return getFrame(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the image contents of layer n. May be smaller than full frame
|
||||
* size - use getFrameOffset() to obtain position of subimage within
|
||||
* main image area.
|
||||
*
|
||||
* @return BufferedImage representation of layer, or null if n is invalid.
|
||||
*/
|
||||
public BufferedImage getLayer(int n) {
|
||||
BufferedImage im = null;
|
||||
if ((n >= 0) && (n < nLayers)) {
|
||||
im = frames[n];
|
||||
}
|
||||
return im;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the subimage offset of layer n if it is smaller than the
|
||||
* full frame size.
|
||||
*
|
||||
* @return Point indicating offset from upper left corner of frame.
|
||||
*/
|
||||
public Point getLayerOffset(int n) {
|
||||
Point p = null;
|
||||
if ((n >= 0) && (n < nLayers)) {
|
||||
int x = layers[n].x;
|
||||
int y = layers[n].y;
|
||||
p = new Point(x, y);
|
||||
}
|
||||
if (p == null) {
|
||||
p = new Point(0, 0);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads PhotoShop layers from stream.
|
||||
*
|
||||
* @param InputStream in PhotoShop format.
|
||||
* @return readData status code (0 = no errors)
|
||||
*/
|
||||
public int read(InputStream stream) {
|
||||
setInput(stream);
|
||||
process();
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads PhotoShop file from specified source (file or URL string)
|
||||
*
|
||||
* @param name String containing source
|
||||
* @return readData status code (0 = no errors)
|
||||
*/
|
||||
public int read(String name) {
|
||||
setInput(name);
|
||||
process();
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes input stream and discards contents of all frames.
|
||||
*
|
||||
*/
|
||||
public void reset() {
|
||||
init();
|
||||
}
|
||||
|
||||
protected void close() {
|
||||
if (input != null) {
|
||||
try {
|
||||
input.close();
|
||||
} catch (Exception e) {}
|
||||
input = null;
|
||||
}
|
||||
}
|
||||
protected boolean err() {
|
||||
return status != STATUS_OK;
|
||||
}
|
||||
|
||||
protected byte[] fillBytes(int size, int value) {
|
||||
// create byte array filled with given value
|
||||
byte[] b = new byte[size];
|
||||
if (value != 0) {
|
||||
byte v = (byte) value;
|
||||
for (int i = 0; i < size; i++) {
|
||||
b[i] = v;
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
close();
|
||||
frameCount = 0;
|
||||
frames = null;
|
||||
layers = null;
|
||||
hasLayers = true;
|
||||
status = STATUS_OK;
|
||||
}
|
||||
|
||||
protected void makeDummyLayer() {
|
||||
// creat dummy layer for non-layered image
|
||||
rleEncoded = readShort() == 1;
|
||||
hasLayers = false;
|
||||
nLayers = 1;
|
||||
layers = new LayerInfo[1];
|
||||
LayerInfo layer = new LayerInfo();
|
||||
layers[0] = layer;
|
||||
layer.h = height;
|
||||
layer.w = width;
|
||||
int nc = Math.min(nChan, 4);
|
||||
if (rleEncoded) {
|
||||
// get list of rle encoded line lengths for all channels
|
||||
readLineLengths(height * nc);
|
||||
}
|
||||
layer.nChan = nc;
|
||||
layer.chanID = new int[nc];
|
||||
for (int i = 0; i < nc; i++) {
|
||||
int id = i;
|
||||
if (i == 3) id = -1;
|
||||
layer.chanID[i] = id;
|
||||
}
|
||||
}
|
||||
|
||||
protected void readLineLengths(int nLines) {
|
||||
// readData list of rle encoded line lengths
|
||||
lineLengths = new short[nLines];
|
||||
for (int i = 0; i < nLines; i++) {
|
||||
lineLengths[i] = readShort();
|
||||
}
|
||||
lineIndex = 0;
|
||||
}
|
||||
|
||||
protected BufferedImage makeImage(int w, int h, byte[] r, byte[] g, byte[] b, byte[] a) {
|
||||
// create image from given plane data
|
||||
BufferedImage im = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
int[] data = ((DataBufferInt) im.getRaster().getDataBuffer()).getData();
|
||||
int n = w * h;
|
||||
int j = 0;
|
||||
while (j < n) {
|
||||
try {
|
||||
int ac = a[j] & 0xff;
|
||||
int rc = r[j] & 0xff;
|
||||
int gc = g[j] & 0xff;
|
||||
int bc = b[j] & 0xff;
|
||||
data[j] = (((((ac << 8) | rc) << 8) | gc) << 8) | bc;
|
||||
} catch (Exception e) {}
|
||||
j++;
|
||||
}
|
||||
return im;
|
||||
}
|
||||
|
||||
protected void process() {
|
||||
// decode PSD file
|
||||
if (err()) return;
|
||||
readHeader();
|
||||
if (err()) return;
|
||||
readLayerInfo();
|
||||
if (err()) return;
|
||||
if (nLayers == 0) {
|
||||
makeDummyLayer();
|
||||
if (err()) return;
|
||||
}
|
||||
readLayers();
|
||||
}
|
||||
|
||||
protected int readByte() {
|
||||
// readData single byte from input
|
||||
int curByte = 0;
|
||||
try {
|
||||
curByte = input.read();
|
||||
} catch (IOException e) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
}
|
||||
return curByte;
|
||||
}
|
||||
|
||||
protected int readBytes(byte[] bytes, int n) {
|
||||
// readData multiple bytes from input
|
||||
if (bytes == null) return 0;
|
||||
int r = 0;
|
||||
try {
|
||||
r = input.read(bytes, 0, n);
|
||||
} catch (IOException e) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
}
|
||||
if (r < n) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
protected void readHeader() {
|
||||
// readData PSD header info
|
||||
String sig = readString(4);
|
||||
int ver = readShort();
|
||||
skipBytes(6);
|
||||
nChan = readShort();
|
||||
height = readInt();
|
||||
width = readInt();
|
||||
int depth = readShort();
|
||||
int mode = readShort();
|
||||
int cmLen = readInt();
|
||||
skipBytes(cmLen);
|
||||
int imResLen = readInt();
|
||||
skipBytes(imResLen);
|
||||
|
||||
// require 8-bit RGB data
|
||||
if ((!sig.equals("8BPS")) || (ver != 1)) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
} else if ((depth != 8) || (mode != 3)) {
|
||||
status = STATUS_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
protected int readInt() {
|
||||
// readData big-endian 32-bit integer
|
||||
return (((((readByte() << 8) | readByte()) << 8) | readByte()) << 8)
|
||||
| readByte();
|
||||
}
|
||||
|
||||
protected void readLayerInfo() {
|
||||
// readData layer header info
|
||||
miscLen = readInt();
|
||||
if (miscLen == 0) {
|
||||
return; // no layers, only base image
|
||||
}
|
||||
int layerInfoLen = readInt();
|
||||
nLayers = readShort();
|
||||
if (nLayers > 0) {
|
||||
layers = new LayerInfo[nLayers];
|
||||
}
|
||||
for (int i = 0; i < nLayers; i++) {
|
||||
LayerInfo info = new LayerInfo();
|
||||
layers[i] = info;
|
||||
info.y = readInt();
|
||||
info.x = readInt();
|
||||
info.h = readInt() - info.y;
|
||||
info.w = readInt() - info.x;
|
||||
info.nChan = readShort();
|
||||
info.chanID = new int[info.nChan];
|
||||
for (int j = 0; j < info.nChan; j++) {
|
||||
int id = readShort();
|
||||
int size = readInt();
|
||||
info.chanID[j] = id;
|
||||
}
|
||||
String s = readString(4);
|
||||
if (!s.equals("8BIM")) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
return;
|
||||
}
|
||||
skipBytes(4); // blend mode
|
||||
info.alpha = readByte();
|
||||
int clipping = readByte();
|
||||
int flags = readByte();
|
||||
readByte(); // filler
|
||||
int extraSize = readInt();
|
||||
skipBytes(extraSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected void readLayers() {
|
||||
// readData and convert each layer to BufferedImage
|
||||
frameCount = nLayers;
|
||||
frames = new BufferedImage[nLayers];
|
||||
for (int i = 0; i < nLayers; i++) {
|
||||
LayerInfo info = layers[i];
|
||||
byte[] r = null, g = null, b = null, a = null;
|
||||
for (int j = 0; j < info.nChan; j++) {
|
||||
int id = info.chanID[j];
|
||||
switch (id) {
|
||||
case 0 : r = readPlane(info.w, info.h); break;
|
||||
case 1 : g = readPlane(info.w, info.h); break;
|
||||
case 2 : b = readPlane(info.w, info.h); break;
|
||||
case -1 : a = readPlane(info.w, info.h); break;
|
||||
default : readPlane(info.w, info.h);
|
||||
}
|
||||
if (err()) break;
|
||||
}
|
||||
if (err()) break;
|
||||
int n = info.w * info.h;
|
||||
if (r == null) r = fillBytes(n, 0);
|
||||
if (g == null) g = fillBytes(n, 0);
|
||||
if (b == null) b = fillBytes(n, 0);
|
||||
if (a == null) a = fillBytes(n, 255);
|
||||
|
||||
BufferedImage im = makeImage(info.w, info.h, r, g, b, a);
|
||||
frames[i] = im;
|
||||
}
|
||||
lineLengths = null;
|
||||
if ((miscLen > 0) && !err()) {
|
||||
int n = readInt(); // global layer mask info len
|
||||
skipBytes(n);
|
||||
}
|
||||
}
|
||||
|
||||
protected byte[] readPlane(int w, int h) {
|
||||
// readData a single color plane
|
||||
byte[] b = null;
|
||||
int size = w * h;
|
||||
if (hasLayers) {
|
||||
// get RLE compression info for channel
|
||||
rleEncoded = readShort() == 1;
|
||||
if (rleEncoded) {
|
||||
// list of encoded line lengths
|
||||
readLineLengths(h);
|
||||
}
|
||||
}
|
||||
|
||||
if (rleEncoded) {
|
||||
b = readPlaneCompressed(w, h);
|
||||
} else {
|
||||
b = new byte[size];
|
||||
readBytes(b, size);
|
||||
}
|
||||
|
||||
return b;
|
||||
|
||||
}
|
||||
|
||||
protected byte[] readPlaneCompressed(int w, int h) {
|
||||
byte[] b = new byte[w * h];
|
||||
byte[] s = new byte[w * 2];
|
||||
int pos = 0;
|
||||
for (int i = 0; i < h; i++) {
|
||||
if (lineIndex >= lineLengths.length) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
return null;
|
||||
}
|
||||
int len = lineLengths[lineIndex++];
|
||||
readBytes(s, len);
|
||||
decodeRLE(s, 0, len, b, pos);
|
||||
pos += w;
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
protected void decodeRLE(byte[] src, int sindex, int slen, byte[] dst, int dindex) {
|
||||
try {
|
||||
int max = sindex + slen;
|
||||
while (sindex < max) {
|
||||
byte b = src[sindex++];
|
||||
int n = (int) b;
|
||||
if (n < 0) {
|
||||
// dup next byte 1-n times
|
||||
n = 1 - n;
|
||||
b = src[sindex++];
|
||||
for (int i = 0; i < n; i++) {
|
||||
dst[dindex++] = b;
|
||||
}
|
||||
} else {
|
||||
// copy next n+1 bytes
|
||||
n = n + 1;
|
||||
System.arraycopy(src, sindex, dst, dindex, n);
|
||||
dindex += n;
|
||||
sindex += n;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
status = STATUS_FORMAT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
protected short readShort() {
|
||||
// readData big-endian 16-bit integer
|
||||
return (short) ((readByte() << 8) | readByte());
|
||||
}
|
||||
|
||||
protected String readString(int len) {
|
||||
// readData string of specified length
|
||||
String s = "";
|
||||
for (int i = 0; i < len; i++) {
|
||||
s = s + (char) readByte();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
protected void skipBytes(int n) {
|
||||
// skip over n input bytes
|
||||
for (int i = 0; i < n; i++) {
|
||||
readByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -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);
|
||||
|
||||
+1
-1
@@ -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:
|
||||
|
||||
+33
@@ -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.readUnicodeString(pInput);
|
||||
mNames.add(name);
|
||||
left -= name.length() * 2 + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
+22
-11
@@ -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;
|
||||
@@ -57,23 +59,32 @@ final class PSDUtil {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||
static String readPascalString(ImageInputStream pInput) throws IOException {
|
||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||
static String readPascalString(final DataInput pInput) throws IOException {
|
||||
int length = pInput.readUnsignedByte();
|
||||
// int length = pInput.readUnsignedShort();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
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];
|
||||
// TODO: Proably also useful for PICT reader, move to some common util?
|
||||
static String readUnicodeString(final DataInput pInput) throws IOException {
|
||||
int length = pInput.readInt();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length * 2];
|
||||
pInput.readFully(bytes);
|
||||
return new String(bytes);
|
||||
|
||||
return StringUtil.decode(bytes, 0, bytes.length, "UTF-16");
|
||||
}
|
||||
|
||||
static DataInputStream createPackBitsStream(final ImageInputStream pInput, long pLength) {
|
||||
|
||||
+57
@@ -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.readUnicodeString(pInput);
|
||||
mReader = PSDUtil.readUnicodeString(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();
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -1,5 +1,7 @@
|
||||
package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@@ -21,6 +23,7 @@ import java.nio.charset.Charset;
|
||||
*/
|
||||
final class PSDXMPData extends PSDImageResource {
|
||||
protected byte[] mData;
|
||||
Directory mDirectory;
|
||||
|
||||
PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
@@ -29,7 +32,9 @@ final class PSDXMPData extends PSDImageResource {
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
|
||||
pInput.readFully(mData);
|
||||
//pInput.readFully(mData);
|
||||
|
||||
mDirectory = new XMPReader().read(pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,7 +52,7 @@ final class PSDXMPData extends PSDImageResource {
|
||||
builder.append("\"]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a character stream containing the XMP metadata (XML).
|
||||
|
||||
@@ -2,4 +2,5 @@ Implement source subsampling and region of interest
|
||||
Separate package for the resources (seems to be a lot)?
|
||||
Possibility to read only some resources? readResources(int[] resourceKeys)?
|
||||
- Probably faster when we only need the color space
|
||||
PSDImageWriter
|
||||
Support for Photoshop specific TIFF tags (extension for TIFFImageReader)?
|
||||
PSDImageWriter?
|
||||
@@ -5,7 +5,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>twelvemonkeys-imageio-reference</artifactId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
<name>TwelveMonkeys ImageIO reference test cases</name>
|
||||
<description>
|
||||
Test cases for the JDK provided ImageReader implementations for reference.
|
||||
@@ -14,7 +14,7 @@
|
||||
<parent>
|
||||
<artifactId>twelvemonkeys-imageio</artifactId>
|
||||
<groupId>com.twelvemonkeys</groupId>
|
||||
<version>2.2</version>
|
||||
<version>2.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user