Back to project page PictureMap.
The source code is released under:
GNU General Public License
If you think the Android project PictureMap listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * EXIFExtractor.java/* w w w. j a va 2s . c o m*/ * * This class based upon code from Jhead, a C program for extracting and * manipulating the Exif data within files written by Matthias Wandel. * http://www.sentex.net/~mwandel/jhead/ * * Jhead is public domain software - that is, you can do whatever you want * with it, and include it software that is licensed under the GNU or the * BSD license, or whatever other licence you choose, including proprietary * closed source licenses. Similarly, I release this Java version under the * same license, though I do ask that you leave this header in tact. * * If you make modifications to this code that you think would benefit the * wider community, please send me a copy and I'll post it on my site. Unlike * Jhead, this code (as it stands) only supports reading of Exif data - no * manipulation, and no thumbnail stuff. * * If you make use of this code, I'd appreciate hearing about it. * drew.noakes@drewnoakes.com * Latest version of this software kept at * http://drewnoakes.com/ * * Created on 28 April 2002, 23:54 * Modified 04 Aug 2002 * - Renamed constants to be inline with changes to ExifTagValues interface * - Substituted usage of JDK 1.4 features (java.nio package) * Modified 29 Oct 2002 (v1.2) * - Proper traversing of Exif file structure and complete refactor & tidy of * the codebase (a few unnoticed bugs removed) * - Reads makernote data for 6 families of camera (5 makes) * - Tags now stored in directories... use the IFD_* constants to refer to the * image file directory you require (Exif, Interop, GPS and Makernote*) -- * this avoids collisions where two tags share the same code * - Takes componentCount of unknown tags into account * - Now understands GPS tags (thanks to Colin Briton for his help with this) * - Some other bug fixes, pointed out by users around the world. Thanks! * Modified 27 Nov 2002 (v2.0) * - Renamed to ExifReader * - Moved to new package com.drewChanged.metadata.exif * Modified since, however changes have not been logged. See release notes for * library-wide modifications. */ package com.drewChanged.metadata.exif; import com.drewChanged.imaging.jpeg.JpegProcessingException; import com.drewChanged.imaging.jpeg.JpegSegmentData; import com.drewChanged.imaging.jpeg.JpegSegmentReader; import com.drewChanged.lang.Rational; import com.drewChanged.metadata.Directory; import com.drewChanged.metadata.Metadata; import com.drewChanged.metadata.MetadataReader; //import java.io.File; import java.io.InputStream; //import java.util.HashMap; import java.util.Hashtable; /** * Extracts Exif data from a JPEG header segment, providing information about the * camera/scanner/capture device (if available). Information is encapsulated in * an <code>Metadata</code> object. * @author Drew Noakes http://drewnoakes.com */ @SuppressWarnings("unchecked") public class ExifReader implements MetadataReader { /** * The JPEG segment as an array of bytes. */ private final byte[] _data; /** * Represents the native byte ordering used in the JPEG segment. If true, * then we're using Motorolla ordering (Big endian), else we're using Intel * ordering (Little endian). */ private boolean _isMotorollaByteOrder; /** * Bean instance to store information about the image and camera/scanner/capture * device. */ private Metadata _metadata; /** * The number of bytes used per format descriptor. */ private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8}; /** * The number of formats known. */ private static final int MAX_FORMAT_CODE = 12; // Format types // Note: Cannot use the DataFormat enumeration in the case statement that uses these tags. // Is there a better way? private static final int FMT_BYTE = 1; private static final int FMT_STRING = 2; private static final int FMT_USHORT = 3; private static final int FMT_ULONG = 4; private static final int FMT_URATIONAL = 5; private static final int FMT_SBYTE = 6; private static final int FMT_UNDEFINED = 7; private static final int FMT_SSHORT = 8; private static final int FMT_SLONG = 9; private static final int FMT_SRATIONAL = 10; private static final int FMT_SINGLE = 11; private static final int FMT_DOUBLE = 12; public static final int TAG_EXIF_OFFSET = 0x8769; public static final int TAG_INTEROP_OFFSET = 0xA005; public static final int TAG_GPS_INFO_OFFSET = 0x8825; public static final int TAG_MAKER_NOTE = 0x927C; public static final int TIFF_HEADER_START_OFFSET = 6; /** * Creates an ExifReader for a JpegSegmentData object. * @param segmentData */ public ExifReader(JpegSegmentData segmentData) { this(segmentData.getSegment(JpegSegmentReader.SEGMENT_APP1)); } /** * Creates an ExifReader for a Jpeg file. * @param file * @throws JpegProcessingException */ // public ExifReader(File file) throws JpegProcessingException // { // this(new JpegSegmentReader(file).readSegment(JpegSegmentReader.SEGMENT_APP1)); // } /** * Creates an ExifReader for a Jpeg stream. * @param is JPEG stream. Stream will be closed. */ public ExifReader(InputStream is) throws JpegProcessingException { this(new JpegSegmentReader(is).readSegment(JpegSegmentReader.SEGMENT_APP1)); } /** * Creates an ExifReader for the given JPEG header segment. */ public ExifReader(byte[] data) { _data = data; } /** * Performs the Exif data extraction, returning a new instance of <code>Metadata</code>. */ public Metadata extract() { return extract(new Metadata()); } /** * Performs the Exif data extraction, adding found values to the specified * instance of <code>Metadata</code>. */ public Metadata extract(Metadata metadata) { _metadata = metadata; // if (_data==null) System.out.println("Bloody _data is null!"); if (_data==null) return _metadata; // once we know there's some data, create the directory and start working on it ExifDirectory directory = (ExifDirectory)_metadata.getDirectory(ExifDirectory.class); // check for the header length if (_data.length<=14) { directory.addError("Exif data segment must contain at least 14 bytes"); // System.out.println("Exif data segment must contain at least 14 bytes"); return _metadata; } // check for the header preamble if (!"Exif\0\0".equals(new String(_data, 0, 6))) { directory.addError("Exif data segment doesn't begin with 'Exif'"); // System.out.println("Exif data segment doesn't begin with 'Exif'"); return _metadata; } // this should be either "MM" or "II" String byteOrderIdentifier = new String(_data, 6, 2); if (!setByteOrder(byteOrderIdentifier)) { directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier); // System.out.println("Exif data segment doesn't begin with 'Exif'"); return _metadata; } // Check the next two values for correctness. if (get16Bits(8)!=0x2a) { directory.addError("Invalid Exif start - should have 0x2A at offset 8 in Exif header"); // System.out.println("Invalid Exif start - should have 0x2A at offset 8 in Exif header"); return _metadata; } int firstDirectoryOffset = get32Bits(10) + TIFF_HEADER_START_OFFSET; // System.out.println("PP 1"); // David Ekholm sent an digital camera image that has this problem if (firstDirectoryOffset>=_data.length - 1) { directory.addError("First exif directory offset is beyond end of Exif data segment"); // System.out.println("First exif directory offset is beyond end of Exif data segment"); // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case firstDirectoryOffset = 14; } // System.out.println("PP 2"); Hashtable processedDirectoryOffsets = new Hashtable(); // 0th IFD (we merge with Exif IFD) processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, TIFF_HEADER_START_OFFSET); // System.out.println("PP 3"); // after the extraction process, if we have the correct tags, we may be able to store thumbnail information storeThumbnailBytes(directory, TIFF_HEADER_START_OFFSET); // System.out.println("PP 4"); return _metadata; } private void storeThumbnailBytes(ExifDirectory exifDirectory, int tiffHeaderOffset) { if (!exifDirectory.containsTag(ExifDirectory.TAG_COMPRESSION)) return; if (!exifDirectory.containsTag(ExifDirectory.TAG_THUMBNAIL_LENGTH) || !exifDirectory.containsTag(ExifDirectory.TAG_THUMBNAIL_OFFSET)) return; try { int offset = exifDirectory.getInt(ExifDirectory.TAG_THUMBNAIL_OFFSET); int length = exifDirectory.getInt(ExifDirectory.TAG_THUMBNAIL_LENGTH); byte[] result = new byte[length]; for (int i = 0; i<result.length; i++) { result[i] = _data[tiffHeaderOffset + offset + i]; } exifDirectory.setByteArray(ExifDirectory.TAG_THUMBNAIL_DATA, result); } catch (Throwable e) { exifDirectory.addError("Unable to extract thumbnail: " + e.getMessage()); // System.out.println("Unable to extract thumbnail: " + e.getMessage()); } } private boolean setByteOrder(String byteOrderIdentifier) { if ("MM".equals(byteOrderIdentifier)) { _isMotorollaByteOrder = true; } else if ("II".equals(byteOrderIdentifier)) { _isMotorollaByteOrder = false; } else { return false; } return true; } /** * Process one of the nested Tiff IFD directories. * 2 bytes: number of tags * for each tag * 2 bytes: tag type * 2 bytes: format code * 4 bytes: component count */ private void processDirectory(Directory directory, Hashtable processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset) { // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist if (processedDirectoryOffsets.containsKey(new Integer(dirStartOffset))) return; // remember that we've visited this directory so that we don't visit it again later processedDirectoryOffsets.put(new Integer(dirStartOffset), "processed"); if (dirStartOffset>=_data.length || dirStartOffset<0) { directory.addError("Ignored directory marked to start outside data segement"); // System.out.println("Ignored directory marked to start outside data segement"); return; } if (!isDirectoryLengthValid(dirStartOffset, tiffHeaderOffset)) { directory.addError("Illegally sized directory"); // System.out.println("Illegally sized directory"); return; } // First two bytes in the IFD are the number of tags in this directory int dirTagCount = get16Bits(dirStartOffset); // Handle each tag in this directory for (int tagNumber = 0; tagNumber<dirTagCount; tagNumber++) { final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber); // 2 bytes for the tag type final int tagType = get16Bits(tagOffset); // 2 bytes for the format code final int formatCode = get16Bits(tagOffset + 2); if (formatCode<1 || formatCode>MAX_FORMAT_CODE) { directory.addError("Invalid format code: " + formatCode); // System.out.println("Invalid format code: " + formatCode); continue; } // 4 bytes dictate the number of components in this tag's data final int componentCount = get32Bits(tagOffset + 4); if (componentCount<0) { directory.addError("Negative component count in EXIF"); // System.out.println("Negative component count in EXIF"); continue; } // each component may have more than one byte... calculate the total number of bytes final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode]; final int tagValueOffset = calculateTagValueOffset(byteCount, tagOffset, tiffHeaderOffset); if (tagValueOffset<0 || tagValueOffset > _data.length) { directory.addError("Illegal pointer offset value in EXIF"); // System.out.println("Illegal pointer offset value in EXIF"); continue; } // Check that this tag isn't going to allocate outside the bounds of the data array. // This addresses an uncommon OutOfMemoryError. if (byteCount < 0 || tagValueOffset + byteCount > _data.length) { directory.addError("Illegal number of bytes: " + byteCount); // System.out.println("Illegal number of bytes: " + byteCount); continue; } // Calculate the value as an offset for cases where the tag represents directory final int subdirOffset = tiffHeaderOffset + get32Bits(tagValueOffset); switch (tagType) { case TAG_EXIF_OFFSET: processDirectory(_metadata.getDirectory(ExifDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset); continue; case TAG_INTEROP_OFFSET: processDirectory(_metadata.getDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset); continue; case TAG_GPS_INFO_OFFSET: processDirectory(_metadata.getDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset); continue; default: processTag(directory, tagType, tagValueOffset, componentCount, formatCode); break; } } // at the end of each IFD is an optional link to the next IFD final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount); int nextDirectoryOffset = get32Bits(finalTagOffset); if (nextDirectoryOffset!=0) { nextDirectoryOffset += tiffHeaderOffset; if (nextDirectoryOffset>=_data.length) { // Last 4 bytes of IFD reference another IFD with an address that is out of bounds // Note this could have been caused by jhead 1.3 cropping too much return; } else if (nextDirectoryOffset < dirStartOffset) { // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory return; } // the next directory is of same type as this one processDirectory(directory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset); } } private boolean isDirectoryLengthValid(int dirStartOffset, int tiffHeaderOffset) { int dirTagCount = get16Bits(dirStartOffset); int dirLength = (2 + (12 * dirTagCount) + 4); if (dirLength + dirStartOffset + tiffHeaderOffset>=_data.length) { // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier might trigger this return false; } return true; } private void processTag(Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode) { // Directory simply stores raw values // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions // System.out.println("Inside processTag"); switch (formatCode) { case FMT_UNDEFINED: // this includes exif user comments final byte[] tagBytes = new byte[componentCount]; final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode]; for (int i=0; i<byteCount; i++) tagBytes[i] = _data[tagValueOffset + i]; directory.setByteArray(tagType, tagBytes); break; case FMT_STRING: directory.setString(tagType, readString(tagValueOffset, componentCount)); break; case FMT_SRATIONAL: case FMT_URATIONAL: if (componentCount==1) { Rational rational = new Rational(get32Bits(tagValueOffset), get32Bits(tagValueOffset + 4)); directory.setRational(tagType, rational); } else { Rational[] rationals = new Rational[componentCount]; for (int i = 0; i<componentCount; i++) rationals[i] = new Rational(get32Bits(tagValueOffset + (8 * i)), get32Bits(tagValueOffset + 4 + (8 * i))); directory.setRationalArray(tagType, rationals); } break; case FMT_SBYTE: case FMT_BYTE: if (componentCount==1) { // this may need to be a byte, but I think casting to int is fine int b = _data[tagValueOffset]; directory.setInt(tagType, b); } else { int[] bytes = new int[componentCount]; for (int i = 0; i<componentCount; i++) bytes[i] = _data[tagValueOffset + i]; directory.setIntArray(tagType, bytes); } break; case FMT_SINGLE: case FMT_DOUBLE: if (componentCount==1) { int i = _data[tagValueOffset]; directory.setInt(tagType, i); } else { int[] ints = new int[componentCount]; for (int i = 0; i<componentCount; i++) ints[i] = _data[tagValueOffset + i]; directory.setIntArray(tagType, ints); } break; case FMT_USHORT: case FMT_SSHORT: if (componentCount==1) { int i = get16Bits(tagValueOffset); directory.setInt(tagType, i); } else { int[] ints = new int[componentCount]; for (int i = 0; i<componentCount; i++) ints[i] = get16Bits(tagValueOffset + (i * 2)); directory.setIntArray(tagType, ints); } break; case FMT_SLONG: case FMT_ULONG: if (componentCount==1) { int i = get32Bits(tagValueOffset); directory.setInt(tagType, i); } else { int[] ints = new int[componentCount]; for (int i = 0; i<componentCount; i++) ints[i] = get32Bits(tagValueOffset + (i * 4)); directory.setIntArray(tagType, ints); } break; default: directory.addError("Unknown format code " + formatCode + " for tag " + tagType); // System.out.println("Unknown format code " + formatCode + " for tag " + tagType); } } private int calculateTagValueOffset(int byteCount, int dirEntryOffset, int tiffHeaderOffset) { if (byteCount>4) { // If its bigger than 4 bytes, the dir entry contains an offset. // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an // offset relative to the start of the makernote itself, not the TIFF segment. final int offsetVal = get32Bits(dirEntryOffset + 8); if (offsetVal + byteCount>_data.length) { // Bogus pointer offset and / or bytecount value return -1; // signal error } return tiffHeaderOffset + offsetVal; } else { // 4 bytes or less and value is in the dir entry itself return dirEntryOffset + 8; } } /** * Creates a String from the _data buffer starting at the specified offset, * and ending where byte=='\0' or where length==maxLength. */ private String readString(int offset, int maxLength) { int length = 0; while ((offset + length)<_data.length && _data[offset + length]!='\0' && length<maxLength) length++; return new String(_data, offset, length); } /** * Determine the offset at which a given InteropArray entry begins within the specified IFD. * @param dirStartOffset the offset at which the IFD starts * @param entryNumber the zero-based entry number */ private int calculateTagOffset(int dirStartOffset, int entryNumber) { // add 2 bytes for the tag count // each entry is 12 bytes, so we skip 12 * the number seen so far return dirStartOffset + 2 + (12 * entryNumber); } /** * Get a 16 bit value from file's native byte order. Between 0x0000 and 0xFFFF. */ private int get16Bits(int offset) { if (offset<0 || offset+2>_data.length) throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index " + offset + " where max index is " + (_data.length - 1) + ")"); if (_isMotorollaByteOrder) { // Motorola - MSB first return (_data[offset] << 8 & 0xFF00) | (_data[offset + 1] & 0xFF); } else { // Intel ordering - LSB first return (_data[offset + 1] << 8 & 0xFF00) | (_data[offset] & 0xFF); } } /** * Get a 32 bit value from file's native byte order. */ private int get32Bits(int offset) { if (offset<0 || offset+4>_data.length) throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index " + offset + " where max index is " + (_data.length - 1) + ")"); if (_isMotorollaByteOrder) { // Motorola - MSB first return (_data[offset] << 24 & 0xFF000000) | (_data[offset + 1] << 16 & 0xFF0000) | (_data[offset + 2] << 8 & 0xFF00) | (_data[offset + 3] & 0xFF); } else { // Intel ordering - LSB first return (_data[offset + 3] << 24 & 0xFF000000) | (_data[offset + 2] << 16 & 0xFF0000) | (_data[offset + 1] << 8 & 0xFF00) | (_data[offset] & 0xFF); } } }