All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.monte.media.exif.EXIFReader Maven / Gradle / Ivy

There is a newer version: 1.1
Show newest version
/*
 * @(#)EXIFReader.java 
 * 
 * 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.exif;

import org.monte.media.io.ImageInputStreamAdapter;
import org.monte.media.io.ByteArrayImageInputStream;
import org.monte.media.jpeg.JFIFInputStream;
import org.monte.media.jpeg.JFIFInputStream.Segment;
import org.monte.media.riff.RIFFChunk;
import org.monte.media.riff.RIFFParser;
import org.monte.media.riff.RIFFVisitor;
import org.monte.media.tiff.FileSegment;
import org.monte.media.tiff.BaselineTagSet;
import org.monte.media.tiff.IFDDataType;
import org.monte.media.tiff.IFD;
import org.monte.media.tiff.IFDEntry;
import org.monte.media.math.Rational;
import org.monte.media.tiff.TIFFDirectory;
import org.monte.media.tiff.TIFFField;
import org.monte.media.tiff.TIFFInputStream;
import org.monte.media.tiff.TIFFNode;
import org.monte.media.tiff.TIFFTag;
import org.monte.media.tiff.TagSet;
import org.monte.media.AbortException;
import org.monte.media.ParseException;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;
import java.util.TreeSet;
import javax.imageio.ImageIO;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.metadata.IIOMetadataNode;
import javax.imageio.stream.FileImageInputStream;
import javax.imageio.stream.ImageInputStream;

/**
 * Reads EXIF and MP meta data from a JPEG, MPO or AVI file. 

Creates a tree * structure of {@code DefaultMutableTreeNode}s. Nodes with a String user object * describe the hierarchy of the meta data. Nodes with an MetaDataEntry as user * object hold the actual meta data.

Sources:

Exchangeable image file * format for digital still cameras: EXIF Version 2.2. (April, 2002). Standard * of Japan Electronics and Information Technology Industries Association. JEITA * CP-3451. http://www.exif.org/Exif2-2.PDF *

Multi-Picture Format (February 4, 2009). Standard of the Camera & Imaging * Products Association. CIPA DC-007-Translation-2009. * http://www.cipa.jp/english/hyoujunka/kikaku/pdf/DC-007_E.pdf * * @author Werner Randelshofer * @version $Id: EXIFReader.java 299 2013-01-03 07:40:18Z werner $ */ public class EXIFReader { private File file; private ImageInputStream iin; /** * When this is set to true, the reader stops after heaving read the * metadata of the first image. */ private boolean firstImageOnly; /** * Whether data from the file container shall be added to the Exif data. For * most file types, this adds the width and height of the image to the Exif. */ private boolean includeContainerMetadata = true; /** * Meta data tree. */ private TIFFNode root; /** * Contains offsets to additional images. */ private TreeSet imageOffsets = new TreeSet(); public EXIFReader(File f) { this.file = f; } public EXIFReader(ImageInputStream iin) { this.iin = iin; } public void setFirstImageOnly(boolean b) { firstImageOnly = b; } public boolean isFirstImageOnly() { return firstImageOnly; } public void setIncludeContainerMetadata(boolean b) { includeContainerMetadata = b; } public boolean isIncludeContainerMetadata() { return includeContainerMetadata; } /** * Reads the meta data from the file or input stream that has been set on * the constructor. */ public void read() throws IOException { if (file != null) { iin = new FileImageInputStream(file); } try { iin.seek(0); // Determine file type int magic = iin.readInt(); iin.seek(0); if (magic == 0x49492a00) { // Little-Endian TIFF File // XXX - Implement Little-Endian TIFF File support } else if (magic == 0x4d4d002a) { // Big-Endian TIFF File // XXX - Implement Big-Endian TIFF File support } else if (magic == 0x52494646) { // Little-Endian RIFF File readRIFF(iin); } else { // JFIF File readJFIF(iin); } } finally { if (file != null) { iin.close(); } } } /** * Reads the metadata from a JFIF file. */ private void readJFIF(ImageInputStream iin) throws IOException { root = new TIFFDirectory(null, null, -1); ByteArrayOutputStream exifStream = null; ArrayList exifSeg = null; ByteArrayOutputStream mpStream = null; ArrayList mpSeg = null; byte[] buf = new byte[512]; JFIFInputStream in = new JFIFInputStream(new BufferedInputStream(new ImageInputStreamAdapter(iin))); int imageCount = 0; TIFFDirectory imageNode = null; // Collect APP2_MARKER data segments with Exif content Extraction: for (Segment seg = in.getNextSegment(); seg != null; seg = in.getNextSegment()) { switch (seg.marker) { case JFIFInputStream.SOF0_MARKER: case JFIFInputStream.SOF1_MARKER: case JFIFInputStream.SOF2_MARKER: case JFIFInputStream.SOF3_MARKER: //case JFIFInputStream.SOF4_MARKER: case JFIFInputStream.SOF5_MARKER: case JFIFInputStream.SOF6_MARKER: case JFIFInputStream.SOF7_MARKER: //case JFIFInputStream.SOF8_MARKER: case JFIFInputStream.SOF9_MARKER: case JFIFInputStream.SOFA_MARKER: case JFIFInputStream.SOFB_MARKER: //case JFIFInputStream.SOFC_MARKER: case JFIFInputStream.SOFD_MARKER: case JFIFInputStream.SOFE_MARKER: case JFIFInputStream.SOFF_MARKER: /* * typedef struct { * ubyte samplePrecision; * ushort numberOfLines; * ushort numberOfSamplesPerLine; * ubyte numberOfComponentsInFrame; * SOFFrameComponent[numberOfComponentsInFrame] frameComponent; * } SOF0; */ if (includeContainerMetadata && imageNode != null) { int samplePrecision = in.read() & 0xff; int numberOfLines = ((in.read() & 0xff) << 8) | (in.read()); int samplesPerLine = ((in.read() & 0xff) << 8) | (in.read()); int numberOfComponents = in.read() & 0xff; TIFFDirectory dir = new TIFFDirectory(BaselineTagSet.getInstance(), null, 0); imageNode.add(dir); dir.add(new TIFFField(BaselineTagSet.BitsPerSample, samplePrecision)); dir.add(new TIFFField(BaselineTagSet.ImageWidth, samplesPerLine)); dir.add(new TIFFField(BaselineTagSet.ImageHeight, numberOfLines)); } break; case JFIFInputStream.SOI_MARKER: imageNode = new TIFFDirectory(ImageTagSet.getInstance(), null, imageCount++, 0, in.getStreamPosition(), new FileSegment(seg.offset, seg.length)); root.add(imageNode); exifStream = new ByteArrayOutputStream(); exifSeg = new ArrayList(); mpStream = new ByteArrayOutputStream(); mpSeg = new ArrayList(); break; case JFIFInputStream.APP1_MARKER: // Test whether segment starts with Exif identifier. try { in.read(buf, 0, 6); if (!new String(buf, 0, 6, "ASCII").equals("Exif\u0000\u0000")) { // the segment does not start with the double // zero-terminated string Exif. skip it. continue; } } catch (IOException e) { // the segment does not start with a zero-terminated string. // skip it. continue; } exifSeg.add(new FileSegment(seg.offset + 6, seg.length - 6)); for (int count = in.read(buf); count != -1; count = in.read(buf)) { exifStream.write(buf, 0, count); } break; case JFIFInputStream.APP2_MARKER: // Test whether segment starts with MPF identifier. try { in.read(buf, 0, 4); if (!new String(buf, 0, 4, "ASCII").equals("MPF\u0000")) { // the segment does not start with the // zero-terminated string MPF. skip it continue; } } catch (IOException e) { // the segment does not start with a zero-terminated string. // skip it. continue; } mpSeg.add(new FileSegment(seg.offset + 4, seg.length - 4)); for (int count = in.read(buf); count != -1; count = in.read(buf)) { mpStream.write(buf, 0, count); } break; case JFIFInputStream.EOI_MARKER: break; case JFIFInputStream.SOS_MARKER: // Extract the Exif data if (exifStream.size() > 0) { TIFFInputStream tin = new TIFFInputStream(new ByteArrayImageInputStream(exifStream.toByteArray())); readTIFFIFD(tin, imageNode, exifSeg); exifStream.reset(); } // Extract the MP data if (mpStream.size() > 0) { TIFFInputStream tin = new TIFFInputStream(new ByteArrayImageInputStream(mpStream.toByteArray())); readMPFIFD(tin, imageNode, null, mpSeg); mpStream.reset(); } if (firstImageOnly) { break Extraction; } else { long streamPosition = in.getStreamPosition(); Long nextImage = imageOffsets.ceiling(streamPosition); if (nextImage == null) { break Extraction; } else { in.skipFully(nextImage - streamPosition); } } break; } } } /** * Reads the Exif metadata from an AVI RIFF file. */ private void readRIFF(ImageInputStream iin) throws IOException { root = new TIFFDirectory(null, null, -1); RIFFParser parser = new RIFFParser(); final int hdrl_ID = RIFFParser.stringToID("hdrl"); final int strl_ID = RIFFParser.stringToID("strl"); final int strh_ID = RIFFParser.stringToID("strh"); final int strd_ID = RIFFParser.stringToID("strd"); final int AVI_ID = RIFFParser.stringToID("AVI "); final int AVIF_ID = RIFFParser.stringToID("AVIF"); final int RIFF_ID = RIFFParser.stringToID("RIFF"); final int LIST_ID = RIFFParser.stringToID("LIST"); parser.declareDataChunk(strl_ID, strh_ID); parser.declareDataChunk(strl_ID, strd_ID); parser.declareGroupChunk(AVI_ID, RIFF_ID); parser.declareGroupChunk(hdrl_ID, LIST_ID); parser.declareGroupChunk(strl_ID, LIST_ID); try { parser.parse(new ImageInputStreamAdapter(iin), new RIFFVisitor() { private boolean isAVI; private int trackCount = 0; private TIFFDirectory trackNode; @Override public void enterGroup(RIFFChunk group) throws ParseException, AbortException { if (group.getType() == AVI_ID) { isAVI = true; } } @Override public void leaveGroup(RIFFChunk group) throws ParseException, AbortException { if (group.getType() == AVI_ID) { isAVI = false; } if (isAVI && group.getType() == hdrl_ID) { throw new AbortException(); } } @Override public void visitChunk(RIFFChunk group, RIFFChunk chunk) throws ParseException, AbortException { if (chunk.getID() == strh_ID) { trackCount++; } else if (chunk.getID() == strd_ID) { trackNode = new TIFFDirectory(TrackTagSet.getInstance(), null, trackCount - 1, null, null, new FileSegment(chunk.getScan(), chunk.getSize())); root.add(trackNode); ByteArrayImageInputStream in = new ByteArrayImageInputStream(chunk.getData(), 8, (int) chunk.getSize() - 8, ByteOrder.LITTLE_ENDIAN); try { TIFFInputStream tin = new TIFFInputStream(in, ByteOrder.LITTLE_ENDIAN, 0); ArrayList tiffSeg = new ArrayList(); tiffSeg.add(new FileSegment(chunk.getScan() + 8, chunk.getSize() - 8)); readTIFFIFD(tin, trackNode, tiffSeg); //} //System.out.println("EXIFReader.readRIFF magic:" + RIFFParser.idToString(magic)); } catch (IOException ex) { ParseException e = new ParseException("Error parsing AVI strd chunk."); e.initCause(ex); throw e; } finally { in.close(); } if (isFirstImageOnly()) { throw new AbortException(); } } } @Override public boolean enteringGroup(RIFFChunk group) { return true; } }); } catch (ParseException ex) { ex.printStackTrace(); } catch (AbortException ex) { // aborts are explicitly done by the visitor } } /** * Reads the Exif metadata from an AVI RIFF file. */ public void readAVIstrdChunk(byte[] data) throws IOException { int track = 0; // track number int scan = 0; root = new TIFFDirectory(null, null, -1); TIFFDirectory trackNode = new TIFFDirectory(TrackTagSet.getInstance(), null, track, null, null, new FileSegment(0, data.length)); root.add(trackNode); ByteArrayImageInputStream in = new ByteArrayImageInputStream(data, 8, (int) data.length - 8, ByteOrder.LITTLE_ENDIAN); TIFFInputStream tin = new TIFFInputStream(in, ByteOrder.LITTLE_ENDIAN, 0); ArrayList tiffSeg = new ArrayList(); tiffSeg.add(new FileSegment(scan + 8, data.length - 8)); readTIFFIFD(tin, trackNode, tiffSeg); } private void readTIFFIFD(TIFFInputStream tin, TIFFDirectory parent, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = BaselineTagSet.getInstance(); for (IFD ifd = tin.readIFD(tin.getFirstIFDOffset(), true, true); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, null, count++, ifd, null, tiffSeg); parent.add(ifdNode); long thumbnailOffset = 0; long thumbnailLength = 0; int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { switch (entry.getTagNumber()) { case BaselineTagSet.TAG_EXIF: readExifIFD(tin, entry.getValueOffset(), ifdNode, entry, tiffSeg); break; case BaselineTagSet.TAG_GPS: readGPSIFD(tin, entry.getValueOffset(), ifdNode, entry, tiffSeg); break; case BaselineTagSet.TAG_Interoperability: readInteropIFD(tin, entry.getValueOffset(), ifdNode, entry, tiffSeg); break; case BaselineTagSet.TAG_JPEGInterchangeFormat: thumbnailOffset = entry.getValueOffset(); ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); break; case BaselineTagSet.TAG_JPEGInterchangeFormatLength: thumbnailLength = entry.getValueOffset(); ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); break; default: ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); break; } entryCount++; } // Hack the thumbnail image in, if one is present if (thumbnailOffset > 0 && thumbnailLength > 0) { byte[] buf = new byte[(int) thumbnailLength]; tin.read(thumbnailOffset, buf, 0, (int) thumbnailLength); IFDEntry entry = new IFDEntry(BaselineTagSet.TAG_JPEGThumbnailImage, IFDDataType.UNDEFINED.getTypeNumber(), thumbnailLength, thumbnailOffset, -1); ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); } } } private void readExifIFD(TIFFInputStream tin, long offset, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = EXIFTagSet.getInstance(); for (IFD ifd = tin.readIFD(offset); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, BaselineTagSet.getInstance().getTag(BaselineTagSet.TAG_EXIF), count++, ifd, parentEntry, tiffSeg); parent.add(ifdNode); int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { if (entry.getTagNumber() == EXIFTagSet.Interoperability.getNumber()) { readInteropIFD(tin, entry.getValueOffset(), ifdNode, entry, tiffSeg); } else if (entry.getTagNumber() == EXIFTagSet.MakerNote.getNumber()) { if (readMakerNoteIFD(tin, entry.getValueOffset(), ifdNode, entry, tiffSeg)) { break; } else { // fall through } } else { ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); } entryCount++; } } } private void readGPSIFD(TIFFInputStream tin, long offset, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = GPSTagSet.getInstance(); for (IFD ifd = tin.readIFD(offset); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, BaselineTagSet.getInstance().getTag(BaselineTagSet.TAG_GPS), count++, ifd, parentEntry, tiffSeg); parent.add(ifdNode); int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); entryCount++; } } } private void readInteropIFD(TIFFInputStream tin, long offset, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = InteroperabilityTagSet.getInstance(); for (IFD ifd = tin.readIFD(offset); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, BaselineTagSet.getInstance().getTag(BaselineTagSet.TAG_Interoperability), count++, ifd, parentEntry, tiffSeg); parent.add(ifdNode); int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); entryCount++; } } } private void readMPFIFD(TIFFInputStream tin, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = MPFTagSet.getInstance(); for (IFD ifd = tin.readIFD(tin.getFirstIFDOffset()); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, null, count++, ifd, parentEntry, tiffSeg); parent.add(ifdNode); int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { switch (entry.getTagNumber()) { case MPFTagSet.TAG_MPEntryInformation: readMPEntries(tin, entry, ifdNode, tiffSeg); break; default: ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); entryCount++; break; } } } } /** * imageCount*16 byte MP Entry Information. */ private void readMPEntries(TIFFInputStream tin, IFDEntry mpEntryInformation, TIFFDirectory parent, ArrayList tiffSeg) throws IOException { byte[] buf = (byte[]) mpEntryInformation.readData(tin); TagSet tagSet = MPEntryTagSet.getInstance(); ByteArrayImageInputStream in = new ByteArrayImageInputStream(buf); ByteOrder bo = tin.getByteOrder(); in.setByteOrder(bo); int numImages = (int) mpEntryInformation.getLength() / 16; try { for (int imageCount = 0; imageCount < numImages; imageCount++) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, tagSet.getTag(MPFTagSet.TAG_MPEntryInformation), imageCount, mpEntryInformation.getValueOffset(), 16 * imageCount, tiffSeg); parent.add(ifdNode); int imageAttr = in.readInt(); short dpif = (short) (imageAttr >>> 31); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_DependentParentImageFlag), dpif)); short dcif = (short) ((imageAttr >>> 30) & 1); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_DependentChildImageFlag), dcif)); short rif = (short) ((imageAttr >>> 29) & 1); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_RepresentativeImageFlag), rif)); short idf = (short) ((imageAttr >>> 24) & 7); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_ImageDataFormat), idf)); long mptc = (imageAttr & 0xffffffL); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_MPTypeCode), mptc)); // Read the individual image size long imageSize = in.readInt() & 0xffffffffL; ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_IndividualImageSize), imageSize)); // Read the individual data offset long imageOffset = in.readInt() & 0xffffffffL; ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_IndividualImageDataOffset), imageOffset)); imageOffsets.add(imageOffset); // Read the dependent image 1 entry number int dependentImageEntryNumber = in.readUnsignedShort(); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_DependentImage1EntryNumber), dependentImageEntryNumber)); // Read the dependent image 2 entry number dependentImageEntryNumber = in.readUnsignedShort(); ifdNode.add(new TIFFField(tagSet.getTag(MPEntryTagSet.TAG_DependentImage2EntryNumber), dependentImageEntryNumber)); } } catch (IOException e) { e.printStackTrace(); } finally { in.close(); } } private boolean readMakerNoteIFD(TIFFInputStream tin, long offset, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { // Test whether segment starts with FUJIFILM magic. try { String magic = tin.readASCII(offset, 10); if (magic.equals("FUJIFILM\u000c")) { return readFujifilmMakerNoteIFD(tin, offset, parent, parentEntry, tiffSeg); } else if (magic.equals("SONY DSC ")) { return readSonyMakerNoteIFD(tin, offset, parent, parentEntry, tiffSeg); } } catch (IOException e) { // the segment does not start with a magic. Return false. return false; } return false; } private boolean readFujifilmMakerNoteIFD(TIFFInputStream tin, long offset, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = FujifilmMakerNoteTagSet.getInstance(); try { for (IFD ifd = tin.readIFD(offset + 12); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, EXIFTagSet.MakerNote, count++, ifd, parentEntry, tiffSeg); parent.add(ifdNode); int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { // Note: FujifilmMakerNode Data pointers are offset by IFD offset entry.setIFDOffset(offset); ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); entryCount++; } } } catch (IOException e) { // the IFD is incomplete or otherwise damaged return false; } return true; } private boolean readSonyMakerNoteIFD(TIFFInputStream tin, long offset, TIFFDirectory parent, IFDEntry parentEntry, ArrayList tiffSeg) throws IOException { int count = 0; TagSet tagSet = SonyMakerNoteTagSet.getInstance(); try { for (IFD ifd = tin.readIFD(offset + 12); ifd != null; ifd = tin.readIFD(ifd.getNextOffset())) { TIFFDirectory ifdNode = new TIFFDirectory(tagSet, EXIFTagSet.MakerNote, count++, ifd, parentEntry, tiffSeg); parent.add(ifdNode); int entryCount = 0; for (IFDEntry entry : ifd.getEntries()) { ifdNode.add(new TIFFField(tagSet.getTag(entry.getTagNumber()), entry.readData(tin), entry)); entryCount++; } } } catch (IOException e) { // the IFD is incomplete or otherwise damaged return false; } return true; } /** * Gets the meta data as a Swing TreeNode structure. */ public TIFFNode getMetaDataTree() { return root; } /** * Returns the number of images that are described with EXIF. Returns -1 if * not known. */ public int getImageCount() { return root == null ? -1 : root.getChildCount(); } /** * Returns all IFDDirectories of the specified tag set for the given image. */ public ArrayList getDirectories(int image, TagSet tagSet) { ArrayList dirs = new ArrayList(); Stack stack = new Stack(); stack.push((TIFFDirectory) getMetaDataTree().getChildAt(image)); while (!stack.isEmpty()) { TIFFDirectory dir = stack.pop(); for (TIFFNode node : dir.getChildren()) { if (node instanceof TIFFDirectory) { TIFFDirectory dirNode = (TIFFDirectory) node; if (dirNode.getTagSet() == tagSet) { dirs.add(0, dirNode); // must insert first because we traverse in post-order } else { stack.push(dirNode); } } } } return dirs; } /** * Returns all thumbnails. */ public ArrayList getThumbnails(boolean suppressException) throws IOException { ArrayList thumbnails = new ArrayList(); Stack stack = new Stack(); stack.push((TIFFDirectory) getMetaDataTree()); if (stack.peek() == null) { return thumbnails; } while (!stack.isEmpty()) { TIFFDirectory dir = stack.pop(); for (TIFFNode node : dir.getChildren()) { if (node instanceof TIFFDirectory) { stack.push((TIFFDirectory) node); } else if (node instanceof TIFFField) { TIFFField field = (TIFFField) node; if (field.getTag() == BaselineTagSet.JPEGThumbnailImage) { try { thumbnails.add(0, ImageIO.read(new ByteArrayImageInputStream((byte[]) field.getData()))); // must insert first because we traverse in post-order } catch (IOException e) { if (!suppressException) { throw e; } } } } } } return thumbnails; } /** * Returns a flat hash map of the metadata. */ public HashMap getMetaDataMap() { HashMap m = new HashMap(); for (Iterator i = root.preorderIterator(); i.hasNext();) { TIFFNode node = i.next(); if (node instanceof TIFFField) { m.put(node.getTag(), (TIFFField) node); } } return m; } /** * Gets the metadata as an ImageIO structure.

Format description * replicated from http://download.java.net/media/jai-imageio/javadoc/1.1/com/sun/media/imageio/plugins/tiff/package-summary.html: *

The DTD for the native image metadata format is as follows: *

     * The DTD for the native image metadata format is as follows:
     * <!DOCTYPE "com_sun_media_imageio_plugins_tiff_image_1.0" [
     *
     *  <!ELEMENT "com_sun_media_imageio_plugins_tiff_image_1.0" (TIFFIFD)*>
     *
     *    <!ELEMENT "TIFFIFD" (TIFFField | TIFFIFD)*>
     *      <!-- An IFD (directory) containing fields -->
     *      <!ATTLIST "TIFFIFD" "tagSets" #CDATA #REQUIRED>
     *        <!-- Data type: String -->
     *      <!ATTLIST "TIFFIFD" "parentTagNumber" #CDATA #IMPLIED>
     *        <!-- The tag number of the field pointing to this IFD -->
     *        <!-- Data type: Integer -->
     *      <!ATTLIST "TIFFIFD" "parentTagName" #CDATA #IMPLIED>
     *        <!-- A mnemonic name for the field pointing to this IFD, if known
     *             -->
     *        <!-- Data type: String -->
     *
     *      <!ELEMENT "TIFFField" (TIFFBytes | TIFFAsciis |
     *        TIFFShorts | TIFFSShorts | TIFFLongs | TIFFSLongs |
     *        TIFFRationals | TIFFSRationals |
     *        TIFFFloats | TIFFDoubles | TIFFUndefined)>
     *        <!-- A field containing data -->
     *        <!ATTLIST "TIFFField" "number" #CDATA #REQUIRED>
     *          <!-- The tag number asociated with the field -->
     *          <!-- Data type: String -->
     *        <!ATTLIST "TIFFField" "name" #CDATA #IMPLIED>
     *          <!-- A mnemonic name associated with the field, if known -->
     *          <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFBytes" (TIFFByte)*>
     *          <!-- A sequence of TIFFByte nodes -->
     *
     *          <!ELEMENT "TIFFByte" EMPTY>
     *            <!-- An integral value between 0 and 255 -->
     *            <!ATTLIST "TIFFByte" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *            <!ATTLIST "TIFFByte" "description" #CDATA #IMPLIED>
     *              <!-- A description, if available -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFAsciis" (TIFFAscii)*>
     *          <!-- A sequence of TIFFAscii nodes -->
     *
     *          <!ELEMENT "TIFFAscii" EMPTY>
     *            <!-- A String value -->
     *            <!ATTLIST "TIFFAscii" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFShorts" (TIFFShort)*>
     *          <!-- A sequence of TIFFShort nodes -->
     *
     *          <!ELEMENT "TIFFShort" EMPTY>
     *            <!-- An integral value between 0 and 65535 -->
     *            <!ATTLIST "TIFFShort" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *            <!ATTLIST "TIFFShort" "description" #CDATA #IMPLIED>
     *              <!-- A description, if available -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFSShorts" (TIFFSShort)*>
     *          <!-- A sequence of TIFFSShort nodes -->
     *
     *          <!ELEMENT "TIFFSShort" EMPTY>
     *            <!-- An integral value between -32768 and 32767 -->
     *            <!ATTLIST "TIFFSShort" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *            <!ATTLIST "TIFFSShort" "description" #CDATA #IMPLIED>
     *              <!-- A description, if available -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFLongs" (TIFFLong)*>
     *          <!-- A sequence of TIFFLong nodes -->
     *
     *          <!ELEMENT "TIFFLong" EMPTY>
     *            <!-- An integral value between 0 and 4294967295 -->
     *            <!ATTLIST "TIFFLong" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *            <!ATTLIST "TIFFLong" "description" #CDATA #IMPLIED>
     *              <!-- A description, if available -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFSLongs" (TIFFSLong)*>
     *          <!-- A sequence of TIFFSLong nodes -->
     *
     *          <!ELEMENT "TIFFSLong" EMPTY>
     *            <!-- An integral value between -2147483648 and 2147482647 -->
     *            <!ATTLIST "TIFFSLong" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *            <!ATTLIST "TIFFSLong" "description" #CDATA #IMPLIED>
     *              <!-- A description, if available -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFRationals" (TIFFRational)*>
     *          <!-- A sequence of TIFFRational nodes -->
     *
     *          <!ELEMENT "TIFFRational" EMPTY>
     *            <!-- A rational value consisting of an unsigned numerator and
     *                 denominator -->
     *            <!ATTLIST "TIFFRational" "value" #CDATA #IMPLIED>
     *              <!-- The numerator and denominator, separated by a slash -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFSRationals" (TIFFSRational)*>
     *          <!-- A sequence of TIFFSRational nodes -->
     *
     *          <!ELEMENT "TIFFSRational" EMPTY>
     *            <!-- A rational value consisting of a signed numerator and
     *                 denominator -->
     *            <!ATTLIST "TIFFSRational" "value" #CDATA #IMPLIED>
     *              <!-- The numerator and denominator, separated by a slash -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFFloats" (TIFFFloat)*>
     *          <!-- A sequence of TIFFFloat nodes -->
     *
     *          <!ELEMENT "TIFFFloat" EMPTY>
     *            <!-- A single-precision floating-point value -->
     *            <!ATTLIST "TIFFFloat" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFDoubles" (TIFFDouble)*>
     *          <!-- A sequence of TIFFDouble nodes -->
     *
     *          <!ELEMENT "TIFFDouble" EMPTY>
     *            <!-- A double-precision floating-point value -->
     *            <!ATTLIST "TIFFDouble" "value" #CDATA #IMPLIED>
     *              <!-- The value -->
     *              <!-- Data type: String -->
     *
     *        <!ELEMENT "TIFFUndefined" EMPTY>
     *          <!-- Uninterpreted byte data -->
     *          <!ATTLIST "TIFFUndefined" "value" #CDATA #IMPLIED>
     *            <!-- A list of comma-separated byte values -->
     *            <!-- Data type: String -->
     *]>
     * 
*/ public IIOMetadataNode getIIOMetadataTree(String formatName, int imageIndex) { if (formatName != null && !formatName.equals("com_sun_media_imageio_plugins_tiff_image_1.0")) { throw new IllegalArgumentException("Unsupported formatName:" + formatName); } IIOMetadataNode iioRoot = new IIOMetadataNode("com_sun_media_imageio_plugins_tiff_image_1.0"); TIFFNode imageRoot = root.getChildAt(imageIndex); for (TIFFNode node : imageRoot.getChildren()) { addIIOMetadataNode(iioRoot, node); } return iioRoot; } private void addIIOMetadataNode(IIOMetadataNode iioParent, TIFFNode node) { if (node instanceof TIFFDirectory) { TIFFDirectory dir = (TIFFDirectory) node; IIOMetadataNode iioNode = new IIOMetadataNode("TIFFIFD"); TagSet tagSet = dir.getTagSet(); iioNode.setAttribute("tagSets", dir == null ? "" : tagSet.getName()); if (dir.getTag() != null) { iioNode.setAttribute("parentTagNumber", Integer.toString(dir.getTagNumber())); iioNode.setAttribute("parentTagName", dir.getTag().getName()); } iioParent.appendChild(iioNode); for (int i = 0; i < node.getChildCount(); i++) { addIIOMetadataNode(iioNode, node.getChildAt(i)); } } else if (node instanceof TIFFField) { TIFFField field = (TIFFField) node; IIOMetadataNode iioNode = new IIOMetadataNode("TIFFField"); iioNode.setAttribute("number", Integer.toString(field.getTagNumber())); if (field.getTagName() != null && !field.getTagName().equals("unknown")) { iioNode.setAttribute("name", field.getTagName()); } IIOMetadataNode iioSequence = null; String description = field.getDescription(); switch (field.getType()) { case ASCII: { iioSequence = new IIOMetadataNode("TIFFAsciis"); IIOMetadataNode iioValue = new IIOMetadataNode("TIFFAscii"); iioValue.setAttribute("value", (String) field.getData()); iioSequence.appendChild(iioValue); break; } case BYTE: { iioSequence = new IIOMetadataNode("TIFFBytes"); short[] value = (field.getData() instanceof Short) ? new short[]{(Short) field.getData()} : (short[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFByte"); iioValue.setAttribute("value", Short.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case SBYTE: { iioSequence = new IIOMetadataNode("TIFFSBytes"); byte[] value = (field.getData() instanceof Byte) ? new byte[]{(Byte) field.getData()} : (byte[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFSByte"); iioValue.setAttribute("value", Byte.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case DOUBLE: { iioSequence = new IIOMetadataNode("TIFFDoubles"); double[] value = (field.getData() instanceof Double) ? new double[]{(Double) field.getData()} : (double[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFDouble"); iioValue.setAttribute("value", Double.toString(value[i])); iioSequence.appendChild(iioValue); } break; } case FLOAT: { iioSequence = new IIOMetadataNode("TIFFFloats"); float[] value = (field.getData() instanceof Float) ? new float[]{(Float) field.getData()} : (float[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFFloat"); iioValue.setAttribute("value", Double.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case LONG: { iioSequence = new IIOMetadataNode("TIFFLongs"); long[] value = (field.getData() instanceof Long) ? new long[]{(Long) field.getData()} : (long[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFLong"); iioValue.setAttribute("value", Long.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case SLONG: { iioSequence = new IIOMetadataNode("TIFFSLongs"); int[] value = (field.getData() instanceof Integer) ? new int[]{(Integer) field.getData()} : (int[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFSLong"); iioValue.setAttribute("value", Integer.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case RATIONAL: { iioSequence = new IIOMetadataNode("TIFFRationals"); Rational[] value = (field.getData() instanceof Rational) ? new Rational[]{(Rational) field.getData()} : (Rational[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFRational"); iioValue.setAttribute("value", Long.toString(value[i].getNumerator()) + "/" + Long.toString(value[i].getDenominator())); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case SRATIONAL: { iioSequence = new IIOMetadataNode("TIFFSRationals"); Rational[] value = (field.getData() instanceof Rational) ? new Rational[]{(Rational) field.getData()} : (Rational[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFSRational"); iioValue.setAttribute("value", Long.toString(value[i].getNumerator()) + "/" + Long.toString(value[i].getDenominator())); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case SHORT: { iioSequence = new IIOMetadataNode("TIFFShorts"); int[] value = (field.getData() instanceof Integer) ? new int[]{(Integer) field.getData()} : (int[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFShort"); iioValue.setAttribute("value", Integer.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case SSHORT: { iioSequence = new IIOMetadataNode("TIFFSShorts"); short[] value = (field.getData() instanceof Short) ? new short[]{(Short) field.getData()} : (short[]) field.getData(); for (int i = 0; i < value.length; i++) { IIOMetadataNode iioValue = new IIOMetadataNode("TIFFSShort"); iioValue.setAttribute("value", Short.toString(value[i])); iioSequence.appendChild(iioValue); if (i == 0 && description != null) { iioValue.setAttribute("description", description); } } break; } case UNDEFINED: { iioSequence = new IIOMetadataNode("TIFFUndefined"); byte[] value = (field.getData() instanceof Byte) ? new byte[]{(Byte) field.getData()} : (byte[]) field.getData(); StringBuilder iioValue = new StringBuilder(); for (int i = 0; i < value.length; i++) { if (i != 0) { iioValue.append(','); } iioValue.append(Integer.toString(value[i] & 0xff)); } iioSequence.setAttribute("value", iioValue.toString()); if (description != null) { iioSequence.setAttribute("description", description); } break; } } if (iioSequence != null) { iioNode.appendChild(iioSequence); } iioParent.appendChild(iioNode); } } public IIOMetadata getIIOMetadata(int i) { throw new UnsupportedOperationException("Not yet implemented"); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy