mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
#390 Deferred parsing of embedded resources. Allows reading pixel data for images with unparseable metadata.
Broken metadata is now ignored + warning, rather than causing exceptions.
This commit is contained in:
parent
6b966a2d4f
commit
1c27b58598
@ -39,6 +39,7 @@ import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
@ -73,6 +74,7 @@ public final class XMPReader extends MetadataReader {
|
||||
// TODO: Refactor scanner to return inputstream?
|
||||
// TODO: Be smarter about ASCII-NULL termination/padding (the SAXParser aka Xerces DOMParser doesn't like it)...
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
builder.setErrorHandler(new DefaultHandler());
|
||||
Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(input)));
|
||||
|
||||
// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding"));
|
||||
|
@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2017, 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.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* PSDDirectoryResource
|
||||
*/
|
||||
abstract class PSDDirectoryResource extends PSDImageResource {
|
||||
byte[] data;
|
||||
private Directory directory;
|
||||
|
||||
PSDDirectoryResource(short resourceId, ImageInputStream input) throws IOException {
|
||||
super(resourceId, input);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
data = new byte[(int) size]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
|
||||
pInput.readFully(data);
|
||||
}
|
||||
|
||||
abstract Directory parseDirectory() throws IOException;
|
||||
|
||||
final void initDirectory() throws IOException {
|
||||
if (directory == null) {
|
||||
directory = parseDirectory();
|
||||
}
|
||||
}
|
||||
|
||||
Directory getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
|
||||
int length = Math.min(256, data.length);
|
||||
String data = StringUtil.decode(this.data, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
|
||||
builder.append(", data: \"").append(data);
|
||||
|
||||
if (length < this.data.length) {
|
||||
builder.append("...");
|
||||
}
|
||||
|
||||
builder.append("\"]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -30,6 +30,7 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.tiff.TIFFReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
@ -45,22 +46,26 @@ import java.io.IOException;
|
||||
* @see <a href="http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html">Aware systems TIFF tag reference</a>
|
||||
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
||||
*/
|
||||
final class PSDEXIF1Data extends PSDImageResource {
|
||||
protected Directory directory;
|
||||
final class PSDEXIF1Data extends PSDDirectoryResource {
|
||||
|
||||
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// This is in essence an embedded TIFF file.
|
||||
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
||||
directory = new TIFFReader().read(pInput);
|
||||
Directory parseDirectory() throws IOException {
|
||||
// The data is in essence an embedded TIFF file.
|
||||
return new TIFFReader().read(new ByteArrayImageInputStream(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Directory directory = getDirectory();
|
||||
|
||||
if (directory == null) {
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
StringBuilder builder = toStringBuilder();
|
||||
builder.append(", ").append(directory);
|
||||
builder.append("]");
|
||||
|
@ -39,6 +39,9 @@ import java.io.IOException;
|
||||
* @version $Id: PSDGlobalLayerMask.java,v 1.0 May 8, 2008 5:33:48 PM haraldk Exp$
|
||||
*/
|
||||
final class PSDGlobalLayerMask {
|
||||
|
||||
static final PSDGlobalLayerMask NULL_MASK = new PSDGlobalLayerMask();
|
||||
|
||||
final int colorSpace;
|
||||
final short[] colors = new short[4];
|
||||
final int opacity;
|
||||
@ -58,6 +61,12 @@ final class PSDGlobalLayerMask {
|
||||
pInput.skipBytes(globalLayerMaskLength - 17);
|
||||
}
|
||||
|
||||
private PSDGlobalLayerMask() {
|
||||
colorSpace = 0;
|
||||
opacity = 0;
|
||||
kind = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder(getClass().getSimpleName());
|
||||
|
@ -40,8 +40,6 @@ import java.io.IOException;
|
||||
* @version $Id: PSDHeader.java,v 1.0 Apr 29, 2008 5:18:22 PM haraldk Exp$
|
||||
*/
|
||||
final class PSDHeader {
|
||||
private static final int PSD_MAX_SIZE = 30000;
|
||||
private static final int PSB_MAX_SIZE = 300000;
|
||||
// The header is 26 bytes in length and is structured as follows:
|
||||
//
|
||||
// typedef struct _PSD_HEADER
|
||||
@ -57,6 +55,9 @@ final class PSDHeader {
|
||||
// WORD Mode; /* Color mode */
|
||||
// } PSD_HEADER;
|
||||
|
||||
private static final int PSD_MAX_SIZE = 30000;
|
||||
private static final int PSB_MAX_SIZE = 300000;
|
||||
|
||||
final short channels;
|
||||
final int width;
|
||||
final int height;
|
||||
|
@ -30,6 +30,7 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.IOException;
|
||||
@ -41,21 +42,24 @@ import java.io.IOException;
|
||||
* @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 directory;
|
||||
|
||||
final class PSDIPTCData extends PSDDirectoryResource {
|
||||
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
// Read IPTC directory
|
||||
directory = new IPTCReader().read(pInput);
|
||||
Directory parseDirectory() throws IOException {
|
||||
return new IPTCReader().read(new ByteArrayImageInputStream(data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
Directory directory = getDirectory();
|
||||
|
||||
if (directory == null) {
|
||||
return super.toString();
|
||||
}
|
||||
|
||||
StringBuilder builder = toStringBuilder();
|
||||
builder.append(", ").append(directory);
|
||||
builder.append("]");
|
||||
|
@ -892,8 +892,8 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
long imageResourcesLength = imageInput.readUnsignedInt();
|
||||
|
||||
if (pParseData && metadata.imageResources == null && imageResourcesLength > 0) {
|
||||
metadata.imageResources = new ArrayList<>();
|
||||
long expectedEnd = imageInput.getStreamPosition() + imageResourcesLength;
|
||||
metadata.imageResources = new ArrayList<>();
|
||||
|
||||
while (imageInput.getStreamPosition() < expectedEnd) {
|
||||
PSDImageResource resource = PSDImageResource.read(imageInput);
|
||||
@ -975,6 +975,9 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
}
|
||||
// TODO: Else skip?
|
||||
}
|
||||
else {
|
||||
metadata.globalLayerMask = PSDGlobalLayerMask.NULL_MASK;
|
||||
}
|
||||
|
||||
// TODO: Parse "Additional layer information"
|
||||
|
||||
@ -982,9 +985,9 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
// imageInput.seek(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
||||
// imageInput.flushBefore(metadata.layerAndMaskInfoStart + layerAndMaskInfoLength + (header.largeFormat ? 8 : 4));
|
||||
|
||||
if (DEBUG) {
|
||||
if (pParseData && DEBUG) {
|
||||
System.out.println("layerInfo: " + metadata.layerInfo);
|
||||
System.out.println("globalLayerMask: " + metadata.globalLayerMask);
|
||||
System.out.println("globalLayerMask: " + (metadata.globalLayerMask != PSDGlobalLayerMask.NULL_MASK ? metadata.globalLayerMask : null));
|
||||
}
|
||||
//}
|
||||
}
|
||||
@ -1171,8 +1174,25 @@ public final class PSDImageReader extends ImageReaderBase {
|
||||
readLayerAndMaskInfo(true);
|
||||
|
||||
// NOTE: Need to make sure compression is set in metadata, even without reading the image data!
|
||||
// TODO: Move this to readLayerAndMaskInfo?
|
||||
if (metadata.compression == -1) {
|
||||
imageInput.seek(metadata.imageDataStart);
|
||||
metadata.compression = imageInput.readShort();
|
||||
}
|
||||
|
||||
// Initialize XMP data etc.
|
||||
for (PSDImageResource resource : metadata.imageResources) {
|
||||
if (resource instanceof PSDDirectoryResource) {
|
||||
PSDDirectoryResource directoryResource = (PSDDirectoryResource) resource;
|
||||
|
||||
try {
|
||||
directoryResource.initDirectory();
|
||||
}
|
||||
catch (IOException e) {
|
||||
processWarningOccurred(String.format("Error parsing %s: %s", resource.getClass().getSimpleName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata; // TODO: clone if we change to mutable metadata
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ import java.util.List;
|
||||
*/
|
||||
public final class PSDMetadata extends AbstractMetadata {
|
||||
|
||||
public static final String NATIVE_METADATA_FORMAT_NAME = "com_twelvemonkeys_imageio_psd_image_1.0";
|
||||
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";
|
||||
// TODO: Support TIFF metadata, based on EXIF/XMP + merge in PSD specifics
|
||||
|
||||
@ -93,7 +93,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
|
||||
static final String[] PRINT_SCALE_STYLES = {"centered", "scaleToFit", "userDefined"};
|
||||
|
||||
protected PSDMetadata() {
|
||||
PSDMetadata() {
|
||||
// TODO: Allow XMP, EXIF (TIFF) and IPTC as extra formats?
|
||||
super(true, NATIVE_METADATA_FORMAT_NAME, NATIVE_METADATA_FORMAT_CLASS_NAME, null, null);
|
||||
}
|
||||
@ -118,7 +118,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
root.appendChild(createLayerInfoNode());
|
||||
}
|
||||
|
||||
if (globalLayerMask != null) {
|
||||
if (globalLayerMask != null && globalLayerMask != PSDGlobalLayerMask.NULL_MASK) {
|
||||
root.appendChild(createGlobalLayerMaskNode());
|
||||
}
|
||||
|
||||
@ -291,9 +291,11 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "IPTC");
|
||||
node.setUserObject(iptc.directory);
|
||||
node.setUserObject(iptc.data);
|
||||
|
||||
appendEntries(node, "IPTC", iptc.directory);
|
||||
if (iptc.getDirectory() != null) {
|
||||
appendEntries(node, "IPTC", iptc.getDirectory());
|
||||
}
|
||||
}
|
||||
else if (imageResource instanceof PSDEXIF1Data) {
|
||||
// TODO: Revise/rethink this...
|
||||
@ -302,9 +304,11 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "TIFF");
|
||||
// TODO: Set byte[] data instead
|
||||
node.setUserObject(exif.directory);
|
||||
node.setUserObject(exif.data);
|
||||
|
||||
appendEntries(node, "EXIF", exif.directory);
|
||||
if (exif.getDirectory() != null) {
|
||||
appendEntries(node, "EXIF", exif.getDirectory());
|
||||
}
|
||||
}
|
||||
else if (imageResource instanceof PSDXMPData) {
|
||||
// TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid...
|
||||
@ -313,10 +317,12 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "XMP");
|
||||
appendEntries(node, "XMP", xmp.directory);
|
||||
|
||||
// Set the entire XMP document as user data
|
||||
node.setUserObject(xmp.data);
|
||||
|
||||
if (xmp.getDirectory() != null) {
|
||||
appendEntries(node, "XMP", xmp.getDirectory());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Generic resource..
|
||||
@ -662,7 +668,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
PSDEXIF1Data data = exif.next();
|
||||
|
||||
// Get the EXIF DateTime (aka ModifyDate) tag if present
|
||||
Entry dateTime = data.directory.getEntryById(TIFF.TAG_DATE_TIME);
|
||||
Entry dateTime = data.getDirectory().getEntryById(TIFF.TAG_DATE_TIME);
|
||||
if (dateTime != null) {
|
||||
IIOMetadataNode imageCreationTime = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime
|
||||
// Format: "YYYY:MM:DD hh:mm:ss"
|
||||
@ -707,7 +713,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
if (textResource instanceof PSDIPTCData) {
|
||||
PSDIPTCData iptc = (PSDIPTCData) textResource;
|
||||
|
||||
appendTextEntriesFlat(text, iptc.directory, new FilterIterator.Filter<Entry>() {
|
||||
appendTextEntriesFlat(text, iptc.getDirectory(), new FilterIterator.Filter<Entry>() {
|
||||
public boolean accept(final Entry pEntry) {
|
||||
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||
|
||||
@ -727,7 +733,7 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
else if (textResource instanceof PSDEXIF1Data) {
|
||||
PSDEXIF1Data exif = (PSDEXIF1Data) textResource;
|
||||
|
||||
appendTextEntriesFlat(text, exif.directory, new FilterIterator.Filter<Entry>() {
|
||||
appendTextEntriesFlat(text, exif.getDirectory(), new FilterIterator.Filter<Entry>() {
|
||||
public boolean accept(final Entry pEntry) {
|
||||
Integer tagId = (Integer) pEntry.getIdentifier();
|
||||
|
||||
@ -743,11 +749,12 @@ public final class PSDMetadata extends AbstractMetadata {
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (textResource instanceof PSDXMPData) {
|
||||
//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/TIFF NativeDigest field to validate if the values are in sync..?
|
||||
PSDXMPData xmp = (PSDXMPData) textResource;
|
||||
}
|
||||
// TODO: Use XMP IPTC/EXIF/TIFF NativeDigest field to validate if the values are in sync..?
|
||||
//PSDXMPData xmp = (PSDXMPData) textResource;
|
||||
//}
|
||||
}
|
||||
|
||||
return text;
|
||||
|
@ -31,11 +31,9 @@ package com.twelvemonkeys.imageio.plugins.psd;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* XMP metadata.
|
||||
@ -47,19 +45,12 @@ import java.nio.charset.Charset;
|
||||
* @see <a href="http://www.adobe.com/products/xmp/">Adobe Extensible Metadata Platform (XMP)</a>
|
||||
* @see <a href="http://www.adobe.com/devnet/xmp/">Adobe XMP Developer Center</a>
|
||||
*/
|
||||
final class PSDXMPData extends PSDImageResource {
|
||||
protected byte[] data;
|
||||
Directory directory;
|
||||
|
||||
final class PSDXMPData extends PSDDirectoryResource {
|
||||
PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||
super(pId, pInput);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||
data = new byte[(int) size]; // TODO: Fix potential overflow, or document why that can't happen (read spec)
|
||||
pInput.readFully(data);
|
||||
|
||||
Directory parseDirectory() throws IOException {
|
||||
// Chop off potential trailing null-termination/padding that SAX parsers don't like...
|
||||
int len = data.length;
|
||||
for (; len > 0; len--) {
|
||||
@ -68,32 +59,6 @@ final class PSDXMPData extends PSDImageResource {
|
||||
}
|
||||
}
|
||||
|
||||
directory = new XMPReader().read(new ByteArrayImageInputStream(data, 0, len));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = toStringBuilder();
|
||||
|
||||
int length = Math.min(256, data.length);
|
||||
String data = StringUtil.decode(this.data, 0, length, "UTF-8").replace('\n', ' ').replaceAll("\\s+", " ");
|
||||
builder.append(", data: \"").append(data);
|
||||
|
||||
if (length < this.data.length) {
|
||||
builder.append("...");
|
||||
}
|
||||
|
||||
builder.append("\"]");
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a character stream containing the XMP metadata (XML).
|
||||
*
|
||||
* @return the XMP metadata.
|
||||
*/
|
||||
public Reader getData() {
|
||||
return new InputStreamReader(new ByteArrayInputStream(data), Charset.forName("UTF-8"));
|
||||
return new XMPReader().read(new ByteArrayImageInputStream(data, 0, len));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user