/* * @(#)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). * <p> * See: <a href="http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-007_E.pdf">MPO Format Specification</a>. * * @author Werner Randelshofer * @version 1.1 2011-02-01 Improves performance of method getImageMetadata. * <br>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<ImageTypeSpecifier> getImageTypes(int imageIndex) throws IOException { readHeader(); LinkedList<ImageTypeSpecifier> l = new LinkedList<ImageTypeSpecifier>(); 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<TIFFTag, TIFFField> 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<TIFFNode> 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; } }