TMI-JPEG: Now does a better effort to gloss over metadata issues in underlying stream.

This commit is contained in:
Harald Kuhr 2013-09-08 14:43:05 +02:00
parent 47425e2ca0
commit 0ff99afe6d

View File

@ -41,13 +41,16 @@ import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegment;
import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil; import com.twelvemonkeys.imageio.metadata.jpeg.JPEGSegmentUtil;
import com.twelvemonkeys.imageio.util.ProgressListenerBase; import com.twelvemonkeys.imageio.util.ProgressListenerBase;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
import org.w3c.dom.Node;
import javax.imageio.*; import javax.imageio.*;
import javax.imageio.event.IIOReadUpdateListener; import javax.imageio.event.IIOReadUpdateListener;
import javax.imageio.event.IIOReadWarningListener; import javax.imageio.event.IIOReadWarningListener;
import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.spi.ImageReaderSpi; import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.color.ICC_ColorSpace; import java.awt.color.ICC_ColorSpace;
@ -89,6 +92,8 @@ public class JPEGImageReader extends ImageReaderBase {
// TODO: Fix the (stream) metadata inconsistency issues. // TODO: Fix the (stream) metadata inconsistency issues.
// - Sun JPEGMetadata class does not (and can not be made to) support CMYK data.. We need to create all new metadata classes.. :-/ // - Sun JPEGMetadata class does not (and can not be made to) support CMYK data.. We need to create all new metadata classes.. :-/
// TODO: Allow automatic rotation based on EXIF rotation field?
private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.jpeg.debug")); private final static boolean DEBUG = "true".equalsIgnoreCase(System.getProperty("com.twelvemonkeys.imageio.plugins.jpeg.debug"));
/** Segment identifiers for the JPEG segments we care about reading. */ /** Segment identifiers for the JPEG segments we care about reading. */
@ -280,7 +285,19 @@ public class JPEGImageReader extends ImageReaderBase {
assertInput(); assertInput();
checkBounds(imageIndex); checkBounds(imageIndex);
// TODO: This test is not good enough for JDK7, which seems to have fixed some of the issues. // CompoundDirectory exif = getExif();
// if (exif != null) {
// System.err.println("exif: " + exif);
// System.err.println("Orientation: " + exif.getEntryById(TIFF.TAG_ORIENTATION));
// Entry exifIFDEntry = exif.getEntryById(TIFF.TAG_EXIF_IFD);
//
// if (exifIFDEntry != null) {
// Directory exifIFD = (Directory) exifIFDEntry.getValue();
// System.err.println("PixelXDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_X_DIMENSION));
// System.err.println("PixelYDimension: " + exifIFD.getEntryById(EXIF.TAG_PIXEL_Y_DIMENSION));
// }
// }
// NOTE: We rely on the fact that unsupported images has no valid types. This is kind of hacky. // NOTE: We rely on the fact that unsupported images has no valid types. This is kind of hacky.
// Might want to look into the metadata, to see if there's a better way to identify these. // Might want to look into the metadata, to see if there's a better way to identify these.
boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext(); boolean unsupported = !delegate.getImageTypes(imageIndex).hasNext();
@ -288,7 +305,6 @@ public class JPEGImageReader extends ImageReaderBase {
ICC_Profile profile = getEmbeddedICCProfile(false); ICC_Profile profile = getEmbeddedICCProfile(false);
AdobeDCTSegment adobeDCT = getAdobeDCT(); AdobeDCTSegment adobeDCT = getAdobeDCT();
// TODO: Probably something bogus here, as ICC profile isn't applied if reading through the delegate any more...
// We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is) // We need to apply ICC profile unless the profile is sRGB/default gray (whatever that is)
// - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream. // - or only filter out the bad ICC profiles in the JPEGSegmentImageInputStream.
if (delegate.canReadRaster() && ( if (delegate.canReadRaster() && (
@ -307,7 +323,7 @@ public class JPEGImageReader extends ImageReaderBase {
if (DEBUG) { if (DEBUG) {
System.out.println("Reading using delegate"); System.out.println("Reading using delegate");
} }
return delegate.read(imageIndex, param); return delegate.read(imageIndex, param);
} }
@ -442,6 +458,8 @@ public class JPEGImageReader extends ImageReaderBase {
// Apply further color conversion for explicit color space, or just copy the pixels into place // Apply further color conversion for explicit color space, or just copy the pixels into place
if (convert != null) { if (convert != null) {
convert.filter(src, dest); convert.filter(src, dest);
// WritableRaster filtered = convert.filter(src, null);
// new AffineTransformOp(AffineTransform.getRotateInstance(2 * Math.PI, filtered.getWidth() / 2.0, filtered.getHeight() / 2.0), null).filter(filtered, dest);
} }
else { else {
dest.setRect(0, 0, src); dest.setRect(0, 0, src);
@ -728,12 +746,12 @@ public class JPEGImageReader extends ImageReaderBase {
private JFIFSegment getJFIF() throws IOException{ private JFIFSegment getJFIF() throws IOException{
List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF"); List<JPEGSegment> jfif = getAppSegments(JPEG.APP0, "JFIF");
if (!jfif.isEmpty()) { if (!jfif.isEmpty()) {
JPEGSegment segment = jfif.get(0); JPEGSegment segment = jfif.get(0);
return JFIFSegment.read(segment.data()); return JFIFSegment.read(segment.data());
} }
return null; return null;
} }
@ -748,6 +766,27 @@ public class JPEGImageReader extends ImageReaderBase {
return null; return null;
} }
private CompoundDirectory getExif() throws IOException {
List<JPEGSegment> exifSegments = getAppSegments(JPEG.APP1, "Exif");
if (!exifSegments.isEmpty()) {
JPEGSegment exif = exifSegments.get(0);
InputStream data = exif.data();
if (data.read() == -1) { // Read pad
processWarningOccurred("Exif chunk has no data.");
}
else {
ImageInputStream stream = ImageIO.createImageInputStream(data);
return (CompoundDirectory) new EXIFReader().read(stream);
// TODO: Directory offset of thumbnail is wrong/relative to container stream, causing trouble for the EXIFReader...
}
}
return null;
}
// TODO: Util method? // TODO: Util method?
static byte[] readFully(DataInput stream, int len) throws IOException { static byte[] readFully(DataInput stream, int len) throws IOException {
if (len == 0) { if (len == 0) {
@ -911,7 +950,7 @@ public class JPEGImageReader extends ImageReaderBase {
processWarningOccurred("Exif chunk has no data."); processWarningOccurred("Exif chunk has no data.");
} }
else { else {
ImageInputStream stream = ImageIO.createImageInputStream(data); ImageInputStream stream = new MemoryCacheImageInputStream(data);
CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream); CompoundDirectory exifMetadata = (CompoundDirectory) new EXIFReader().read(stream);
if (exifMetadata.directoryCount() == 2) { if (exifMetadata.directoryCount() == 2) {
@ -965,16 +1004,15 @@ public class JPEGImageReader extends ImageReaderBase {
return thumbnails.get(thumbnailIndex).read(); return thumbnails.get(thumbnailIndex).read();
} }
// Metadata // Metadata
@Override @Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException { public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
// TODO: Nice try, but no cigar.. getAsTree does not return a "live" view, so any modifications are thrown away
IIOMetadata metadata = delegate.getImageMetadata(imageIndex); IIOMetadata metadata = delegate.getImageMetadata(imageIndex);
// IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(metadata.getNativeMetadataFormatName()); String format = metadata.getNativeMetadataFormatName();
// Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0); IIOMetadataNode tree = (IIOMetadataNode) metadata.getAsTree(format);
Node jpegVariety = tree.getElementsByTagName("JPEGvariety").item(0);
// TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node. // TODO: Allow EXIF (as app1EXIF) in the JPEGvariety (sic) node.
// As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like: // As EXIF is (a subset of) TIFF, (and the EXIF data is a valid TIFF stream) probably use something like:
@ -996,12 +1034,17 @@ public class JPEGImageReader extends ImageReaderBase {
the version to the method/constructor used to obtain an IIOMetadata object.) the version to the method/constructor used to obtain an IIOMetadata object.)
*/ */
// IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC"); IIOMetadataNode app2ICC = new IIOMetadataNode("app2ICC");
// app2ICC.setUserObject(getEmbeddedICCProfile()); app2ICC.setUserObject(getEmbeddedICCProfile(true));
// jpegVariety.getFirstChild().appendChild(app2ICC); Node jpegVarietyFirstChild = jpegVariety.getFirstChild();
if (jpegVarietyFirstChild != null) {
jpegVarietyFirstChild.appendChild(app2ICC);
}
// new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false); // new XMLSerializer(System.err, System.getProperty("file.encoding")).serialize(tree, false);
metadata.mergeTree(format, tree);
return metadata; return metadata;
} }