
org.monte.media.mpo.MPOImageReader Maven / Gradle / Ivy
/*
* @(#)MPOImageReader.java 1.1 2011-02-01
*
* Copyright (c) 2009-2011 Werner Randelshofer, Goldau, Switzerland.
* All rights reserved.
*
* You may not use, copy or modify this file, except in compliance with the
* license agreement you entered into with Werner Randelshofer.
* For details see accompanying license terms.
*/
package org.monte.media.mpo;
import org.monte.media.io.SubImageInputStream;
import org.monte.media.exif.DefaultIIOMetadata;
import org.monte.media.exif.EXIFReader;
import org.monte.media.exif.EXIFTagSet;
import org.monte.media.exif.MPEntryTagSet;
import org.monte.media.exif.MPFTagSet;
import org.monte.media.tiff.TIFFDirectory;
import org.monte.media.tiff.TIFFField;
import org.monte.media.tiff.TIFFNode;
import org.monte.media.tiff.TIFFTag;
import com.sun.imageio.plugins.jpeg.JPEGImageReader;
import java.awt.image.BufferedImage;
import java.awt.image.DirectColorModel;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.stream.ImageInputStream;
import org.monte.media.jpeg.CMYKJPEGImageReader;
/**
* Reads an image in the MultiPicture Object format (MPO).
*
* See: MPO Format Specification.
*
* @author Werner Randelshofer
* @version 1.1 2011-02-01 Improves performance of method getImageMetadata.
*
1.0 2009-12-14 Created.
*/
public class MPOImageReader extends ImageReader {
private static DirectColorModel RGB = new DirectColorModel(24, 0xff0000, 0xff00, 0xff, 0x0);
/** Number of images. -1 if not known. */
private int numImages = -1;
/** Image offsets. null if not known. */
private long[] imageOffsets;
/** Image lengths. null if not known. */
private long[] imageLengths;
/** Thumbnail offsets. null if not known. */
private long[] thumbOffsets;
/** Thumbnail lengths. null if not known. */
private long[] thumbLengths;
/** Width of the images. */
private int width = -1;
/** Height of the images. */
private int height = -1;
/** Metadata of all images. */
private IIOMetadata[] imageMetadata;
private EXIFReader er;
public MPOImageReader(MPOImageReaderSpi originatingProvider) {
super(originatingProvider);
}
@Override
public int getNumImages(boolean allowSearch) throws IOException {
if (allowSearch && numImages == -1) {
readHeader();
}
return numImages;
}
@Override
public int getWidth(int imageIndex) throws IOException {
readHeader();
return width;
}
@Override
public int getHeight(int imageIndex) throws IOException {
readHeader();
return height;
}
@Override
public Iterator getImageTypes(int imageIndex) throws IOException {
readHeader();
LinkedList l = new LinkedList();
l.add(new ImageTypeSpecifier(RGB, RGB.createCompatibleSampleModel(width, height)));
return l.iterator();
}
@Override
public IIOMetadata getStreamMetadata() throws IOException {
return null;
}
@Override
public IIOMetadata getImageMetadata(int imageIndex) throws IOException {
readHeader();
return imageMetadata[imageIndex];
}
@Override
public BufferedImage read(int imageIndex, ImageReadParam param)
throws IOException {
readHeader();
ImageInputStream in = (ImageInputStream) getInput();
SubImageInputStream sin = new SubImageInputStream(in, imageOffsets[imageIndex], imageLengths[imageIndex]);
sin.seek(0);
ImageReader ir = new CMYKJPEGImageReader(getOriginatingProvider());
ir.setInput(sin);
BufferedImage img = ir.read(0);
ir.dispose();
return img;
}
@Override
public int getNumThumbnails(int imageIndex) throws IOException {
readHeader();
return super.getNumThumbnails(imageIndex);
}
/** Reads the header.
* Does nothing if the header has already been loaded.
*/
private void readHeader() throws IOException {
if (numImages == -1) {
ImageInputStream in = (ImageInputStream) getInput();
in.seek(0);
er = new EXIFReader(in);
er.setFirstImageOnly(false);
er.read();
// Get some information that is easy to obtain through a map
{
HashMap m = er.getMetaDataMap();
TIFFField mde;
if ((mde = m.get(MPFTagSet.get(MPFTagSet.TAG_NumberOfImages))) != null) {
numImages = ((Number) mde.getData()).intValue();
} else {
numImages = 1;
}
if ((mde = m.get(EXIFTagSet.PixelXDimension)) != null) {
width = ((Number) mde.getData()).intValue();
}
if ((mde = m.get(EXIFTagSet.PixelYDimension)) != null) {
height = ((Number) mde.getData()).intValue();
}
}
imageOffsets = new long[numImages];
imageLengths = new long[numImages];
if (numImages == 1) {
imageOffsets[0] = 0;
imageLengths[0] = in.length();
}
// Get now at the tough part
int index = 0;
for (Iterator e = er.getMetaDataTree().preorderIterator(); e.hasNext();) {
TIFFNode n = e.next();
if (n instanceof TIFFDirectory) {
TIFFDirectory dir = (TIFFDirectory) n;
//System.out.println("dir:" + dir.getName());
if (dir.getName() != null && dir.getName().equals("MPEntry")) {
long dirOffset = dir.getFileSegments().get(0).getOffset();
TIFFField offsetField = dir.getField(MPEntryTagSet.IndividualImageDataOffset);
TIFFField lengthField = dir.getField(MPEntryTagSet.IndividualImageSize);
if (offsetField != null && lengthField != null) {
long dataOffset = (Long) offsetField.getData();
imageOffsets[index] = dataOffset == 0 ? 0 : dirOffset + dataOffset;
imageLengths[index] = (Long) lengthField.getData();
index++;
}
}
}
}
// Store metadata for later access
String formatName = "com_sun_media_imageio_plugins_tiff_image_1.0";
imageMetadata = new IIOMetadata[numImages];
for (int i = 0; i < numImages; i++) {
imageMetadata[i] = new DefaultIIOMetadata(formatName, er.getIIOMetadataTree(formatName, i));
}
in.seek(0);
}
}
public TIFFNode getExifMetadata() throws IOException {
readHeader();
return er.getMetaDataTree();
}
public EXIFReader getExifReader() throws IOException {
readHeader();
return er;
}
}