From c274ded801f745d6318186958107622e7a4fef33 Mon Sep 17 00:00:00 2001 From: Ruben Brunk Date: Mon, 11 Mar 2013 19:00:12 -0700 Subject: Exif parser modifications. Bug: 8018327 Change-Id: I66a2ec309f9807ac255bbf29d8f5f26de60e89b8 --- .../src/com/android/gallery3d/exif/ExifData.java | 399 ++-- .../com/android/gallery3d/exif/ExifInterface.java | 2403 ++++++++++++++++++++ .../com/android/gallery3d/exif/ExifModifier.java | 70 +- .../android/gallery3d/exif/ExifOutputStream.java | 256 ++- .../src/com/android/gallery3d/exif/ExifParser.java | 436 ++-- .../src/com/android/gallery3d/exif/ExifReader.java | 24 +- .../src/com/android/gallery3d/exif/ExifTag.java | 1798 ++++++--------- .../src/com/android/gallery3d/exif/IfdData.java | 59 +- .../src/com/android/gallery3d/exif/IfdId.java | 2 + .../gallery3d/exif/OrderedDataOutputStream.java | 16 +- .../src/com/android/gallery3d/exif/Rational.java | 58 +- 11 files changed, 3838 insertions(+), 1683 deletions(-) create mode 100644 gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java (limited to 'gallerycommon/src/com/android/gallery3d') diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java index adaceb519..8422382bb 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java @@ -19,91 +19,67 @@ package com.android.gallery3d.exif; import android.util.Log; import java.io.UnsupportedEncodingException; -import java.lang.reflect.Array; import java.nio.ByteOrder; -import java.text.DateFormat; -import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; -import java.util.TimeZone; +import java.util.List; /** - * This class stores the EXIF header in IFDs according to the JPEG specification. - * It is the result produced by {@link ExifReader}. - * @see ExifReader - * @see IfdData + * This class stores the EXIF header in IFDs according to the JPEG + * specification. It is the result produced by {@link ExifReader}. + * + * @see ExifReader + * @see IfdData */ -public class ExifData { +class ExifData { private static final String TAG = "ExifData"; - private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; - private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"; - private static final byte[] USER_COMMENT_ASCII = { - 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00}; + 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 + }; private static final byte[] USER_COMMENT_JIS = { - 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00}; + 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 + }; private static final byte[] USER_COMMENT_UNICODE = { - 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00}; - private static final byte[] USER_COMMENT_UNDEFINED = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; - - private final DateFormat mDateTimeStampFormat = - new SimpleDateFormat(DATETIME_FORMAT_STR); - private final DateFormat mGPSDateStampFormat = - new SimpleDateFormat(GPS_DATE_FORMAT_STR); - private final Calendar mGPSTimeStampCalendar = Calendar.getInstance( - TimeZone.getTimeZone("UTC")); + 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 + }; private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; private byte[] mThumbnail; private ArrayList mStripBytes = new ArrayList(); private final ByteOrder mByteOrder; - public ExifData(ByteOrder order) { + ExifData(ByteOrder order) { mByteOrder = order; - mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - IfdData getIfdData(int ifdId) { - return mIfdDatas[ifdId]; } /** - * Adds IFD data. If IFD data of the same type already exists, - * it will be replaced by the new data. - */ - void addIfdData(IfdData data) { - mIfdDatas[data.getId()] = data; - } - - /** - * Gets the compressed thumbnail. Returns null if there is no compressed thumbnail. + * Gets the compressed thumbnail. Returns null if there is no compressed + * thumbnail. * * @see #hasCompressedThumbnail() */ - public byte[] getCompressedThumbnail() { + protected byte[] getCompressedThumbnail() { return mThumbnail; } /** * Sets the compressed thumbnail. */ - public void setCompressedThumbnail(byte[] thumbnail) { + protected void setCompressedThumbnail(byte[] thumbnail) { mThumbnail = thumbnail; } /** * Returns true it this header contains a compressed thumbnail. */ - public boolean hasCompressedThumbnail() { + protected boolean hasCompressedThumbnail() { return mThumbnail != null; } /** * Adds an uncompressed strip. */ - public void setStripBytes(int index, byte[] strip) { + protected void setStripBytes(int index, byte[] strip) { if (index < mStripBytes.size()) { mStripBytes.set(index, strip); } else { @@ -117,135 +93,57 @@ public class ExifData { /** * Gets the strip count. */ - public int getStripCount() { + protected int getStripCount() { return mStripBytes.size(); } /** * Gets the strip at the specified index. + * * @exceptions #IndexOutOfBoundException */ - public byte[] getStrip(int index) { + protected byte[] getStrip(int index) { return mStripBytes.get(index); } /** - * Gets the byte order. + * Returns true if this header contains uncompressed strip. */ - public ByteOrder getByteOrder() { - return mByteOrder; + protected boolean hasUncompressedStrip() { + return mStripBytes.size() != 0; } /** - * Returns true if this header contains uncompressed strip of thumbnail. + * Gets the byte order. */ - public boolean hasUncompressedStrip() { - return mStripBytes.size() != 0; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof ExifData) { - ExifData data = (ExifData) obj; - if (data.mByteOrder != mByteOrder - || !Arrays.equals(data.mThumbnail, mThumbnail) - || data.mStripBytes.size() != mStripBytes.size()) return false; - - for (int i = 0; i < mStripBytes.size(); i++) { - if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) return false; - } - - for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { - IfdData ifd1 = data.getIfdData(i); - IfdData ifd2 = getIfdData(i); - if ((ifd1 != ifd2) && (ifd1 != null && !ifd1.equals(ifd2))) return false; - } - return true; - } - return false; + protected ByteOrder getByteOrder() { + return mByteOrder; } /** - * A convenient method to adds tags {@link ExifTag#TAG_GPS_LATITUDE}, - * {@link ExifTag#TAG_GPS_LONGITUDE}, {@link ExifTag#TAG_GPS_LATITUDE_REF} and - * {@link ExifTag#TAG_GPS_LONGITUDE_REF} at once with the - * given latitude and longitude. + * Returns the {@link IfdData} object corresponding to a given IFD if it + * exists or null. */ - public void addGpsTags(double latitude, double longitude) { - IfdData gpsIfd = getIfdData(IfdId.TYPE_IFD_GPS); - if (gpsIfd == null) { - gpsIfd = new IfdData(IfdId.TYPE_IFD_GPS); - addIfdData(gpsIfd); + protected IfdData getIfdData(int ifdId) { + if (ExifTag.isValidIfd(ifdId)) { + return mIfdDatas[ifdId]; } - ExifTag latTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE, ExifTag.TYPE_RATIONAL, - 3, IfdId.TYPE_IFD_GPS); - ExifTag longTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE, ExifTag.TYPE_RATIONAL, - 3, IfdId.TYPE_IFD_GPS); - ExifTag latRefTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE_REF, - ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS); - ExifTag longRefTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE_REF, - ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS); - latTag.setValue(toExifLatLong(latitude)); - longTag.setValue(toExifLatLong(longitude)); - latRefTag.setValue(latitude >= 0 - ? ExifTag.GpsLatitudeRef.NORTH - : ExifTag.GpsLatitudeRef.SOUTH); - longRefTag.setValue(longitude >= 0 - ? ExifTag.GpsLongitudeRef.EAST - : ExifTag.GpsLongitudeRef.WEST); - gpsIfd.setTag(latTag); - gpsIfd.setTag(longTag); - gpsIfd.setTag(latRefTag); - gpsIfd.setTag(longRefTag); + return null; } /** - * A convenient method to add date or time related tags ( - * {@link ExifTag#TAG_DATE_TIME_DIGITIZED}, {@link ExifTag#TAG_DATE_TIME_ORIGINAL}, - * and {@link ExifTag#TAG_DATE_TIME}) with the given time stamp value. - * + * Adds IFD data. If IFD data of the same type already exists, it will be + * replaced by the new data. */ - public void addDateTimeStampTag(short tagId, long timestamp, TimeZone timezone) { - if (tagId == ExifTag.TAG_DATE_TIME || - tagId == ExifTag.TAG_DATE_TIME_DIGITIZED || - tagId == ExifTag.TAG_DATE_TIME_ORIGINAL) { - mDateTimeStampFormat.setTimeZone(timezone); - addTag(tagId).setValue(mDateTimeStampFormat.format(timestamp)); - } else { - throw new IllegalArgumentException( - String.format("Tag %04x is not a supported date or time stamp tag", tagId)); - } + protected void addIfdData(IfdData data) { + mIfdDatas[data.getId()] = data; } /** - * A convenient method to add both {@link ExifTag#TAG_GPS_DATE_STAMP} - * and {@link ExifTag#TAG_GPS_TIME_STAMP}). - * Note that UTC timezone will be used as specified in the EXIF standard. + * Returns the {@link IfdData} object corresponding to a given IFD or + * generates one if none exist. */ - public void addGpsDateTimeStampTag(long timestamp) { - addTag(ExifTag.TAG_GPS_DATE_STAMP).setValue(mGPSDateStampFormat.format(timestamp)); - - mGPSTimeStampCalendar.setTimeInMillis(timestamp); - addTag(ExifTag.TAG_GPS_TIME_STAMP). - setValue(new Rational[] { - new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1), - new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1), - new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)}); - } - - private static Rational[] toExifLatLong(double value) { - // convert to the format dd/1 mm/1 ssss/100 - value = Math.abs(value); - int degrees = (int) value; - value = (value - degrees) * 60; - int minutes = (int) value; - value = (value - minutes) * 6000; - int seconds = (int) value; - return new Rational[] { - new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)}; - } - - private IfdData getOrCreateIfdData(int ifdId) { + protected IfdData getOrCreateIfdData(int ifdId) { IfdData ifdData = mIfdDatas[ifdId]; if (ifdData == null) { ifdData = new IfdData(ifdId); @@ -255,98 +153,78 @@ public class ExifData { } /** - * Gets the tag with the given tag ID. Returns null if the tag does not exist. For tags - * related to interoperability or thumbnail, call {@link #getInteroperabilityTag(short)} and - * {@link #getThumbnailTag(short)} respectively. - */ - public ExifTag getTag(short tagId) { - int ifdId = ExifTag.getIfdIdFromTagId(tagId); - IfdData ifdData = mIfdDatas[ifdId]; - return (ifdData == null) ? null : ifdData.getTag(tagId); - } - - /** - * Gets the thumbnail-related tag with the given tag ID. - */ - public ExifTag getThumbnailTag(short tagId) { - IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_1]; - return (ifdData == null) ? null : ifdData.getTag(tagId); - } - - /** - * Gets the interoperability-related tag with the given tag ID. + * Returns the tag with a given TID in the given IFD if the tag exists. + * Otherwise returns null. */ - public ExifTag getInteroperabilityTag(short tagId) { - IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY]; - return (ifdData == null) ? null : ifdData.getTag(tagId); + protected ExifTag getTag(short tag, int ifd) { + IfdData ifdData = mIfdDatas[ifd]; + return (ifdData == null) ? null : ifdData.getTag(tag); } /** - * Adds a tag with the given tag ID. If the tag of the given ID already exists, - * the original tag will be replaced. For tags - * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or - * {@link #addThumbnailTag(short)} respectively. - * @exception IllegalArgumentException if the tag ID is invalid. + * Adds the given ExifTag to its default IFD and returns an existing ExifTag + * with the same TID or null if none exist. */ - public ExifTag addTag(short tagId) { - int ifdId = ExifTag.getIfdIdFromTagId(tagId); - IfdData ifdData = getOrCreateIfdData(ifdId); - ExifTag tag = ExifTag.buildTag(tagId); - ifdData.setTag(tag); - return tag; + protected ExifTag addTag(ExifTag tag) { + if (tag != null) { + int ifd = tag.getIfd(); + return addTag(tag, ifd); + } + return null; } /** - * Adds the given ExifTag to its corresponding IFD. + * Adds the given ExifTag to the given IFD and returns an existing ExifTag + * with the same TID or null if none exist. */ - public void addTag(ExifTag tag) { - IfdData ifdData = getOrCreateIfdData(tag.getIfd()); - ifdData.setTag(tag); + protected ExifTag addTag(ExifTag tag, int ifdId) { + if (tag != null && ExifTag.isValidIfd(ifdId)) { + IfdData ifdData = getOrCreateIfdData(ifdId); + return ifdData.setTag(tag); + } + return null; } - /** - * Adds a thumbnail-related tag with the given tag ID. If the tag of the given ID - * already exists, the original tag will be replaced. - * @exception IllegalArgumentException if the tag ID is invalid. - */ - public ExifTag addThumbnailTag(short tagId) { - IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1); - ExifTag tag = ExifTag.buildThumbnailTag(tagId); - ifdData.setTag(tag); - return tag; + protected void clearThumbnailAndStrips() { + mThumbnail = null; + mStripBytes.clear(); } /** - * Adds an interoperability-related tag with the given tag ID. If the tag of the given ID - * already exists, the original tag will be replaced. - * @exception IllegalArgumentException if the tag ID is invalid. + * Removes the thumbnail and its related tags. IFD1 will be removed. */ - public ExifTag addInteroperabilityTag(short tagId) { - IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); - ExifTag tag = ExifTag.buildInteroperabilityTag(tagId); - ifdData.setTag(tag); - return tag; + protected void removeThumbnailData() { + clearThumbnailAndStrips(); + mIfdDatas[IfdId.TYPE_IFD_1] = null; } /** - * Removes the thumbnail and its related tags. IFD1 will be removed. + * Removes the tag with a given TID and IFD. */ - public void removeThumbnailData() { - mThumbnail = null; - mStripBytes.clear(); - mIfdDatas[IfdId.TYPE_IFD_1] = null; + protected void removeTag(short tagId, int ifdId) { + IfdData ifdData = mIfdDatas[ifdId]; + if (ifdData == null) { + return; + } + ifdData.removeTag(tagId); } /** - * Decodes the user comment tag into string as specified in the EXIF standard. - * Returns null if decoding failed. + * Decodes the user comment tag into string as specified in the EXIF + * standard. Returns null if decoding failed. */ - public String decodeUserComment() { + protected String getUserComment() { IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0]; - if (ifdData == null) return null; - ExifTag tag = ifdData.getTag(ExifTag.TAG_USER_COMMENT); - if (tag == null) return null; - if (tag.getComponentCount() < 8) return null; + if (ifdData == null) { + return null; + } + ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)); + if (tag == null) { + return null; + } + if (tag.getComponentCount() < 8) { + return null; + } byte[] buf = new byte[tag.getComponentCount()]; tag.getBytes(buf); @@ -368,6 +246,103 @@ public class ExifData { Log.w(TAG, "Failed to decode the user comment"); return null; } + } + /** + * Returns a list of all {@link ExifTag}s in the ExifData or null if there + * are none. + */ + protected List getAllTags() { + ArrayList ret = new ArrayList(); + for (IfdData d : mIfdDatas) { + if (d != null) { + ExifTag[] tags = d.getAllTags(); + if (tags != null) { + for (ExifTag t : tags) { + ret.add(t); + } + } + } + } + if (ret.size() == 0) { + return null; + } + return ret; } + + /** + * Returns a list of all {@link ExifTag}s in a given IFD or null if there + * are none. + */ + protected List getAllTagsForIfd(int ifd) { + IfdData d = mIfdDatas[ifd]; + if (d == null) { + return null; + } + ExifTag[] tags = d.getAllTags(); + if (tags == null) { + return null; + } + ArrayList ret = new ArrayList(tags.length); + for (ExifTag t : tags) { + ret.add(t); + } + if (ret.size() == 0) { + return null; + } + return ret; + } + + /** + * Returns a list of all {@link ExifTag}s with a given TID or null if there + * are none. + */ + protected List getAllTagsForTagId(short tag) { + ArrayList ret = new ArrayList(); + for (IfdData d : mIfdDatas) { + if (d != null) { + ExifTag t = d.getTag(tag); + if (t != null) { + ret.add(t); + } + } + } + if (ret.size() == 0) { + return null; + } + return ret; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof ExifData) { + ExifData data = (ExifData) obj; + if (data.mByteOrder != mByteOrder || + data.mStripBytes.size() != mStripBytes.size() || + !Arrays.equals(data.mThumbnail, mThumbnail)) { + return false; + } + for (int i = 0; i < mStripBytes.size(); i++) { + if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) { + return false; + } + } + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + IfdData ifd1 = data.getIfdData(i); + IfdData ifd2 = getIfdData(i); + if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) { + return false; + } + } + return true; + } + return false; + } + } diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java new file mode 100644 index 000000000..2fef9ede0 --- /dev/null +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifInterface.java @@ -0,0 +1,2403 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.exif; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.SparseIntArray; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel.MapMode; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.TimeZone; + +/** + * This class provides methods and constants for reading and writing jpeg file + * metadata. It contains a collection of ExifTags, and a collection of + * definitions for creating valid ExifTags. The collection of ExifTags can be + * updated by: reading new ones from a file, deleting or adding existing ones, + * or building new ExifTags from a tag definition. These ExifTags can be written + * to a valid jpeg image as exif metadata. + *

+ * Each ExifTag has a tag ID (TID) and is stored in a specific image file + * directory (IFD) as specified by the exif standard. A tag definition can be + * looked up with a constant that is a combination of TID and IFD. This + * definition has information about the type, number of components, and valid + * IFDs for a tag. + * + * @see ExifTag + */ +public class ExifInterface { + public static final int TAG_NULL = -1; + public static final int IFD_NULL = -1; + public static final int DEFINITION_NULL = 0; + + /** + * Tag constants for Jeita EXIF 2.2 + */ + + // IFD 0 + public static final int TAG_IMAGE_WIDTH = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0100); + public static final int TAG_IMAGE_LENGTH = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height + public static final int TAG_BITS_PER_SAMPLE = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0102); + public static final int TAG_COMPRESSION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0103); + public static final int TAG_PHOTOMETRIC_INTERPRETATION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0106); + public static final int TAG_IMAGE_DESCRIPTION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x010E); + public static final int TAG_MAKE = + defineTag(IfdId.TYPE_IFD_0, (short) 0x010F); + public static final int TAG_MODEL = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0110); + public static final int TAG_STRIP_OFFSETS = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0111); + public static final int TAG_ORIENTATION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0112); + public static final int TAG_SAMPLES_PER_PIXEL = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0115); + public static final int TAG_ROWS_PER_STRIP = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0116); + public static final int TAG_STRIP_BYTE_COUNTS = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0117); + public static final int TAG_X_RESOLUTION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x011A); + public static final int TAG_Y_RESOLUTION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x011B); + public static final int TAG_PLANAR_CONFIGURATION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x011C); + public static final int TAG_RESOLUTION_UNIT = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0128); + public static final int TAG_TRANSFER_FUNCTION = + defineTag(IfdId.TYPE_IFD_0, (short) 0x012D); + public static final int TAG_SOFTWARE = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0131); + public static final int TAG_DATE_TIME = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0132); + public static final int TAG_ARTIST = + defineTag(IfdId.TYPE_IFD_0, (short) 0x013B); + public static final int TAG_WHITE_POINT = + defineTag(IfdId.TYPE_IFD_0, (short) 0x013E); + public static final int TAG_PRIMARY_CHROMATICITIES = + defineTag(IfdId.TYPE_IFD_0, (short) 0x013F); + public static final int TAG_Y_CB_CR_COEFFICIENTS = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0211); + public static final int TAG_Y_CB_CR_SUB_SAMPLING = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0212); + public static final int TAG_Y_CB_CR_POSITIONING = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0213); + public static final int TAG_REFERENCE_BLACK_WHITE = + defineTag(IfdId.TYPE_IFD_0, (short) 0x0214); + public static final int TAG_COPYRIGHT = + defineTag(IfdId.TYPE_IFD_0, (short) 0x8298); + public static final int TAG_EXIF_IFD = + defineTag(IfdId.TYPE_IFD_0, (short) 0x8769); + public static final int TAG_GPS_IFD = + defineTag(IfdId.TYPE_IFD_0, (short) 0x8825); + // IFD 1 + public static final int TAG_JPEG_INTERCHANGE_FORMAT = + defineTag(IfdId.TYPE_IFD_1, (short) 0x0201); + public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = + defineTag(IfdId.TYPE_IFD_1, (short) 0x0202); + // IFD Exif Tags + public static final int TAG_EXPOSURE_TIME = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A); + public static final int TAG_F_NUMBER = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D); + public static final int TAG_EXPOSURE_PROGRAM = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822); + public static final int TAG_SPECTRAL_SENSITIVITY = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824); + public static final int TAG_ISO_SPEED_RATINGS = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827); + public static final int TAG_OECF = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828); + public static final int TAG_EXIF_VERSION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000); + public static final int TAG_DATE_TIME_ORIGINAL = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003); + public static final int TAG_DATE_TIME_DIGITIZED = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004); + public static final int TAG_COMPONENTS_CONFIGURATION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101); + public static final int TAG_COMPRESSED_BITS_PER_PIXEL = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102); + public static final int TAG_SHUTTER_SPEED_VALUE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201); + public static final int TAG_APERTURE_VALUE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202); + public static final int TAG_BRIGHTNESS_VALUE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203); + public static final int TAG_EXPOSURE_BIAS_VALUE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204); + public static final int TAG_MAX_APERTURE_VALUE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205); + public static final int TAG_SUBJECT_DISTANCE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206); + public static final int TAG_METERING_MODE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207); + public static final int TAG_LIGHT_SOURCE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208); + public static final int TAG_FLASH = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209); + public static final int TAG_FOCAL_LENGTH = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A); + public static final int TAG_SUBJECT_AREA = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214); + public static final int TAG_MAKER_NOTE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C); + public static final int TAG_USER_COMMENT = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286); + public static final int TAG_SUB_SEC_TIME = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290); + public static final int TAG_SUB_SEC_TIME_ORIGINAL = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291); + public static final int TAG_SUB_SEC_TIME_DIGITIZED = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292); + public static final int TAG_FLASHPIX_VERSION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000); + public static final int TAG_COLOR_SPACE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001); + public static final int TAG_PIXEL_X_DIMENSION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002); + public static final int TAG_PIXEL_Y_DIMENSION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003); + public static final int TAG_RELATED_SOUND_FILE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004); + public static final int TAG_INTEROPERABILITY_IFD = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005); + public static final int TAG_FLASH_ENERGY = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B); + public static final int TAG_SPATIAL_FREQUENCY_RESPONSE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C); + public static final int TAG_FOCAL_PLANE_X_RESOLUTION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E); + public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F); + public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210); + public static final int TAG_SUBJECT_LOCATION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214); + public static final int TAG_EXPOSURE_INDEX = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215); + public static final int TAG_SENSING_METHOD = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217); + public static final int TAG_FILE_SOURCE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300); + public static final int TAG_SCENE_TYPE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301); + public static final int TAG_CFA_PATTERN = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302); + public static final int TAG_CUSTOM_RENDERED = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401); + public static final int TAG_EXPOSURE_MODE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402); + public static final int TAG_WHITE_BALANCE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403); + public static final int TAG_DIGITAL_ZOOM_RATIO = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404); + public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405); + public static final int TAG_SCENE_CAPTURE_TYPE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406); + public static final int TAG_GAIN_CONTROL = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407); + public static final int TAG_CONTRAST = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408); + public static final int TAG_SATURATION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409); + public static final int TAG_SHARPNESS = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A); + public static final int TAG_DEVICE_SETTING_DESCRIPTION = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B); + public static final int TAG_SUBJECT_DISTANCE_RANGE = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C); + public static final int TAG_IMAGE_UNIQUE_ID = + defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420); + // IFD GPS tags + public static final int TAG_GPS_VERSION_ID = + defineTag(IfdId.TYPE_IFD_GPS, (short) 0); + public static final int TAG_GPS_LATITUDE_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 1); + public static final int TAG_GPS_LATITUDE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 2); + public static final int TAG_GPS_LONGITUDE_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 3); + public static final int TAG_GPS_LONGITUDE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 4); + public static final int TAG_GPS_ALTITUDE_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 5); + public static final int TAG_GPS_ALTITUDE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 6); + public static final int TAG_GPS_TIME_STAMP = + defineTag(IfdId.TYPE_IFD_GPS, (short) 7); + public static final int TAG_GPS_SATTELLITES = + defineTag(IfdId.TYPE_IFD_GPS, (short) 8); + public static final int TAG_GPS_STATUS = + defineTag(IfdId.TYPE_IFD_GPS, (short) 9); + public static final int TAG_GPS_MEASURE_MODE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 10); + public static final int TAG_GPS_DOP = + defineTag(IfdId.TYPE_IFD_GPS, (short) 11); + public static final int TAG_GPS_SPEED_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 12); + public static final int TAG_GPS_SPEED = + defineTag(IfdId.TYPE_IFD_GPS, (short) 13); + public static final int TAG_GPS_TRACK_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 14); + public static final int TAG_GPS_TRACK = + defineTag(IfdId.TYPE_IFD_GPS, (short) 15); + public static final int TAG_GPS_IMG_DIRECTION_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 16); + public static final int TAG_GPS_IMG_DIRECTION = + defineTag(IfdId.TYPE_IFD_GPS, (short) 17); + public static final int TAG_GPS_MAP_DATUM = + defineTag(IfdId.TYPE_IFD_GPS, (short) 18); + public static final int TAG_GPS_DEST_LATITUDE_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 19); + public static final int TAG_GPS_DEST_LATITUDE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 20); + public static final int TAG_GPS_DEST_LONGITUDE_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 21); + public static final int TAG_GPS_DEST_LONGITUDE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 22); + public static final int TAG_GPS_DEST_BEARING_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 23); + public static final int TAG_GPS_DEST_BEARING = + defineTag(IfdId.TYPE_IFD_GPS, (short) 24); + public static final int TAG_GPS_DEST_DISTANCE_REF = + defineTag(IfdId.TYPE_IFD_GPS, (short) 25); + public static final int TAG_GPS_DEST_DISTANCE = + defineTag(IfdId.TYPE_IFD_GPS, (short) 26); + public static final int TAG_GPS_PROCESSING_METHOD = + defineTag(IfdId.TYPE_IFD_GPS, (short) 27); + public static final int TAG_GPS_AREA_INFORMATION = + defineTag(IfdId.TYPE_IFD_GPS, (short) 28); + public static final int TAG_GPS_DATE_STAMP = + defineTag(IfdId.TYPE_IFD_GPS, (short) 29); + public static final int TAG_GPS_DIFFERENTIAL = + defineTag(IfdId.TYPE_IFD_GPS, (short) 30); + // IFD Interoperability tags + public static final int TAG_INTEROPERABILITY_INDEX = + defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1); + + /** + * Tags that contain offset markers. These are included in the banned + * defines. + */ + private static HashSet sOffsetTags = new HashSet(); + static { + sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD)); + sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD)); + sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)); + sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD)); + sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS)); + } + + /** + * Tags with definitions that cannot be overridden (banned defines). + */ + protected static HashSet sBannedDefines = new HashSet(sOffsetTags); + static { + sBannedDefines.add(getTrueTagKey(TAG_NULL)); + sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); + sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS)); + } + + /** + * Returns the constant representing a tag with a given TID and default IFD. + */ + public static int defineTag(int ifdId, short tagId) { + return (tagId & 0x0000ffff) | (ifdId << 16); + } + + /** + * Returns the TID for a tag constant. + */ + public static short getTrueTagKey(int tag) { + // Truncate + return (short) tag; + } + + /** + * Returns the default IFD for a tag constant. + */ + public static int getTrueIfd(int tag) { + return tag >>> 16; + } + + /** + * Constants for {@link TAG_ORIENTATION}. They can be interpreted as + * follows: + *

    + *
  • TOP_LEFT is the normal orientation.
  • + *
  • TOP_RIGHT is a left-right mirror.
  • + *
  • BOTTOM_LEFT is a 180 degree rotation.
  • + *
  • BOTTOM_RIGHT is a top-bottom mirror.
  • + *
  • LEFT_TOP is mirrored about the top-left<->bottom-right axis.
  • + *
  • RIGHT_TOP is a 90 degree clockwise rotation.
  • + *
  • LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.
  • + *
  • RIGHT_BOTTOM is a 270 degree clockwise rotation.
  • + *
+ */ + public static interface Orientation { + public static final short TOP_LEFT = 1; + public static final short TOP_RIGHT = 2; + public static final short BOTTOM_LEFT = 3; + public static final short BOTTOM_RIGHT = 4; + public static final short LEFT_TOP = 5; + public static final short RIGHT_TOP = 6; + public static final short LEFT_BOTTOM = 7; + public static final short RIGHT_BOTTOM = 8; + } + + /** + * Constants for {@link TAG_Y_CB_CR_POSITIONING} + */ + public static interface YCbCrPositioning { + public static final short CENTERED = 1; + public static final short CO_SITED = 2; + } + + /** + * Constants for {@link TAG_COMPRESSION} + */ + public static interface Compression { + public static final short UNCOMPRESSION = 1; + public static final short JPEG = 6; + } + + /** + * Constants for {@link TAG_RESOLUTION_UNIT} + */ + public static interface ResolutionUnit { + public static final short INCHES = 2; + public static final short CENTIMETERS = 3; + } + + /** + * Constants for {@link TAG_PHOTOMETRIC_INTERPRETATION} + */ + public static interface PhotometricInterpretation { + public static final short RGB = 2; + public static final short YCBCR = 6; + } + + /** + * Constants for {@link TAG_PLANAR_CONFIGURATION} + */ + public static interface PlanarConfiguration { + public static final short CHUNKY = 1; + public static final short PLANAR = 2; + } + + /** + * Constants for {@link TAG_EXPOSURE_PROGRAM} + */ + public static interface ExposureProgram { + public static final short NOT_DEFINED = 0; + public static final short MANUAL = 1; + public static final short NORMAL_PROGRAM = 2; + public static final short APERTURE_PRIORITY = 3; + public static final short SHUTTER_PRIORITY = 4; + public static final short CREATIVE_PROGRAM = 5; + public static final short ACTION_PROGRAM = 6; + public static final short PROTRAIT_MODE = 7; + public static final short LANDSCAPE_MODE = 8; + } + + /** + * Constants for {@link TAG_METERING_MODE} + */ + public static interface MeteringMode { + public static final short UNKNOWN = 0; + public static final short AVERAGE = 1; + public static final short CENTER_WEIGHTED_AVERAGE = 2; + public static final short SPOT = 3; + public static final short MULTISPOT = 4; + public static final short PATTERN = 5; + public static final short PARTAIL = 6; + public static final short OTHER = 255; + } + + /** + * Constants for {@link TAG_FLASH} As the definition in Jeita EXIF 2.2 + * standard, we can treat this constant as bitwise flag. + *

+ * e.g. + *

+ * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | + * MODE_AUTO_MODE + */ + public static interface Flash { + // LSB + public static final short DID_NOT_FIRED = 0; + public static final short FIRED = 1; + // 1st~2nd bits + public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1; + public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1; + public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1; + // 3rd~4th bits + public static final short MODE_UNKNOWN = 0 << 3; + public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3; + public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3; + public static final short MODE_AUTO_MODE = 3 << 3; + // 5th bit + public static final short FUNCTION_PRESENT = 0 << 5; + public static final short FUNCTION_NO_FUNCTION = 1 << 5; + // 6th bit + public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6; + public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6; + } + + /** + * Constants for {@link TAG_COLOR_SPACE} + */ + public static interface ColorSpace { + public static final short SRGB = 1; + public static final short UNCALIBRATED = (short) 0xFFFF; + } + + /** + * Constants for {@link TAG_EXPOSURE_MODE} + */ + public static interface ExposureMode { + public static final short AUTO_EXPOSURE = 0; + public static final short MANUAL_EXPOSURE = 1; + public static final short AUTO_BRACKET = 2; + } + + /** + * Constants for {@link TAG_WHITE_BALANCE} + */ + public static interface WhiteBalance { + public static final short AUTO = 0; + public static final short MANUAL = 1; + } + + /** + * Constants for {@link TAG_SCENE_CAPTURE_TYPE} + */ + public static interface SceneCapture { + public static final short STANDARD = 0; + public static final short LANDSCAPE = 1; + public static final short PROTRAIT = 2; + public static final short NIGHT_SCENE = 3; + } + + /** + * Constants for {@link TAG_COMPONENTS_CONFIGURATION} + */ + public static interface ComponentsConfiguration { + public static final short NOT_EXIST = 0; + public static final short Y = 1; + public static final short CB = 2; + public static final short CR = 3; + public static final short R = 4; + public static final short G = 5; + public static final short B = 6; + } + + /** + * Constants for {@link TAG_LIGHT_SOURCE} + */ + public static interface LightSource { + public static final short UNKNOWN = 0; + public static final short DAYLIGHT = 1; + public static final short FLUORESCENT = 2; + public static final short TUNGSTEN = 3; + public static final short FLASH = 4; + public static final short FINE_WEATHER = 9; + public static final short CLOUDY_WEATHER = 10; + public static final short SHADE = 11; + public static final short DAYLIGHT_FLUORESCENT = 12; + public static final short DAY_WHITE_FLUORESCENT = 13; + public static final short COOL_WHITE_FLUORESCENT = 14; + public static final short WHITE_FLUORESCENT = 15; + public static final short STANDARD_LIGHT_A = 17; + public static final short STANDARD_LIGHT_B = 18; + public static final short STANDARD_LIGHT_C = 19; + public static final short D55 = 20; + public static final short D65 = 21; + public static final short D75 = 22; + public static final short D50 = 23; + public static final short ISO_STUDIO_TUNGSTEN = 24; + public static final short OTHER = 255; + } + + /** + * Constants for {@link TAG_SENSING_METHOD} + */ + public static interface SensingMethod { + public static final short NOT_DEFINED = 1; + public static final short ONE_CHIP_COLOR = 2; + public static final short TWO_CHIP_COLOR = 3; + public static final short THREE_CHIP_COLOR = 4; + public static final short COLOR_SEQUENTIAL_AREA = 5; + public static final short TRILINEAR = 7; + public static final short COLOR_SEQUENTIAL_LINEAR = 8; + } + + /** + * Constants for {@link TAG_FILE_SOURCE} + */ + public static interface FileSource { + public static final short DSC = 3; + } + + /** + * Constants for {@link TAG_SCENE_TYPE} + */ + public static interface SceneType { + public static final short DIRECT_PHOTOGRAPHED = 1; + } + + /** + * Constants for {@link TAG_GAIN_CONTROL} + */ + public static interface GainControl { + public static final short NONE = 0; + public static final short LOW_UP = 1; + public static final short HIGH_UP = 2; + public static final short LOW_DOWN = 3; + public static final short HIGH_DOWN = 4; + } + + /** + * Constants for {@link TAG_CONTRAST} + */ + public static interface Contrast { + public static final short NORMAL = 0; + public static final short SOFT = 1; + public static final short HARD = 2; + } + + /** + * Constants for {@link TAG_SATURATION} + */ + public static interface Saturation { + public static final short NORMAL = 0; + public static final short LOW = 1; + public static final short HIGH = 2; + } + + /** + * Constants for {@link TAG_SHARPNESS} + */ + public static interface Sharpness { + public static final short NORMAL = 0; + public static final short SOFT = 1; + public static final short HARD = 2; + } + + /** + * Constants for {@link TAG_SUBJECT_DISTANCE} + */ + public static interface SubjectDistance { + public static final short UNKNOWN = 0; + public static final short MACRO = 1; + public static final short CLOSE_VIEW = 2; + public static final short DISTANT_VIEW = 3; + } + + /** + * Constants for {@link TAG_GPS_LATITUDE_REF}, + * {@link TAG_GPS_DEST_LATITUDE_REF} + */ + public static interface GpsLatitudeRef { + public static final String NORTH = "N"; + public static final String SOUTH = "S"; + } + + /** + * Constants for {@link TAG_GPS_LONGITUDE_REF}, + * {@link TAG_GPS_DEST_LONGITUDE_REF} + */ + public static interface GpsLongitudeRef { + public static final String EAST = "E"; + public static final String WEST = "W"; + } + + /** + * Constants for {@link TAG_GPS_ALTITUDE_REF} + */ + public static interface GpsAltitudeRef { + public static final short SEA_LEVEL = 0; + public static final short SEA_LEVEL_NEGATIVE = 1; + } + + /** + * Constants for {@link TAG_GPS_STATUS} + */ + public static interface GpsStatus { + public static final String IN_PROGRESS = "A"; + public static final String INTEROPERABILITY = "V"; + } + + /** + * Constants for {@link TAG_GPS_MEASURE_MODE} + */ + public static interface GpsMeasureMode { + public static final String MODE_2_DIMENSIONAL = "2"; + public static final String MODE_3_DIMENSIONAL = "3"; + } + + /** + * Constants for {@link TAG_GPS_SPEED_REF}, + * {@link TAG_GPS_DEST_DISTANCE_REF} + */ + public static interface GpsSpeedRef { + public static final String KILOMETERS = "K"; + public static final String MILES = "M"; + public static final String KNOTS = "N"; + } + + /** + * Constants for {@link TAG_GPS_TRACK_REF}, + * {@link TAG_GPS_IMG_DIRECTION_REF}, {@link TAG_GPS_DEST_BEARING_REF} + */ + public static interface GpsTrackRef { + public static final String TRUE_DIRECTION = "T"; + public static final String MAGNETIC_DIRECTION = "M"; + } + + /** + * Constants for {@link TAG_GPS_DIFFERENTIAL} + */ + public static interface GpsDifferential { + public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0; + public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1; + } + + private static final String NULL_ARGUMENT_STRING = "Argument is null"; + private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER); + public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN; + + public ExifInterface() { + mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + /** + * Reads the exif tags from a byte array, clearing this ExifInterface + * object's existing exif tags. + * + * @param jpeg a byte array containing a jpeg compressed image. + * @throws IOException + */ + public void readExif(byte[] jpeg) throws IOException { + readExif(new ByteArrayInputStream(jpeg)); + } + + /** + * Reads the exif tags from an InputStream, clearing this ExifInterface + * object's existing exif tags. + * + * @param inStream an InputStream containing a jpeg compressed image. + * @throws IOException + */ + public void readExif(InputStream inStream) throws IOException { + if (inStream == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + ExifData d = null; + try { + d = new ExifReader(this).read(inStream); + } catch (ExifInvalidFormatException e) { + throw new IOException("Invalid exif format : " + e); + } + mData = d; + } + + /** + * Reads the exif tags from a file, clearing this ExifInterface object's + * existing exif tags. + * + * @param inFileName a string representing the filepath to jpeg file. + * @throws FileNotFoundException + * @throws IOException + */ + public void readExif(String inFileName) throws FileNotFoundException, IOException { + if (inFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + InputStream is = null; + try { + is = (InputStream) new FileInputStream(inFileName); + readExif(is); + } catch (IOException e) { + closeSilently(is); + throw e; + } + is.close(); + } + + /** + * Sets the exif tags, clearing this ExifInterface object's existing exif + * tags. + * + * @param tags a collection of exif tags to set. + */ + public void setExif(Collection tags) { + clearExif(); + setTags(tags); + } + + /** + * Clears this ExifInterface object's existing exif tags. + */ + public void clearExif() { + mData = new ExifData(DEFAULT_BYTE_ORDER); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg image, + * removing prior exif tags. + * + * @param jpeg a byte array containing a jpeg compressed image. + * @param exifOutStream an OutputStream to which the jpeg image with added + * exif tags will be written. + * @throws IOException + */ + public void writeExif(byte[] jpeg, OutputStream exifOutStream) throws IOException { + if (jpeg == null || exifOutStream == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream s = getExifWriterStream(exifOutStream); + s.write(jpeg, 0, jpeg.length); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg compressed + * bitmap, removing prior exif tags. + * + * @param bmap a bitmap to compress and write exif into. + * @param exifOutStream the OutputStream to which the jpeg image with added + * exif tags will be written. + * @throws IOException + */ + public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException { + if (bmap == null || exifOutStream == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream s = getExifWriterStream(exifOutStream); + bmap.compress(Bitmap.CompressFormat.JPEG, 90, s); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg stream, + * removing prior exif tags. + * + * @param jpegStream an InputStream containing a jpeg compressed image. + * @param exifOutStream an OutputStream to which the jpeg image with added + * exif tags will be written. + * @throws IOException + */ + public void writeExif(InputStream jpegStream, OutputStream exifOutStream) throws IOException { + if (jpegStream == null || exifOutStream == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream s = getExifWriterStream(exifOutStream); + doExifStreamIO(jpegStream, s); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg image, + * removing prior exif tags. + * + * @param jpeg a byte array containing a jpeg compressed image. + * @param exifOutFileName a String containing the filepath to which the jpeg + * image with added exif tags will be written. + * @throws FileNotFoundException + * @throws IOException + */ + public void writeExif(byte[] jpeg, String exifOutFileName) throws FileNotFoundException, + IOException { + if (jpeg == null || exifOutFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream s = null; + try { + s = getExifWriterStream(exifOutFileName); + s.write(jpeg, 0, jpeg.length); + s.flush(); + } catch (IOException e) { + closeSilently(s); + throw e; + } + s.close(); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg compressed + * bitmap, removing prior exif tags. + * + * @param bmap a bitmap to compress and write exif into. + * @param exifOutFileName a String containing the filepath to which the jpeg + * image with added exif tags will be written. + * @throws FileNotFoundException + * @throws IOException + */ + public void writeExif(Bitmap bmap, String exifOutFileName) throws FileNotFoundException, + IOException { + if (bmap == null || exifOutFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream s = null; + try { + s = getExifWriterStream(exifOutFileName); + bmap.compress(Bitmap.CompressFormat.JPEG, 90, s); + s.flush(); + } catch (IOException e) { + closeSilently(s); + throw e; + } + s.close(); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg stream, + * removing prior exif tags. + * + * @param jpegStream an InputStream containing a jpeg compressed image. + * @param exifOutFileName a String containing the filepath to which the jpeg + * image with added exif tags will be written. + * @throws FileNotFoundException + * @throws IOException + */ + public void writeExif(InputStream jpegStream, String exifOutFileName) + throws FileNotFoundException, IOException { + if (jpegStream == null || exifOutFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream s = null; + try { + s = getExifWriterStream(exifOutFileName); + doExifStreamIO(jpegStream, s); + s.flush(); + } catch (IOException e) { + closeSilently(s); + throw e; + } + s.close(); + } + + /** + * Writes the tags from this ExifInterface object into a jpeg file, removing + * prior exif tags. + * + * @param jpegFileName a String containing the filepath for a jpeg file. + * @param exifOutFileName a String containing the filepath to which the jpeg + * image with added exif tags will be written. + * @throws FileNotFoundException + * @throws IOException + */ + public void writeExif(String jpegFileName, String exifOutFileName) + throws FileNotFoundException, IOException { + if (jpegFileName == null || exifOutFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + InputStream is = null; + try { + is = new FileInputStream(jpegFileName); + writeExif(is, exifOutFileName); + } catch (IOException e) { + closeSilently(is); + throw e; + } + is.close(); + } + + /** + * Wraps an OutputStream object with an ExifOutputStream. Exif tags in this + * ExifInterface object will be added to a jpeg image written to this + * stream, removing prior exif tags. Other methods of this ExifInterface + * object should not be called until the returned OutputStream has been + * closed. + * + * @param outStream an OutputStream to wrap. + * @return an OutputStream that wraps the outStream parameter, and adds exif + * metadata. A jpeg image should be written to this stream. + */ + public OutputStream getExifWriterStream(OutputStream outStream) { + if (outStream == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + ExifOutputStream eos = new ExifOutputStream(outStream, this); + eos.setExifData(mData); + return eos; + } + + /** + * Returns an OutputStream object that writes to a file. Exif tags in this + * ExifInterface object will be added to a jpeg image written to this + * stream, removing prior exif tags. Other methods of this ExifInterface + * object should not be called until the returned OutputStream has been + * closed. + * + * @param exifOutFileName an String containing a filepath for a jpeg file. + * @return an OutputStream that writes to the exifOutFileName file, and adds + * exif metadata. A jpeg image should be written to this stream. + * @throws FileNotFoundException + */ + public OutputStream getExifWriterStream(String exifOutFileName) throws FileNotFoundException { + if (exifOutFileName == null) { + throw new IllegalArgumentException(NULL_ARGUMENT_STRING); + } + OutputStream out = null; + try { + out = (OutputStream) new FileOutputStream(exifOutFileName); + } catch (FileNotFoundException e) { + closeSilently(out); + throw e; + } + return getExifWriterStream(out); + } + + /** + * Attempts to do an in-place rewrite the exif metadata in a file for the + * given tags. If tags do not exist or do not have the same size as the + * existing exif tags, this method will fail. + * + * @param filename a String containing a filepath for a jpeg file with exif + * tags to rewrite. + * @param tags tags that will be written into the jpeg file over existing + * tags if possible. + * @return true if success, false if could not overwrite. If false, no + * changes are made to the file. + * @throws FileNotFoundException + * @throws IOException + */ + public boolean rewriteExif(String filename, Collection tags) + throws FileNotFoundException, IOException { + RandomAccessFile file = null; + InputStream is = null; + boolean ret; + try { + File temp = new File(filename); + is = new FileInputStream(temp); + + // Parse beginning of APP1 in exif to find size of exif header. + ExifParser parser = null; + try { + parser = ExifParser.parse(is, this); + } catch (ExifInvalidFormatException e) { + throw new IOException("Invalid exif format : ", e); + } + long exifSize = parser.getOffsetToExifEndFromSOF(); + + // Free up resources + is.close(); + is = null; + + // Open file for memory mapping. + file = new RandomAccessFile(temp, "rw"); + long fileLength = file.length(); + if (fileLength < exifSize) { + throw new IOException("Filesize changed during operation"); + } + + // Map only exif header into memory. + ByteBuffer buf = file.getChannel().map(MapMode.READ_WRITE, 0, exifSize); + + // Attempt to overwrite tag values without changing lengths (avoids + // file copy). + ret = rewriteExif(buf, tags); + } catch (IOException e) { + closeSilently(file); + throw e; + } finally { + closeSilently(is); + } + file.close(); + return ret; + } + + /** + * Attempts to do an in-place rewrite the exif metadata in a ByteBuffer for + * the given tags. If tags do not exist or do not have the same size as the + * existing exif tags, this method will fail. + * + * @param buf a ByteBuffer containing a jpeg file with existing exif tags to + * rewrite. + * @param tags tags that will be written into the jpeg ByteBuffer over + * existing tags if possible. + * @return true if success, false if could not overwrite. If false, no + * changes are made to the ByteBuffer. + * @throws IOException + */ + public boolean rewriteExif(ByteBuffer buf, Collection tags) throws IOException { + ExifModifier mod = null; + try { + mod = new ExifModifier(buf, this); + for (ExifTag t : tags) { + mod.modifyTag(t); + } + return mod.commit(); + } catch (ExifInvalidFormatException e) { + throw new IOException("Invalid exif format : " + e); + } + } + + /** + * Attempts to do an in-place rewrite of the exif metadata. If this fails, + * fall back to overwriting file. This preserves tags that are not being + * rewritten. + * + * @param filename a String containing a filepath for a jpeg file. + * @param tags tags that will be written into the jpeg file over existing + * tags if possible. + * @throws FileNotFoundException + * @throws IOException + * @see #rewriteExif + */ + public void forceRewriteExif(String filename, Collection tags) + throws FileNotFoundException, + IOException { + // Attempt in-place write + if (!rewriteExif(filename, tags)) { + // Fall back to doing a copy + ExifData tempData = mData; + mData = new ExifData(DEFAULT_BYTE_ORDER); + FileInputStream is = null; + ByteArrayOutputStream bytes = null; + try { + is = new FileInputStream(filename); + bytes = new ByteArrayOutputStream(); + doExifStreamIO(is, bytes); + byte[] imageBytes = bytes.toByteArray(); + readExif(imageBytes); + setTags(tags); + writeExif(imageBytes, filename); + } catch (IOException e) { + closeSilently(is); + throw e; + } finally { + is.close(); + // Prevent clobbering of mData + mData = tempData; + } + } + } + + /** + * Attempts to do an in-place rewrite of the exif metadata using the tags in + * this ExifInterface object. If this fails, fall back to overwriting file. + * This preserves tags that are not being rewritten. + * + * @param filename a String containing a filepath for a jpeg file. + * @throws FileNotFoundException + * @throws IOException + * @see #rewriteExif + */ + public void forceRewriteExif(String filename) throws FileNotFoundException, IOException { + forceRewriteExif(filename, getAllTags()); + } + + /** + * Get the exif tags in this ExifInterface object or null if none exist. + * + * @return a List of {@link ExifTag}s. + */ + public List getAllTags() { + return mData.getAllTags(); + } + + /** + * Returns a list of ExifTags that share a TID (which can be obtained by + * calling {@link #getTrueTagKey} on a defined tag constant) or null if none + * exist. + * + * @param tagId a TID as defined in the exif standard (or with + * {@link #defineTag}). + * @return a List of {@link ExifTag}s. + */ + public List getTagsForTagId(short tagId) { + return mData.getAllTagsForTagId(tagId); + } + + /** + * Returns a list of ExifTags that share an IFD (which can be obtained by + * calling {@link #getTrueIFD} on a defined tag constant) or null if none + * exist. + * + * @param ifdId an IFD as defined in the exif standard (or with + * {@link #defineTag}). + * @return a List of {@link ExifTag}s. + */ + public List getTagsForIfdId(int ifdId) { + return mData.getAllTagsForIfd(ifdId); + } + + /** + * Gets an ExifTag for an IFD other than the tag's default. + * + * @see #getTag + */ + public ExifTag getTag(int tagId, int ifdId) { + if (!ExifTag.isValidIfd(ifdId)) { + return null; + } + return mData.getTag(getTrueTagKey(tagId), ifdId); + } + + /** + * Returns the ExifTag in that tag's default IFD for a defined tag constant + * or null if none exists. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @return an {@link ExifTag} or null if none exists. + */ + public ExifTag getTag(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTag(tagId, ifdId); + } + + /** + * Gets a tag value for an IFD other than the tag's default. + * + * @see #getTagValue + */ + public Object getTagValue(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + return (t == null) ? null : t.getValue(); + } + + /** + * Returns the value of the ExifTag in that tag's default IFD for a defined + * tag constant or null if none exists or the value could not be cast into + * the return type. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @return the value of the ExifTag or null if none exists. + */ + public Object getTagValue(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagValue(tagId, ifdId); + } + + /* + * Getter methods that are similar to getTagValue. Null is returned if the + * tag value cannot be cast into the return type. + */ + + /** + * @see #getTagValue + */ + public String getTagStringValue(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return null; + } + return t.getValueAsString(); + } + + /** + * @see #getTagValue + */ + public String getTagStringValue(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagStringValue(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public Long getTagLongValue(int tagId, int ifdId) { + long[] l = getTagLongValues(tagId, ifdId); + if (l == null || l.length <= 0) { + return null; + } + return new Long(l[0]); + } + + /** + * @see #getTagValue + */ + public Long getTagLongValue(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagLongValue(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public Integer getTagIntValue(int tagId, int ifdId) { + int[] l = getTagIntValues(tagId, ifdId); + if (l == null || l.length <= 0) { + return null; + } + return new Integer(l[0]); + } + + /** + * @see #getTagValue + */ + public Integer getTagIntValue(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagIntValue(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public Byte getTagByteValue(int tagId, int ifdId) { + byte[] l = getTagByteValues(tagId, ifdId); + if (l == null || l.length <= 0) { + return null; + } + return new Byte(l[0]); + } + + /** + * @see #getTagValue + */ + public Byte getTagByteValue(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagByteValue(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public Rational getTagRationalValue(int tagId, int ifdId) { + Rational[] l = getTagRationalValues(tagId, ifdId); + if (l == null || l.length == 0) { + return null; + } + return new Rational(l[0]); + } + + /** + * @see #getTagValue + */ + public Rational getTagRationalValue(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagRationalValue(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public long[] getTagLongValues(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return null; + } + return t.getValueAsLongs(); + } + + /** + * @see #getTagValue + */ + public long[] getTagLongValues(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagLongValues(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public int[] getTagIntValues(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return null; + } + return t.getValueAsInts(); + } + + /** + * @see #getTagValue + */ + public int[] getTagIntValues(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagIntValues(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public byte[] getTagByteValues(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return null; + } + return t.getValueAsBytes(); + } + + /** + * @see #getTagValue + */ + public byte[] getTagByteValues(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagByteValues(tagId, ifdId); + } + + /** + * @see #getTagValue + */ + public Rational[] getTagRationalValues(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return null; + } + return t.getValueAsRationals(); + } + + /** + * @see #getTagValue + */ + public Rational[] getTagRationalValues(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return getTagRationalValues(tagId, ifdId); + } + + /** + * Checks whether a tag has a defined number of elements. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @return true if the tag has a defined number of elements. + */ + public boolean isTagCountDefined(int tagId) { + int info = getTagInfo().get(tagId); + // No value in info can be zero, as all tags have a non-zero type + if (info == 0) { + return false; + } + return getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED; + } + + /** + * Gets the defined number of elements for a tag. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the + * tag or the number of elements is not defined. + */ + public int getDefinedTagCount(int tagId) { + int info = getTagInfo().get(tagId); + if (info == 0) { + return ExifTag.SIZE_UNDEFINED; + } + return getComponentCountFromInfo(info); + } + + /** + * Gets the number of elements for an ExifTag in a given IFD. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @param ifdId the IFD containing the ExifTag to check. + * @return the number of elements in the ExifTag, if the tag's size is + * undefined this will return the actual number of elements that is + * in the ExifTag's value. + */ + public int getActualTagCount(int tagId, int ifdId) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return 0; + } + return t.getComponentCount(); + } + + /** + * Gets the default IFD for a tag. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @return the default IFD for a tag definition or {@link #IFD_NULL} if no + * definition exists. + */ + public int getDefinedTagDefaultIfd(int tagId) { + int info = getTagInfo().get(tagId); + if (info == DEFINITION_NULL) { + return IFD_NULL; + } + return getTrueIfd(tagId); + } + + /** + * Gets the defined type for a tag. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @return the type. + * @see ExifTag#getDataType() + */ + public short getDefinedTagType(int tagId) { + int info = getTagInfo().get(tagId); + if (info == 0) { + return -1; + } + return getTypeFromInfo(info); + } + + /** + * Returns true if tag TID is one of the following: {@link TAG_EXIF_IFD}, + * {@link TAG_GPS_IFD}, {@link TAG_JPEG_INTERCHANGE_FORMAT}, + * {@link TAG_STRIP_OFFSETS}, {@link TAG_INTEROPERABILITY_IFD} + *

+ * Note: defining tags with these TID's is disallowed. + * + * @param tag a tag's TID (can be obtained from a defined tag constant with + * {@link #getTrueTagKey}). + * @return true if the TID is that of an offset tag. + */ + protected static boolean isOffsetTag(short tag) { + return sOffsetTags.contains(tag); + } + + /** + * Creates a tag for a defined tag constant in a given IFD if that IFD is + * allowed for the tag. This method will fail anytime the appropriate + * {@link ExifTag#setValue} for this tag's datatype would fail. + * + * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @param ifdId the IFD that the tag should be in. + * @param val the value of the tag to set. + * @return an ExifTag object or null if one could not be constructed. + * @see #buildTag + */ + public ExifTag buildTag(int tagId, int ifdId, Object val) { + int info = getTagInfo().get(tagId); + if (info == 0 || val == null) { + return null; + } + short type = getTypeFromInfo(info); + int definedCount = getComponentCountFromInfo(info); + boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED); + if (!ExifInterface.isIfdAllowed(info, ifdId)) { + return null; + } + ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount); + if (!t.setValue(val)) { + return null; + } + return t; + } + + /** + * Creates a tag for a defined tag constant in the tag's default IFD. + * + * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @param val the tag's value. + * @return an ExifTag object. + */ + public ExifTag buildTag(int tagId, Object val) { + int ifdId = getTrueIfd(tagId); + return buildTag(tagId, ifdId, val); + } + + protected ExifTag buildUninitializedTag(int tagId) { + int info = getTagInfo().get(tagId); + if (info == 0) { + return null; + } + short type = getTypeFromInfo(info); + int definedCount = getComponentCountFromInfo(info); + boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED); + int ifdId = getTrueIfd(tagId); + ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount); + return t; + } + + /** + * Sets the value of an ExifTag if it exists in the given IFD. The value + * must be the correct type and length for that ExifTag. + * + * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @param ifdId the IFD that the ExifTag is in. + * @param val the value to set. + * @return true if success, false if the ExifTag doesn't exist or the value + * is the wrong type/length. + * @see #setTagValue + */ + public boolean setTagValue(int tagId, int ifdId, Object val) { + ExifTag t = getTag(tagId, ifdId); + if (t == null) { + return false; + } + return t.setValue(val); + } + + /** + * Sets the value of an ExifTag if it exists it's default IFD. The value + * must be the correct type and length for that ExifTag. + * + * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @param val the value to set. + * @return true if success, false if the ExifTag doesn't exist or the value + * is the wrong type/length. + */ + public boolean setTagValue(int tagId, Object val) { + int ifdId = getDefinedTagDefaultIfd(tagId); + return setTagValue(tagId, ifdId, val); + } + + /** + * Puts an ExifTag into this ExifInterface object's tags, removing a + * previous ExifTag with the same TID and IFD. The IFD it is put into will + * be the one the tag was created with in {@link #buildTag}. + * + * @param tag an ExifTag to put into this ExifInterface's tags. + * @return the previous ExifTag with the same TID and IFD or null if none + * exists. + */ + public ExifTag setTag(ExifTag tag) { + return mData.addTag(tag); + } + + /** + * Puts a collection of ExifTags into this ExifInterface objects's tags. Any + * previous ExifTags with the same TID and IFDs will be removed. + * + * @param tags a Collection of ExifTags. + * @see #setTag + */ + public void setTags(Collection tags) { + for (ExifTag t : tags) { + setTag(t); + } + } + + /** + * Removes the ExifTag for a tag constant from the given IFD. + * + * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + * @param ifdId the IFD of the ExifTag to remove. + */ + public void deleteTag(int tagId, int ifdId) { + mData.removeTag(getTrueTagKey(tagId), ifdId); + } + + /** + * Removes the ExifTag for a tag constant from that tag's default IFD. + * + * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + */ + public void deleteTag(int tagId) { + int ifdId = getDefinedTagDefaultIfd(tagId); + deleteTag(tagId, ifdId); + } + + /** + * Creates a new tag definition in this ExifInterface object for a given TID + * and default IFD. Creating a definition with the same TID and default IFD + * as a previous definition will override it. + * + * @param tagId the TID for the tag. + * @param defaultIfd the default IFD for the tag. + * @param tagType the type of the tag (see {@link ExifTag#getDataType()}). + * @param defaultComponentCount the number of elements of this tag's type in + * the tags value. + * @param allowedIfds the IFD's this tag is allowed to be put in. + * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or + * {@link #TAG_NULL} if the definition could not be made. + */ + public int setTagDefinition(short tagId, int defaultIfd, short tagType, + short defaultComponentCount, int[] allowedIfds) { + if (sBannedDefines.contains(tagId)) { + return TAG_NULL; + } + if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) { + int tagDef = defineTag(defaultIfd, tagId); + if (tagDef == TAG_NULL) { + return TAG_NULL; + } + int[] otherDefs = getTagDefinitionsForTagId(tagId); + SparseIntArray infos = getTagInfo(); + // Make sure defaultIfd is in allowedIfds + boolean defaultCheck = false; + for (int i : allowedIfds) { + if (defaultIfd == i) { + defaultCheck = true; + } + if (!ExifTag.isValidIfd(i)) { + return TAG_NULL; + } + } + if (!defaultCheck) { + return TAG_NULL; + } + + int ifdFlags = getFlagsFromAllowedIfds(allowedIfds); + // Make sure no identical tags can exist in allowedIfds + if (otherDefs != null) { + for (int def : otherDefs) { + int tagInfo = infos.get(def); + int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo); + if ((ifdFlags & allowedFlags) != 0) { + return TAG_NULL; + } + } + } + getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount); + return tagDef; + } + return TAG_NULL; + } + + protected int getTagDefinition(short tagId, int defaultIfd) { + return getTagInfo().get(defineTag(defaultIfd, tagId)); + } + + protected int[] getTagDefinitionsForTagId(short tagId) { + int[] ifds = IfdData.getIfds(); + int[] defs = new int[ifds.length]; + int counter = 0; + SparseIntArray infos = getTagInfo(); + for (int i : ifds) { + int def = defineTag(i, tagId); + if (infos.get(def) != DEFINITION_NULL) { + defs[counter++] = def; + } + } + if (counter == 0) { + return null; + } + + return Arrays.copyOfRange(defs, 0, counter); + } + + protected int getTagDefinitionForTag(ExifTag tag) { + short type = tag.getDataType(); + int count = tag.getComponentCount(); + int ifd = tag.getIfd(); + return getTagDefinitionForTag(tag.getTagId(), type, count, ifd); + } + + protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) { + int[] defs = getTagDefinitionsForTagId(tagId); + if (defs == null) { + return TAG_NULL; + } + SparseIntArray infos = getTagInfo(); + int ret = TAG_NULL; + for (int i : defs) { + int info = infos.get(i); + short def_type = getTypeFromInfo(info); + int def_count = getComponentCountFromInfo(info); + int[] def_ifds = getAllowedIfdsFromInfo(info); + boolean valid_ifd = false; + for (int j : def_ifds) { + if (j == ifd) { + valid_ifd = true; + break; + } + } + if (valid_ifd && type == def_type + && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) { + ret = i; + break; + } + } + return ret; + } + + /** + * Removes a tag definition for given defined tag constant. + * + * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}. + */ + public void removeTagDefinition(int tagId) { + getTagInfo().delete(tagId); + } + + /** + * Resets tag definitions to the default ones. + */ + public void resetTagDefinitions() { + mTagInfo = null; + } + + /** + * Returns the thumbnail from IFD1 as a bitmap, or null if none exists. + * + * @return the thumbnail as a bitmap. + */ + public Bitmap getThumbnailBitmap() { + if (mData.hasCompressedThumbnail()) { + byte[] thumb = mData.getCompressedThumbnail(); + return BitmapFactory.decodeByteArray(thumb, 0, thumb.length); + } else if (mData.hasUncompressedStrip()) { + // TODO: implement uncompressed + } + return null; + } + + /** + * Returns the thumbnail from IFD1 as a byte array, or null if none exists. + * The bytes may either be an uncompressed strip as specified in the exif + * standard or a jpeg compressed image. + * + * @return the thumbnail as a byte array. + */ + public byte[] getThumbnailBytes() { + if (mData.hasCompressedThumbnail()) { + return mData.getCompressedThumbnail(); + } else if (mData.hasUncompressedStrip()) { + // TODO: implement this + } + return null; + } + + /** + * Returns the thumbnail if it is jpeg compressed, or null if none exists. + * + * @return the thumbnail as a byte array. + */ + public byte[] getThumbnail() { + return mData.getCompressedThumbnail(); + } + + /** + * Check if thumbnail is compressed. + * + * @return true if the thumbnail is compressed. + */ + public boolean isThumbnailCompressed() { + return mData.hasCompressedThumbnail(); + } + + /** + * Check if thumbnail exists. + * + * @return true if a compressed thumbnail exists. + */ + public boolean hasThumbnail() { + // TODO: add back in uncompressed strip + return mData.hasCompressedThumbnail(); + } + + // TODO: uncompressed thumbnail setters + + /** + * Sets the thumbnail to be a jpeg compressed image. Clears any prior + * thumbnail. + * + * @param thumb a byte array containing a jpeg compressed image. + * @return true if the thumbnail was set. + */ + public boolean setCompressedThumbnail(byte[] thumb) { + mData.clearThumbnailAndStrips(); + mData.setCompressedThumbnail(thumb); + return true; + } + + /** + * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior + * thumbnail. + * + * @param thumb a bitmap to compress to a jpeg thumbnail. + * @return true if the thumbnail was set. + */ + public boolean setCompressedThumbnail(Bitmap thumb) { + ByteArrayOutputStream thumbnail = new ByteArrayOutputStream(); + if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) { + return false; + } + return setCompressedThumbnail(thumbnail.toByteArray()); + } + + /** + * Clears the compressed thumbnail if it exists. + */ + public void removeCompressedThumbnail() { + mData.setCompressedThumbnail(null); + } + + // Convenience methods: + + /** + * Decodes the user comment tag into string as specified in the EXIF + * standard. Returns null if decoding failed. + */ + public String getUserComment() { + return mData.getUserComment(); + } + + /** + * Returns the Orientation ExifTag value for a given number of degrees. + * + * @param degrees the amount an image is rotated in degrees. + */ + public static short getOrientationValueForRotation(int degrees) { + degrees %= 360; + if (degrees < 0) { + degrees += 360; + } + if (degrees < 90) { + return Orientation.TOP_LEFT; // 0 degrees + } else if (degrees < 180) { + return Orientation.RIGHT_TOP; // 90 degrees cw + } else if (degrees < 270) { + return Orientation.BOTTOM_LEFT; // 180 degrees + } else { + return Orientation.RIGHT_BOTTOM; // 270 degrees cw + } + } + + /** + * Returns the rotation degrees corresponding to an ExifTag Orientation + * value. + * + * @param orientation the ExifTag Orientation value. + */ + public static int getRotationForOrientationValue(short orientation) { + switch (orientation) { + case Orientation.TOP_LEFT: + return 0; + case Orientation.RIGHT_TOP: + return 90; + case Orientation.BOTTOM_LEFT: + return 180; + case Orientation.RIGHT_BOTTOM: + return 270; + default: + return 0; + } + } + + /** + * Gets the double representation of the GPS latitude or longitude + * coordinate. + * + * @param coordinate an array of 3 Rationals representing the degrees, + * minutes, and seconds of the GPS location as defined in the + * exif specification. + * @param reference a GPS reference reperesented by a String containing "N", + * "S", "E", or "W". + * @return the GPS coordinate represented as degrees + minutes/60 + + * seconds/3600 + */ + public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) { + try { + double degrees = coordinate[0].toDouble(); + double minutes = coordinate[1].toDouble(); + double seconds = coordinate[2].toDouble(); + double result = degrees + minutes / 60.0 + seconds / 3600.0; + if ((reference.equals("S") || reference.equals("W"))) { + return -result; + } + return result; + } catch (ArrayIndexOutOfBoundsException e) { + throw new IllegalArgumentException(); + } + } + + /** + * Gets the GPS latitude and longitude as a pair of doubles from this + * ExifInterface object's tags, or null if the necessary tags do not exist. + * + * @return an array of 2 doubles containing the latitude, and longitude + * respectively. + * @see #convertLatOrLongToDouble + */ + public double[] getLatLongAsDoubles() { + Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE); + String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF); + Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE); + String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF); + if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null + || latitude.length < 3 || longitude.length < 3) { + return null; + } + double[] latLon = new double[2]; + latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef); + latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef); + return latLon; + } + + private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd"; + private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss"; + private final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR); + private final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR); + private final Calendar mGPSTimeStampCalendar = Calendar + .getInstance(TimeZone.getTimeZone("UTC")); + + /** + * Creates, formats, and sets the DateTimeStamp tag for one of: + * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED}, + * {@link #TAG_DATE_TIME_ORIGINAL}. + * + * @param tagId one of the DateTimeStamp tags. + * @param timestamp a timestamp to format. + * @param timezone a TimeZone object. + * @return true if success, false if the tag could not be set. + */ + public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) { + if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED + || tagId == TAG_DATE_TIME_ORIGINAL) { + mDateTimeStampFormat.setTimeZone(timezone); + ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp)); + if (t == null) { + return false; + } + setTag(t); + } else { + return false; + } + return true; + } + + /** + * Creates and sets all to the GPS tags for a give latitude and longitude. + * + * @param latitude a GPS latitude coordinate. + * @param longitude a GPS longitude coordinate. + * @return true if success, false if they could not be created or set. + */ + public boolean addGpsTags(double latitude, double longitude) { + ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude)); + ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude)); + ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF, + latitude >= 0 ? ExifInterface.GpsLatitudeRef.NORTH + : ExifInterface.GpsLatitudeRef.SOUTH); + ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF, + longitude >= 0 ? ExifInterface.GpsLongitudeRef.EAST + : ExifInterface.GpsLongitudeRef.WEST); + if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) { + return false; + } + setTag(latTag); + setTag(longTag); + setTag(latRefTag); + setTag(longRefTag); + return true; + } + + /** + * Creates and sets the GPS timestamp tag. + * + * @param timestamp a GPS timestamp. + * @return true if success, false if could not be created or set. + */ + public boolean addGpsDateTimeStampTag(long timestamp) { + ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp)); + if (t == null) { + return false; + } + setTag(t); + mGPSTimeStampCalendar.setTimeInMillis(timestamp); + t = buildTag(TAG_GPS_TIME_STAMP, new Rational[] { + new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1), + new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1), + new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1) + }); + if (t == null) { + return false; + } + setTag(t); + return true; + } + + private static Rational[] toExifLatLong(double value) { + // convert to the format dd/1 mm/1 ssss/100 + value = Math.abs(value); + int degrees = (int) value; + value = (value - degrees) * 60; + int minutes = (int) value; + value = (value - minutes) * 6000; + int seconds = (int) value; + return new Rational[] { + new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100) + }; + } + + private void doExifStreamIO(InputStream is, OutputStream os) throws IOException { + byte[] buf = new byte[1024]; + int ret = is.read(buf, 0, 1024); + while (ret != -1) { + os.write(buf, 0, ret); + ret = is.read(buf, 0, 1024); + } + } + + protected static void closeSilently(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (Throwable e) { + // ignored + } + } + } + + private SparseIntArray mTagInfo = null; + + protected SparseIntArray getTagInfo() { + if (mTagInfo == null) { + mTagInfo = new SparseIntArray(); + initTagInfo(); + } + return mTagInfo; + } + + private void initTagInfo() { + /** + * We put tag information in a 4-bytes integer. The first byte a bitmask + * representing the allowed IFDs of the tag, the second byte is the data + * type, and the last two byte are a short value indicating the default + * component count of this tag. + */ + // IFD0 tags + int[] ifdAllowedIfds = { + IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1 + }; + int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24; + mTagInfo.put(ExifInterface.TAG_MAKE, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3); + mTagInfo.put(ExifInterface.TAG_COMPRESSION, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 + | 1); + mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2); + mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_X_RESOLUTION, + ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION, + ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION, + ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256); + mTagInfo.put(ExifInterface.TAG_WHITE_POINT, + ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2); + mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES, + ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6); + mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, + ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3); + mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE, + ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6); + mTagInfo.put(ExifInterface.TAG_DATE_TIME, + ifdFlags | ExifTag.TYPE_ASCII << 16 | 20); + mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_MAKE, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_MODEL, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_SOFTWARE, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_ARTIST, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_COPYRIGHT, + ifdFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_EXIF_IFD, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_IFD, + ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + // IFD1 tags + int[] ifd1AllowedIfds = { + IfdId.TYPE_IFD_1 + }; + int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24; + mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, + ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, + ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + // Exif tags + int[] exifAllowedIfds = { + IfdId.TYPE_IFD_EXIF + }; + int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24; + mTagInfo.put(ExifInterface.TAG_EXIF_VERSION, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); + mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); + mTagInfo.put(ExifInterface.TAG_COLOR_SPACE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4); + mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION, + exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, + exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + mTagInfo.put(ExifInterface.TAG_MAKER_NOTE, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_USER_COMMENT, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE, + exifFlags | ExifTag.TYPE_ASCII << 16 | 13); + mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL, + exifFlags | ExifTag.TYPE_ASCII << 16 | 20); + mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED, + exifFlags | ExifTag.TYPE_ASCII << 16 | 20); + mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME, + exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL, + exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED, + exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID, + exifFlags | ExifTag.TYPE_ASCII << 16 | 33); + mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_F_NUMBER, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY, + exifFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_OECF, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, + exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE, + exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE, + exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_METERING_MODE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_FLASH, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2); + mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SENSING_METHOD, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_FILE_SOURCE, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SCENE_TYPE, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1); + mTagInfo.put(ExifInterface.TAG_CFA_PATTERN, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL, + exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_CONTRAST, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SATURATION, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_SHARPNESS, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, + exifFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, + exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1); + mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags + | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1); + // GPS tag + int[] gpsAllowedIfds = { + IfdId.TYPE_IFD_GPS + }; + int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24; + mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID, + gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4); + mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE, + gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3); + mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE, + gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3); + mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF, + gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3); + mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES, + gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_GPS_STATUS, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_DOP, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_SPEED, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_TRACK, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM, + gpsFlags | ExifTag.TYPE_ASCII << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 2); + mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE, + gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1); + mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD, + gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION, + gpsFlags | ExifTag.TYPE_UNDEFINED << 16 | ExifTag.SIZE_UNDEFINED); + mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP, + gpsFlags | ExifTag.TYPE_ASCII << 16 | 11); + mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL, + gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11); + // Interoperability tag + int[] interopAllowedIfds = { + IfdId.TYPE_IFD_INTEROPERABILITY + }; + int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24; + mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16 + | ExifTag.SIZE_UNDEFINED); + } + + protected static int getAllowedIfdFlagsFromInfo(int info) { + return info >>> 24; + } + + protected static int[] getAllowedIfdsFromInfo(int info) { + int ifdFlags = getAllowedIfdFlagsFromInfo(info); + int[] ifds = IfdData.getIfds(); + ArrayList l = new ArrayList(); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + int flag = (ifdFlags >> i) & 1; + if (flag == 1) { + l.add(ifds[i]); + } + } + if (l.size() <= 0) { + return null; + } + int[] ret = new int[l.size()]; + int j = 0; + for (int i : l) { + ret[j++] = i; + } + return ret; + } + + protected static boolean isIfdAllowed(int info, int ifd) { + int[] ifds = IfdData.getIfds(); + int ifdFlags = getAllowedIfdFlagsFromInfo(info); + for (int i = 0; i < ifds.length; i++) { + if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) { + return true; + } + } + return false; + } + + protected static int getFlagsFromAllowedIfds(int[] allowedIfds) { + if (allowedIfds == null || allowedIfds.length == 0) { + return 0; + } + int flags = 0; + int[] ifds = IfdData.getIfds(); + for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { + for (int j : allowedIfds) { + if (ifds[i] == j) { + flags |= 1 << i; + break; + } + } + } + return flags; + } + + protected static short getTypeFromInfo(int info) { + return (short) ((info >> 16) & 0x0ff); + } + + protected static int getComponentCountFromInfo(int info) { + return info & 0x0ffff; + } + +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java index da31a29db..f00362b6b 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java @@ -16,6 +16,8 @@ package com.android.gallery3d.exif; +import android.util.Log; + import java.io.Closeable; import java.io.IOException; import java.io.InputStream; @@ -24,10 +26,13 @@ import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; -public class ExifModifier { +class ExifModifier { + public static final String TAG = "ExifModifier"; + public static final boolean DEBUG = false; private final ByteBuffer mByteBuffer; private final ExifData mTagToModified; private final List mTagOffsets = new ArrayList(); + private final ExifInterface mInterface; private int mOffsetBase; private static class TagOffset { @@ -38,33 +43,31 @@ public class ExifModifier { mTag = tag; mOffset = offset; } - - public ExifTag getTag() { - return mTag; - } } - public ExifModifier(ByteBuffer byteBuffer) throws IOException, ExifInvalidFormatException { + protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException, + ExifInvalidFormatException { mByteBuffer = byteBuffer; mOffsetBase = byteBuffer.position(); + mInterface = iRef; InputStream is = null; try { is = new ByteBufferInputStream(byteBuffer); // Do not require any IFD; - ExifParser parser = ExifParser.parse(is, 0); + ExifParser parser = ExifParser.parse(is, mInterface); mTagToModified = new ExifData(parser.getByteOrder()); mOffsetBase += parser.getTiffStartPosition(); mByteBuffer.position(0); } finally { - closeSilently(is); + ExifInterface.closeSilently(is); } } - public ByteOrder getByteOrder() { + protected ByteOrder getByteOrder() { return mTagToModified.getByteOrder(); } - public boolean commit() throws IOException, ExifInvalidFormatException { + protected boolean commit() throws IOException, ExifInvalidFormatException { InputStream is = null; try { is = new ByteBufferInputStream(mByteBuffer); @@ -77,22 +80,32 @@ public class ExifModifier { mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS) }; - if (ifdDatas[IfdId.TYPE_IFD_0] != null) flag |= ExifParser.OPTION_IFD_0; - if (ifdDatas[IfdId.TYPE_IFD_1] != null) flag |= ExifParser.OPTION_IFD_1; - if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) flag |= ExifParser.OPTION_IFD_EXIF; - if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) flag |= ExifParser.OPTION_IFD_GPS; + if (ifdDatas[IfdId.TYPE_IFD_0] != null) { + flag |= ExifParser.OPTION_IFD_0; + } + if (ifdDatas[IfdId.TYPE_IFD_1] != null) { + flag |= ExifParser.OPTION_IFD_1; + } + if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) { + flag |= ExifParser.OPTION_IFD_EXIF; + } + if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) { + flag |= ExifParser.OPTION_IFD_GPS; + } if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) { flag |= ExifParser.OPTION_IFD_INTEROPERABILITY; } - ExifParser parser = ExifParser.parse(is, flag); + ExifParser parser = ExifParser.parse(is, flag, mInterface); int event = parser.next(); IfdData currIfd = null; while (event != ExifParser.EVENT_END) { switch (event) { case ExifParser.EVENT_START_OF_IFD: currIfd = ifdDatas[parser.getCurrentIfd()]; - if (currIfd == null) parser.skipRemainingTagsInCurrentIfd(); + if (currIfd == null) { + parser.skipRemainingTagsInCurrentIfd(); + } break; case ExifParser.EVENT_NEW_TAG: ExifTag oldTag = parser.getTag(); @@ -113,24 +126,30 @@ public class ExifModifier { } event = parser.next(); } - for (IfdData ifd: ifdDatas) { - if (ifd != null && ifd.getTagCount() > 0) return false; + for (IfdData ifd : ifdDatas) { + if (ifd != null && ifd.getTagCount() > 0) { + return false; + } } modify(); } finally { - closeSilently(is); + ExifInterface.closeSilently(is); } return true; } private void modify() { mByteBuffer.order(getByteOrder()); - for (TagOffset tagOffset: mTagOffsets) { + for (TagOffset tagOffset : mTagOffsets) { writeTagValue(tagOffset.mTag, tagOffset.mOffset); } } private void writeTagValue(ExifTag tag, int offset) { + if (DEBUG) { + Log.v(TAG, "modifying tag to: \n" + tag.toString()); + Log.v(TAG, "at offset: " + offset); + } mByteBuffer.position(offset + mOffsetBase); switch (tag.getDataType()) { case ExifTag.TYPE_ASCII: @@ -153,7 +172,7 @@ public class ExifModifier { case ExifTag.TYPE_UNSIGNED_RATIONAL: for (int i = 0, n = tag.getComponentCount(); i < n; i++) { Rational v = tag.getRational(i); - mByteBuffer.putInt((int) v.getNominator()); + mByteBuffer.putInt((int) v.getNumerator()); mByteBuffer.putInt((int) v.getDenominator()); } break; @@ -174,13 +193,4 @@ public class ExifModifier { public void modifyTag(ExifTag tag) { mTagToModified.addTag(tag); } - - private static void closeSilently(Closeable c) { - if (c == null) return; - try { - c.close(); - } catch (Throwable t) { - // do nothing - } - } } diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java index f34321b82..6ea655431 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifOutputStream.java @@ -16,6 +16,8 @@ package com.android.gallery3d.exif; +import android.util.Log; + import java.io.FilterOutputStream; import java.io.IOException; import java.io.OutputStream; @@ -26,16 +28,15 @@ import java.nio.ByteOrder; * This class provides a way to replace the Exif header of a JPEG image. *

* Below is an example of writing EXIF data into a file + * *

  * public static void writeExif(byte[] jpeg, ExifData exif, String path) {
  *     OutputStream os = null;
  *     try {
  *         os = new FileOutputStream(path);
  *         ExifOutputStream eos = new ExifOutputStream(os);
- *
  *         // Set the exif header
  *         eos.setExifData(exif);
- *
  *         // Write the original jpeg out, the header will be add into the file.
  *         eos.write(jpeg);
  *     } catch (FileNotFoundException e) {
@@ -54,8 +55,9 @@ import java.nio.ByteOrder;
  * }
  * 
*/ -public class ExifOutputStream extends FilterOutputStream { +class ExifOutputStream extends FilterOutputStream { private static final String TAG = "ExifOutputStream"; + private static final boolean DEBUG = false; private static final int STATE_SOI = 0; private static final int STATE_FRAME_HEADER = 1; @@ -67,29 +69,32 @@ public class ExifOutputStream extends FilterOutputStream { private static final short TIFF_LITTLE_ENDIAN = 0x4949; private static final short TAG_SIZE = 12; private static final short TIFF_HEADER_SIZE = 8; + private static final int MAX_EXIF_SIZE = 65535; private ExifData mExifData; private int mState = STATE_SOI; private int mByteToSkip; private int mByteToCopy; private ByteBuffer mBuffer = ByteBuffer.allocate(4); + private final ExifInterface mInterface; - public ExifOutputStream(OutputStream ou) { + protected ExifOutputStream(OutputStream ou, ExifInterface iRef) { super(ou); + mInterface = iRef; } /** - * Sets the ExifData to be written into the JPEG file. Should be called before writing image - * data. + * Sets the ExifData to be written into the JPEG file. Should be called + * before writing image data. */ - public void setExifData(ExifData exifData) { + protected void setExifData(ExifData exifData) { mExifData = exifData; } /** * Gets the Exif header to be written into the JPEF file. */ - public ExifData getExifData() { + protected ExifData getExifData() { return mExifData; } @@ -102,12 +107,12 @@ public class ExifOutputStream extends FilterOutputStream { } /** - * Writes the image out. The input data should be a valid JPEG format. After writing, it's - * Exif header will be replaced by the given header. + * Writes the image out. The input data should be a valid JPEG format. After + * writing, it's Exif header will be replaced by the given header. */ @Override public void write(byte[] buffer, int offset, int length) throws IOException { - while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) + while ((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA) && length > 0) { if (mByteToSkip > 0) { int byteToProcess = length > mByteToSkip ? mByteToSkip : length; @@ -122,22 +127,29 @@ public class ExifOutputStream extends FilterOutputStream { mByteToCopy -= byteToProcess; offset += byteToProcess; } - if (length == 0) return; + if (length == 0) { + return; + } switch (mState) { case STATE_SOI: int byteRead = requestByteToBuffer(2, buffer, offset, length); offset += byteRead; length -= byteRead; - if (mBuffer.position() < 2) return; + if (mBuffer.position() < 2) { + return; + } mBuffer.rewind(); - assert(mBuffer.getShort() == JpegHeader.SOI); - out.write(mBuffer.array(), 0 ,2); + if (mBuffer.getShort() != JpegHeader.SOI) { + throw new IOException("Not a valid jpeg image, cannot write exif"); + } + out.write(mBuffer.array(), 0, 2); mState = STATE_FRAME_HEADER; mBuffer.rewind(); writeExifData(); break; case STATE_FRAME_HEADER: - // We ignore the APP1 segment and copy all other segments until SOF tag. + // We ignore the APP1 segment and copy all other segments + // until SOF tag. byteRead = requestByteToBuffer(4, buffer, offset, length); offset += byteRead; length -= byteRead; @@ -149,15 +161,17 @@ public class ExifOutputStream extends FilterOutputStream { mBuffer.rewind(); } } - if (mBuffer.position() < 4) return; + if (mBuffer.position() < 4) { + return; + } mBuffer.rewind(); short marker = mBuffer.getShort(); if (marker == JpegHeader.APP1) { - mByteToSkip = (mBuffer.getShort() & 0xff) - 2; + mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2; mState = STATE_JPEG_DATA; } else if (!JpegHeader.isSofMarker(marker)) { out.write(mBuffer.array(), 0, 4); - mByteToCopy = (mBuffer.getShort() & 0xff) - 2; + mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2; } else { out.write(mBuffer.array(), 0, 4); mState = STATE_JPEG_DATA; @@ -171,12 +185,14 @@ public class ExifOutputStream extends FilterOutputStream { } /** - * Writes the one bytes out. The input data should be a valid JPEG format. After writing, it's - * Exif header will be replaced by the given header. + * Writes the one bytes out. The input data should be a valid JPEG format. + * After writing, it's Exif header will be replaced by the given header. */ @Override public void write(int oneByte) throws IOException { - byte[] buf = new byte[] {(byte) (0xff & oneByte)}; + byte[] buf = new byte[] { + (byte) (0xff & oneByte) + }; write(buf); } @@ -189,9 +205,17 @@ public class ExifOutputStream extends FilterOutputStream { } private void writeExifData() throws IOException { - if (mExifData == null) return; + if (mExifData == null) { + return; + } + if (DEBUG) { + Log.v(TAG, "Writing exif data..."); + } createRequiredIfdAndTag(); int exifSize = calculateAllOffset(); + if (exifSize + 8 > MAX_EXIF_SIZE) { + throw new IOException("Exif header is too large (>64Kb)"); + } OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out); dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN); dataOutputStream.writeShort(JpegHeader.APP1); @@ -241,70 +265,34 @@ public class ExifOutputStream extends FilterOutputStream { throws IOException { ExifTag[] tags = ifd.getAllTags(); dataOutputStream.writeShort((short) tags.length); - for (ExifTag tag: tags) { + for (ExifTag tag : tags) { dataOutputStream.writeShort(tag.getTagId()); dataOutputStream.writeShort(tag.getDataType()); dataOutputStream.writeInt(tag.getComponentCount()); + if (DEBUG) { + Log.v(TAG, "\n" + tag.toString()); + } if (tag.getDataSize() > 4) { dataOutputStream.writeInt(tag.getOffset()); } else { - writeTagValue(tag, dataOutputStream); + ExifOutputStream.writeTagValue(tag, dataOutputStream); for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) { dataOutputStream.write(0); } } } dataOutputStream.writeInt(ifd.getOffsetToNextIfd()); - for (ExifTag tag: tags) { + for (ExifTag tag : tags) { if (tag.getDataSize() > 4) { - writeTagValue(tag, dataOutputStream); + ExifOutputStream.writeTagValue(tag, dataOutputStream); } } } - private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream) - throws IOException { - switch (tag.getDataType()) { - case ExifTag.TYPE_ASCII: - byte buf[] = tag.getStringByte(); - if (buf.length == tag.getComponentCount()) { - buf[buf.length - 1] = 0; - dataOutputStream.write(buf); - } else { - dataOutputStream.write(buf); - dataOutputStream.write(0); - } - break; - case ExifTag.TYPE_LONG: - case ExifTag.TYPE_UNSIGNED_LONG: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeInt((int) tag.getValueAt(i)); - } - break; - case ExifTag.TYPE_RATIONAL: - case ExifTag.TYPE_UNSIGNED_RATIONAL: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeRational(tag.getRational(i)); - } - break; - case ExifTag.TYPE_UNDEFINED: - case ExifTag.TYPE_UNSIGNED_BYTE: - buf = new byte[tag.getComponentCount()]; - tag.getBytes(buf); - dataOutputStream.write(buf); - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - for (int i = 0, n = tag.getComponentCount(); i < n; i++) { - dataOutputStream.writeShort((short) tag.getValueAt(i)); - } - break; - } - } - private int calculateOffsetOfIfd(IfdData ifd, int offset) { offset += 2 + ifd.getTagCount() * TAG_SIZE + 4; ExifTag[] tags = ifd.getAllTags(); - for(ExifTag tag: tags) { + for (ExifTag tag : tags) { if (tag.getDataSize() > 4) { tag.setOffset(offset); offset += tag.getDataSize(); @@ -313,18 +301,21 @@ public class ExifOutputStream extends FilterOutputStream { return offset; } - private void createRequiredIfdAndTag() { + private void createRequiredIfdAndTag() throws IOException { // IFD0 is required for all file IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0); if (ifd0 == null) { ifd0 = new IfdData(IfdId.TYPE_IFD_0); mExifData.addIfdData(ifd0); } - ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD, - ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0); + ExifTag exifOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD); + if (exifOffsetTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_EXIF_IFD); + } ifd0.setTag(exifOffsetTag); - // Exif IFD is required for all file. + // Exif IFD is required for all files. IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF); if (exifIfd == null) { exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF); @@ -334,16 +325,23 @@ public class ExifOutputStream extends FilterOutputStream { // GPS IFD IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); if (gpsIfd != null) { - ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD, - ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0); + ExifTag gpsOffsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD); + if (gpsOffsetTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_GPS_IFD); + } ifd0.setTag(gpsOffsetTag); } // Interoperability IFD IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); if (interIfd != null) { - ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD, - ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF); + ExifTag interOffsetTag = mInterface + .buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD); + if (interOffsetTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_INTEROPERABILITY_IFD); + } exifIfd.setTag(interOffsetTag); } @@ -351,27 +349,50 @@ public class ExifOutputStream extends FilterOutputStream { // thumbnail if (mExifData.hasCompressedThumbnail()) { + if (ifd1 == null) { ifd1 = new IfdData(IfdId.TYPE_IFD_1); mExifData.addIfdData(ifd1); } - ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT, - ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1); + + ExifTag offsetTag = mInterface + .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); + if (offsetTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); + } + ifd1.setTag(offsetTag); - ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, - ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1); + ExifTag lengthTag = mInterface + .buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + if (lengthTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + } + lengthTag.setValue(mExifData.getCompressedThumbnail().length); ifd1.setTag(lengthTag); - } else if (mExifData.hasUncompressedStrip()){ + + // Get rid of tags for uncompressed if they exist. + ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)); + ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); + } else if (mExifData.hasUncompressedStrip()) { if (ifd1 == null) { ifd1 = new IfdData(IfdId.TYPE_IFD_1); mExifData.addIfdData(ifd1); } int stripCount = mExifData.getStripCount(); - ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS, - ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1); - ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS, - ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1); + ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS); + if (offsetTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_STRIP_OFFSETS); + } + ExifTag lengthTag = mInterface + .buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS); + if (lengthTag == null) { + throw new IOException("No definition for crucial exif tag: " + + ExifInterface.TAG_STRIP_BYTE_COUNTS); + } long[] lengths = new long[stripCount]; for (int i = 0; i < mExifData.getStripCount(); i++) { lengths[i] = mExifData.getStrip(i).length; @@ -379,6 +400,17 @@ public class ExifOutputStream extends FilterOutputStream { lengthTag.setValue(lengths); ifd1.setTag(offsetTag); ifd1.setTag(lengthTag); + // Get rid of tags for compressed if they exist. + ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)); + ifd1.removeTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); + } else { + // Get rid of offset and length tags if there is no thumbnail. + ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)); + ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS)); + ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)); + ifd1.removeTag(ExifInterface + .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)); } } @@ -386,20 +418,21 @@ public class ExifOutputStream extends FilterOutputStream { int offset = TIFF_HEADER_SIZE; IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0); offset = calculateOffsetOfIfd(ifd0, offset); - ifd0.getTag(ExifTag.TAG_EXIF_IFD).setValue(offset); + ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_EXIF_IFD)).setValue(offset); IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF); offset = calculateOffsetOfIfd(exifIfd, offset); IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); if (interIfd != null) { - exifIfd.getTag(ExifTag.TAG_INTEROPERABILITY_IFD).setValue(offset); + exifIfd.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD)) + .setValue(offset); offset = calculateOffsetOfIfd(interIfd, offset); } IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS); if (gpsIfd != null) { - ifd0.getTag(ExifTag.TAG_GPS_IFD).setValue(offset); + ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD)).setValue(offset); offset = calculateOffsetOfIfd(gpsIfd, offset); } @@ -411,17 +444,58 @@ public class ExifOutputStream extends FilterOutputStream { // thumbnail if (mExifData.hasCompressedThumbnail()) { - ifd1.getTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset); + ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) + .setValue(offset); offset += mExifData.getCompressedThumbnail().length; - } else if (mExifData.hasUncompressedStrip()){ + } else if (mExifData.hasUncompressedStrip()) { int stripCount = mExifData.getStripCount(); long[] offsets = new long[stripCount]; for (int i = 0; i < mExifData.getStripCount(); i++) { offsets[i] = offset; offset += mExifData.getStrip(i).length; } - ifd1.getTag(ExifTag.TAG_STRIP_OFFSETS).setValue(offsets); + ifd1.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue( + offsets); } return offset; } -} \ No newline at end of file + + static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream) + throws IOException { + switch (tag.getDataType()) { + case ExifTag.TYPE_ASCII: + byte buf[] = tag.getStringByte(); + if (buf.length == tag.getComponentCount()) { + buf[buf.length - 1] = 0; + dataOutputStream.write(buf); + } else { + dataOutputStream.write(buf); + dataOutputStream.write(0); + } + break; + case ExifTag.TYPE_LONG: + case ExifTag.TYPE_UNSIGNED_LONG: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + dataOutputStream.writeInt((int) tag.getValueAt(i)); + } + break; + case ExifTag.TYPE_RATIONAL: + case ExifTag.TYPE_UNSIGNED_RATIONAL: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + dataOutputStream.writeRational(tag.getRational(i)); + } + break; + case ExifTag.TYPE_UNDEFINED: + case ExifTag.TYPE_UNSIGNED_BYTE: + buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + dataOutputStream.write(buf); + break; + case ExifTag.TYPE_UNSIGNED_SHORT: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + dataOutputStream.writeShort((short) tag.getValueAt(i)); + } + break; + } + } +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java index 1e4803f68..b6d7e61dd 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java @@ -26,10 +26,13 @@ import java.util.Map.Entry; import java.util.TreeMap; /** - * This class provides a low-level EXIF parsing API. Given a JPEG format InputStream, the caller - * can request which IFD's to read via {@link #parse(InputStream, int)} with given options. + * This class provides a low-level EXIF parsing API. Given a JPEG format + * InputStream, the caller can request which IFD's to read via + * {@link #parse(InputStream, int)} with given options. *

- * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the parser. + * Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the + * parser. + * *

  * void parse() {
  *     ExifParser parser = ExifParser.parse(mImageInputStream,
@@ -63,11 +66,12 @@ import java.util.TreeMap;
  * }
  * 
*/ -public class ExifParser { +class ExifParser { + private static final boolean LOGV = false; private static final String TAG = "ExifParser"; /** - * When the parser reaches a new IFD area. Call - * {@link #getCurrentIfd()} to know which IFD we are in. + * When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to + * know which IFD we are in. */ public static final int EVENT_START_OF_IFD = 0; /** @@ -77,8 +81,8 @@ public class ExifParser { public static final int EVENT_NEW_TAG = 1; /** * When the parser reaches the value area of tag that is registered by - * {@link #registerForTagValue(ExifTag)} previously. Call - * {@link #getTag()} to get the corresponding tag. + * {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()} + * to get the corresponding tag. */ public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2; @@ -87,8 +91,9 @@ public class ExifParser { */ public static final int EVENT_COMPRESSED_IMAGE = 3; /** - * When the parser reaches the uncompressed image strip. - * Call {@link #getStripIndex()} to get the index of the strip. + * When the parser reaches the uncompressed image strip. Call + * {@link #getStripIndex()} to get the index of the strip. + * * @see #getStripIndex() * @see #getStripCount() */ @@ -123,20 +128,20 @@ public class ExifParser { */ public static final int OPTION_THUMBNAIL = 1 << 5; - private static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif" - private static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1 + protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif" + protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1 // TIFF header - private static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II" - private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM" - private static final short TIFF_HEADER_TAIL = 0x002A; + protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II" + protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM" + protected static final short TIFF_HEADER_TAIL = 0x002A; - private static final int TAG_SIZE = 12; - private static final int OFFSET_SIZE = 2; + protected static final int TAG_SIZE = 12; + protected static final int OFFSET_SIZE = 2; private static final Charset US_ASCII = Charset.forName("US-ASCII"); - private static final int DEFAULT_IFD0_OFFSET = 8; + protected static final int DEFAULT_IFD0_OFFSET = 8; private final CountedDataInputStream mTiffStream; private final int mOptions; @@ -151,9 +156,25 @@ public class ExifParser { private boolean mNeedToParseOffsetsInCurrentIfd; private boolean mContainExifData = false; private int mApp1End; + private int mOffsetToApp1EndFromSOF = 0; private byte[] mDataAboveIfd0; private int mIfd0Position; private int mTiffStartPosition; + private final ExifInterface mInterface; + + private static final short TAG_EXIF_IFD = ExifInterface + .getTrueTagKey(ExifInterface.TAG_EXIF_IFD); + private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD); + private static final short TAG_INTEROPERABILITY_IFD = ExifInterface + .getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD); + private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface + .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT); + private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface + .getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH); + private static final short TAG_STRIP_OFFSETS = ExifInterface + .getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS); + private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface + .getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS); private final TreeMap mCorrespondingEvent = new TreeMap(); @@ -177,15 +198,21 @@ public class ExifParser { return (mOptions & OPTION_THUMBNAIL) != 0; } - private ExifParser(InputStream inputStream, int options) + private ExifParser(InputStream inputStream, int options, ExifInterface iRef) throws IOException, ExifInvalidFormatException { if (inputStream == null) { throw new IOException("Null argument inputStream to ExifParser"); } + if (LOGV) { + Log.v(TAG, "Reading exif..."); + } + mInterface = iRef; mContainExifData = seekTiffData(inputStream); mTiffStream = new CountedDataInputStream(inputStream); mOptions = options; - if (!mContainExifData) return; + if (!mContainExifData) { + return; + } parseTiffHeader(); long offset = mTiffStream.readUnsignedInt(); @@ -205,26 +232,28 @@ public class ExifParser { /** * Parses the the given InputStream with the given options + * * @exception IOException * @exception ExifInvalidFormatException */ - public static ExifParser parse(InputStream inputStream, int options) + protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef) throws IOException, ExifInvalidFormatException { - return new ExifParser(inputStream, options); + return new ExifParser(inputStream, options, iRef); } /** - * Parses the the given InputStream with default options; that is, every IFD and thumbnaill - * will be parsed. + * Parses the the given InputStream with default options; that is, every IFD + * and thumbnaill will be parsed. + * * @exception IOException * @exception ExifInvalidFormatException * @see #parse(InputStream, int) */ - public static ExifParser parse(InputStream inputStream) + protected static ExifParser parse(InputStream inputStream, ExifInterface iRef) throws IOException, ExifInvalidFormatException { return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1 | OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY - | OPTION_THUMBNAIL); + | OPTION_THUMBNAIL, iRef); } /** @@ -239,7 +268,7 @@ public class ExifParser { * @see #EVENT_UNCOMPRESSED_STRIP * @see #EVENT_END */ - public int next() throws IOException, ExifInvalidFormatException { + protected int next() throws IOException, ExifInvalidFormatException { if (!mContainExifData) { return EVENT_END; } @@ -280,7 +309,7 @@ public class ExifParser { } } } - while(mCorrespondingEvent.size() != 0) { + while (mCorrespondingEvent.size() != 0) { Entry entry = mCorrespondingEvent.pollFirstEntry(); Object event = entry.getValue(); try { @@ -325,21 +354,25 @@ public class ExifParser { } /** - * Skips the tags area of current IFD, if the parser is not in the tag area, nothing will - * happen. + * Skips the tags area of current IFD, if the parser is not in the tag area, + * nothing will happen. * * @throws IOException * @throws ExifInvalidFormatException */ - public void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException { + protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException { int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd; int offset = mTiffStream.getReadByteCount(); - if (offset > endOfTags) return; + if (offset > endOfTags) { + return; + } if (mNeedToParseOffsetsInCurrentIfd) { while (offset < endOfTags) { mTag = readTag(); offset += TAG_SIZE; - if (mTag == null) continue; + if (mTag == null) { + continue; + } checkOffsetOrImageTag(mTag); } } else { @@ -372,20 +405,20 @@ public class ExifParser { } /** - * If {@link #next()} return {@link #EVENT_NEW_TAG} or {@link #EVENT_VALUE_OF_REGISTERED_TAG}, - * call this function to get the corresponding tag. + * If {@link #next()} return {@link #EVENT_NEW_TAG} or + * {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the + * corresponding tag. *

- * - * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size of the value is - * greater than 4 bytes. One should call {@link ExifTag#hasValue()} to check if the tag - * contains value. - * If there is no value,call {@link #registerForTagValue(ExifTag)} to have the parser emit - * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area pointed by the offset. - * + * For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size + * of the value is greater than 4 bytes. One should call + * {@link ExifTag#hasValue()} to check if the tag contains value. If there + * is no value,call {@link #registerForTagValue(ExifTag)} to have the parser + * emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area + * pointed by the offset. *

- * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the tag will have - * already been read except for tags of undefined type. For tags of undefined type, call - * one of the read methods to get the value. + * When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the + * tag will have already been read except for tags of undefined type. For + * tags of undefined type, call one of the read methods to get the value. * * @see #registerForTagValue(ExifTag) * @see #read(byte[]) @@ -395,14 +428,14 @@ public class ExifParser { * @see #readString(int) * @see #readString(int, Charset) */ - public ExifTag getTag() { + protected ExifTag getTag() { return mTag; } /** * Gets number of tags in the current IFD area. */ - public int getTagCountInCurrentIfd() { + protected int getTagCountInCurrentIfd() { return mNumOfTagInIfd; } @@ -415,42 +448,48 @@ public class ExifParser { * @see IfdId#TYPE_IFD_INTEROPERABILITY * @see IfdId#TYPE_IFD_EXIF */ - public int getCurrentIfd() { + protected int getCurrentIfd() { return mIfdType; } /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, - * call this function to get the index of this strip. + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to + * get the index of this strip. + * * @see #getStripCount() */ - public int getStripIndex() { + protected int getStripIndex() { return mImageEvent.stripIndex; } /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the number - * of strip data. + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to + * get the number of strip data. + * * @see #getStripIndex() */ - public int getStripCount() { + protected int getStripCount() { return mStripCount; } /** - * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to get the strip size. + * When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to + * get the strip size. */ - public int getStripSize() { - if (mStripSizeTag == null) return 0; + protected int getStripSize() { + if (mStripSizeTag == null) + return 0; return (int) mStripSizeTag.getValueAt(0); } /** - * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get the image data - * size. + * When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get + * the image data size. */ - public int getCompressedImageSize() { - if (mJpegSizeTag == null) return 0; + protected int getCompressedImageSize() { + if (mJpegSizeTag == null) { + return 0; + } return (int) mJpegSizeTag.getValueAt(0); } @@ -462,13 +501,15 @@ public class ExifParser { } /** - * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, - * the tag may not contain the value if the size of the value is greater than 4 bytes. - * When the value is not available here, call this method so that the parser will emit - * {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area where the value is located. + * When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may + * not contain the value if the size of the value is greater than 4 bytes. + * When the value is not available here, call this method so that the parser + * will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area + * where the value is located. + * * @see #EVENT_VALUE_OF_REGISTERED_TAG */ - public void registerForTagValue(ExifTag tag) { + protected void registerForTagValue(ExifTag tag) { mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true)); } @@ -501,7 +542,9 @@ public class ExifParser { mTiffStream.skip(4); return null; } - ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType); + // TODO: handle numOfComp overflow + ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType, + ((int) numOfComp) != ExifTag.SIZE_UNDEFINED); int dataSize = tag.getDataSize(); if (dataSize > 4) { long offset = mTiffStream.readUnsignedInt(); @@ -529,146 +572,154 @@ public class ExifParser { } /** - * Check the tag, if the tag is one of the offset tag that points to the IFD or image the - * caller is interested in, register the IFD or image. + * Check the tag, if the tag is one of the offset tag that points to the IFD + * or image the caller is interested in, register the IFD or image. */ private void checkOffsetOrImageTag(ExifTag tag) { // Some invalid formattd image contains tag with 0 size. if (tag.getComponentCount() == 0) { return; } - switch (tag.getTagId()) { - case ExifTag.TAG_EXIF_IFD: - if (isIfdRequested(IfdId.TYPE_IFD_EXIF) - || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); - } - break; - case ExifTag.TAG_GPS_IFD: - if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { - registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); - } - break; - case ExifTag.TAG_INTEROPERABILITY_IFD: - if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { - registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); - } - break; - case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT: - if (isThumbnailRequested()) { - registerCompressedImage(tag.getValueAt(0)); - } - break; - case ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH: - if (isThumbnailRequested()) { - mJpegSizeTag = tag; - } - break; - case ExifTag.TAG_STRIP_OFFSETS: - if (isThumbnailRequested()) { - if (tag.hasValue()) { - for (int i = 0; i < tag.getComponentCount(); i++) { - if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { - registerUncompressedStrip(i, tag.getValueAt(i)); - } else { - registerUncompressedStrip(i, tag.getValueAt(i)); - } + short tid = tag.getTagId(); + int ifd = tag.getIfd(); + if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) { + if (isIfdRequested(IfdId.TYPE_IFD_EXIF) + || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { + registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0)); + } + } else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) { + if (isIfdRequested(IfdId.TYPE_IFD_GPS)) { + registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0)); + } + } else if (tid == TAG_INTEROPERABILITY_IFD + && checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) { + if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) { + registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0)); + } + } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT + && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) { + if (isThumbnailRequested()) { + registerCompressedImage(tag.getValueAt(0)); + } + } else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH + && checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) { + if (isThumbnailRequested()) { + mJpegSizeTag = tag; + } + } else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) { + if (isThumbnailRequested()) { + if (tag.hasValue()) { + for (int i = 0; i < tag.getComponentCount(); i++) { + if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) { + registerUncompressedStrip(i, tag.getValueAt(i)); + } else { + registerUncompressedStrip(i, tag.getValueAt(i)); } - } else { - mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false)); - } - } - break; - case ExifTag.TAG_STRIP_BYTE_COUNTS: - if (isThumbnailRequested()) { - if (tag.hasValue()) { - mStripSizeTag = tag; } + } else { + mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false)); } - break; + } + } else if (tid == TAG_STRIP_BYTE_COUNTS + && checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS) + &&isThumbnailRequested() && tag.hasValue()) { + mStripSizeTag = tag; + } + } + + private boolean checkAllowed(int ifd, int tagId) { + int info = mInterface.getTagInfo().get(tagId); + if (info == ExifInterface.DEFINITION_NULL) { + return false; } + return ExifInterface.isIfdAllowed(info, ifd); } - void readFullTagValue(ExifTag tag) throws IOException { + protected void readFullTagValue(ExifTag tag) throws IOException { // Some invalid images contains tags with wrong size, check it here short type = tag.getDataType(); if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED || type == ExifTag.TYPE_UNSIGNED_BYTE) { int size = tag.getComponentCount(); if (mCorrespondingEvent.size() > 0) { - if (mCorrespondingEvent.firstEntry().getKey() < - mTiffStream.getReadByteCount() + size) { - Log.w(TAG, "Invalid size of tag."); - size = mCorrespondingEvent.firstEntry().getKey() - - mTiffStream.getReadByteCount(); - tag.setComponentCount(size); + if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount() + + size) { + if (mCorrespondingEvent.firstEntry().getValue() instanceof ImageEvent) { + // Invalid thumbnail offset: tag metadata overlaps with + // strip. + Entry entry = mCorrespondingEvent.pollFirstEntry(); + // Ignore thumbnail. + Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey()); + } else { + size = mCorrespondingEvent.firstEntry().getKey() + - mTiffStream.getReadByteCount(); + Log.w(TAG, "Invalid size of tag: \n" + tag.toString() + + " setting count to: " + size); + tag.forceSetComponentCount(size); + } } } } - switch(tag.getDataType()) { + switch (tag.getDataType()) { case ExifTag.TYPE_UNSIGNED_BYTE: - case ExifTag.TYPE_UNDEFINED: - { - byte buf[] = new byte[tag.getComponentCount()]; - read(buf); - tag.setValue(buf); - } + case ExifTag.TYPE_UNDEFINED: { + byte buf[] = new byte[tag.getComponentCount()]; + read(buf); + tag.setValue(buf); + } break; case ExifTag.TYPE_ASCII: tag.setValue(readString(tag.getComponentCount())); break; - case ExifTag.TYPE_UNSIGNED_LONG: - { - long value[] = new long[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedLong(); - } - tag.setValue(value); + case ExifTag.TYPE_UNSIGNED_LONG: { + long value[] = new long[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedLong(); } + tag.setValue(value); + } break; - case ExifTag.TYPE_UNSIGNED_RATIONAL: - { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedRational(); - } - tag.setValue(value); + case ExifTag.TYPE_UNSIGNED_RATIONAL: { + Rational value[] = new Rational[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedRational(); } + tag.setValue(value); + } break; - case ExifTag.TYPE_UNSIGNED_SHORT: - { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedShort(); - } - tag.setValue(value); + case ExifTag.TYPE_UNSIGNED_SHORT: { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedShort(); } + tag.setValue(value); + } break; - case ExifTag.TYPE_LONG: - { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readLong(); - } - tag.setValue(value); + case ExifTag.TYPE_LONG: { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readLong(); } + tag.setValue(value); + } break; - case ExifTag.TYPE_RATIONAL: - { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readRational(); - } - tag.setValue(value); + case ExifTag.TYPE_RATIONAL: { + Rational value[] = new Rational[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readRational(); } + tag.setValue(value); + } break; } + if (LOGV) { + Log.v(TAG, "\n" + tag.toString()); + } } private void parseTiffHeader() throws IOException, ExifInvalidFormatException { short byteOrder = mTiffStream.readShort(); - ByteOrder order; if (LITTLE_ENDIAN_TAG == byteOrder) { mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN); } else if (BIG_ENDIAN_TAG == byteOrder) { @@ -690,7 +741,7 @@ public class ExifParser { } short marker = dataStream.readShort(); - while(marker != JpegHeader.EOI + while (marker != JpegHeader.EOI && !JpegHeader.isSofMarker(marker)) { int length = dataStream.readUnsignedShort(); // Some invalid formatted image contains multiple APP1, @@ -705,6 +756,7 @@ public class ExifParser { if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { mTiffStartPosition = dataStream.getReadByteCount(); mApp1End = length; + mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End; return true; } } @@ -718,41 +770,44 @@ public class ExifParser { return false; } - int getTiffStartPosition() { + protected int getOffsetToExifEndFromSOF() { + return mOffsetToApp1EndFromSOF; + } + + protected int getTiffStartPosition() { return mTiffStartPosition; } /** * Reads bytes from the InputStream. */ - public int read(byte[] buffer, int offset, int length) throws IOException { + protected int read(byte[] buffer, int offset, int length) throws IOException { return mTiffStream.read(buffer, offset, length); } /** * Equivalent to read(buffer, 0, buffer.length). */ - public int read(byte[] buffer) throws IOException { + protected int read(byte[] buffer) throws IOException { return mTiffStream.read(buffer); } /** - * Reads a String from the InputStream with US-ASCII charset. - * The parser will read n bytes and convert it to ascii string. - * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. + * Reads a String from the InputStream with US-ASCII charset. The parser + * will read n bytes and convert it to ascii string. This is used for + * reading values of type {@link ExifTag#TYPE_ASCII}. */ - public String readString(int n) throws IOException { + protected String readString(int n) throws IOException { return readString(n, US_ASCII); } /** - * Reads a String from the InputStream with the given charset. - * The parser will read n bytes and convert it to string. - * This is used for reading values of type {@link ExifTag#TYPE_ASCII}. + * Reads a String from the InputStream with the given charset. The parser + * will read n bytes and convert it to string. This is used for reading + * values of type {@link ExifTag#TYPE_ASCII}. */ - public String readString(int n, Charset charset) throws IOException { + protected String readString(int n, Charset charset) throws IOException { if (n > 0) { - byte[] buf = new byte[n]; return mTiffStream.readString(n, charset); } else { return ""; @@ -760,23 +815,26 @@ public class ExifParser { } /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the InputStream. + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the + * InputStream. */ - public int readUnsignedShort() throws IOException { + protected int readUnsignedShort() throws IOException { return mTiffStream.readShort() & 0xffff; } /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the InputStream. + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the + * InputStream. */ - public long readUnsignedLong() throws IOException { + protected long readUnsignedLong() throws IOException { return readLong() & 0xffffffffL; } /** - * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the InputStream. + * Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the + * InputStream. */ - public Rational readUnsignedRational() throws IOException { + protected Rational readUnsignedRational() throws IOException { long nomi = readUnsignedLong(); long denomi = readUnsignedLong(); return new Rational(nomi, denomi); @@ -785,14 +843,14 @@ public class ExifParser { /** * Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream. */ - public int readLong() throws IOException { + protected int readLong() throws IOException { return mTiffStream.readInt(); } /** * Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream. */ - public Rational readRational() throws IOException { + protected Rational readRational() throws IOException { int nomi = readLong(); int denomi = readLong(); return new Rational(nomi, denomi); @@ -801,10 +859,12 @@ public class ExifParser { private static class ImageEvent { int stripIndex; int type; + ImageEvent(int type) { this.stripIndex = 0; this.type = type; } + ImageEvent(int type, int stripIndex) { this.type = type; this.stripIndex = stripIndex; @@ -814,6 +874,7 @@ public class ExifParser { private static class IfdEvent { int ifd; boolean isRequested; + IfdEvent(int ifd, boolean isInterestedIfd) { this.ifd = ifd; this.isRequested = isInterestedIfd; @@ -823,6 +884,7 @@ public class ExifParser { private static class ExifTagEvent { ExifTag tag; boolean isRequested; + ExifTagEvent(ExifTag tag, boolean isRequireByUser) { this.tag = tag; this.isRequested = isRequireByUser; @@ -832,7 +894,7 @@ public class ExifParser { /** * Gets the byte order of the current InputStream. */ - public ByteOrder getByteOrder() { + protected ByteOrder getByteOrder() { return mTiffStream.getByteOrder(); } } diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java index 5bce9c496..68e972fb7 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifReader.java @@ -22,19 +22,30 @@ import java.io.IOException; import java.io.InputStream; /** - * This class reads the EXIF header of a JPEG file and stores it in {@link ExifData}. + * This class reads the EXIF header of a JPEG file and stores it in + * {@link ExifData}. */ -public class ExifReader { +class ExifReader { private static final String TAG = "ExifReader"; + + private final ExifInterface mInterface; + + ExifReader(ExifInterface iRef) { + mInterface = iRef; + } + /** - * Parses the inputStream and and returns the EXIF data in an {@link ExifData}. + * Parses the inputStream and and returns the EXIF data in an + * {@link ExifData}. + * * @throws ExifInvalidFormatException * @throws IOException */ - public ExifData read(InputStream inputStream) throws ExifInvalidFormatException, + protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException, IOException { - ExifParser parser = ExifParser.parse(inputStream); + ExifParser parser = ExifParser.parse(inputStream, mInterface); ExifData exifData = new ExifData(parser.getByteOrder()); + ExifTag tag = null; int event = parser.next(); while (event != ExifParser.EVENT_END) { @@ -43,7 +54,7 @@ public class ExifReader { exifData.addIfdData(new IfdData(parser.getCurrentIfd())); break; case ExifParser.EVENT_NEW_TAG: - ExifTag tag = parser.getTag(); + tag = parser.getTag(); if (!tag.hasValue()) { parser.registerForTagValue(tag); } else { @@ -69,6 +80,7 @@ public class ExifReader { buf = new byte[parser.getStripSize()]; if (buf.length == parser.read(buf)) { exifData.setStripBytes(parser.getStripIndex(), buf); + } else { Log.w(TAG, "Failed to read the strip bytes"); } break; diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java index 753b18ce8..0b31c4df5 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java @@ -16,488 +16,26 @@ package com.android.gallery3d.exif; -import android.util.SparseArray; - import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; /** - * This class stores information of an EXIF tag. - * @see ExifParser - * @see ExifReader - * @see IfdData - * @see ExifData + * This class stores information of an EXIF tag. For more information about + * defined EXIF tags, please read the Jeita EXIF 2.2 standard. Tags should be + * instantiated using {@link ExifInterface#buildTag}. + * + * @see ExifInterface */ public class ExifTag { - // Tiff Tags - public static final short TAG_IMAGE_WIDTH = 0x100; - /* - * The height of the image. - */ - public static final short TAG_IMAGE_LENGTH = 0x101; - public static final short TAG_BITS_PER_SAMPLE = 0x102; - public static final short TAG_COMPRESSION = 0x103; - public static final short TAG_PHOTOMETRIC_INTERPRETATION = 0x106; - public static final short TAG_IMAGE_DESCRIPTION = 0x10E; - public static final short TAG_MAKE = 0x10F; - public static final short TAG_MODEL = 0x110; - public static final short TAG_STRIP_OFFSETS = 0x111; - public static final short TAG_ORIENTATION = 0x112; - public static final short TAG_SAMPLES_PER_PIXEL = 0x115; - public static final short TAG_ROWS_PER_STRIP = 0x116; - public static final short TAG_STRIP_BYTE_COUNTS = 0x117; - public static final short TAG_X_RESOLUTION = 0x11A; - public static final short TAG_Y_RESOLUTION = 0x11B; - public static final short TAG_PLANAR_CONFIGURATION = 0x11C; - public static final short TAG_RESOLUTION_UNIT = 0x128; - public static final short TAG_TRANSFER_FUNCTION = 0x12D; - public static final short TAG_SOFTWARE = 0x131; - public static final short TAG_DATE_TIME = 0x132; - public static final short TAG_ARTIST = 0x13B; - public static final short TAG_WHITE_POINT = 0x13E; - public static final short TAG_PRIMARY_CHROMATICITIES = 0x13F; - public static final short TAG_JPEG_INTERCHANGE_FORMAT = 0x201; - public static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = 0x202; - public static final short TAG_Y_CB_CR_COEFFICIENTS = 0x211; - public static final short TAG_Y_CB_CR_SUB_SAMPLING = 0x212; - public static final short TAG_Y_CB_CR_POSITIONING = 0x213; - public static final short TAG_REFERENCE_BLACK_WHITE = 0x214; - public static final short TAG_COPYRIGHT = (short) 0x8298; - public static final short TAG_EXIF_IFD = (short) 0x8769; - public static final short TAG_GPS_IFD = (short) 0x8825; - - // Exif Tags - public static final short TAG_EXPOSURE_TIME = (short) 0x829A; - public static final short TAG_F_NUMBER = (short) 0x829D; - public static final short TAG_EXPOSURE_PROGRAM = (short) 0x8822; - public static final short TAG_SPECTRAL_SENSITIVITY = (short) 0x8824; - public static final short TAG_ISO_SPEED_RATINGS = (short) 0x8827; - public static final short TAG_OECF = (short) 0x8828; - public static final short TAG_EXIF_VERSION = (short) 0x9000; - public static final short TAG_DATE_TIME_ORIGINAL = (short) 0x9003; - public static final short TAG_DATE_TIME_DIGITIZED = (short) 0x9004; - public static final short TAG_COMPONENTS_CONFIGURATION = (short) 0x9101; - public static final short TAG_COMPRESSED_BITS_PER_PIXEL = (short) 0x9102; - public static final short TAG_SHUTTER_SPEED_VALUE = (short) 0x9201; - public static final short TAG_APERTURE_VALUE = (short) 0x9202; - public static final short TAG_BRIGHTNESS_VALUE = (short) 0x9203; - public static final short TAG_EXPOSURE_BIAS_VALUE = (short) 0x9204; - public static final short TAG_MAX_APERTURE_VALUE = (short) 0x9205; - public static final short TAG_SUBJECT_DISTANCE = (short) 0x9206; - public static final short TAG_METERING_MODE = (short) 0x9207; - public static final short TAG_LIGHT_SOURCE = (short) 0x9208; - public static final short TAG_FLASH = (short) 0x9209; - public static final short TAG_FOCAL_LENGTH = (short) 0x920A; - public static final short TAG_SUBJECT_AREA = (short) 0x9214; - public static final short TAG_MAKER_NOTE = (short) 0x927C; - public static final short TAG_USER_COMMENT = (short) 0x9286; - public static final short TAG_SUB_SEC_TIME = (short) 0x9290; - public static final short TAG_SUB_SEC_TIME_ORIGINAL = (short) 0x9291; - public static final short TAG_SUB_SEC_TIME_DIGITIZED = (short) 0x9292; - public static final short TAG_FLASHPIX_VERSION = (short) 0xA000; - public static final short TAG_COLOR_SPACE = (short) 0xA001; - public static final short TAG_PIXEL_X_DIMENSION = (short) 0xA002; - public static final short TAG_PIXEL_Y_DIMENSION = (short) 0xA003; - public static final short TAG_RELATED_SOUND_FILE = (short) 0xA004; - public static final short TAG_INTEROPERABILITY_IFD = (short) 0xA005; - public static final short TAG_FLASH_ENERGY = (short) 0xA20B; - public static final short TAG_SPATIAL_FREQUENCY_RESPONSE = (short) 0xA20C; - public static final short TAG_FOCAL_PLANE_X_RESOLUTION = (short) 0xA20E; - public static final short TAG_FOCAL_PLANE_Y_RESOLUTION = (short) 0xA20F; - public static final short TAG_FOCAL_PLANE_RESOLUTION_UNIT = (short) 0xA210; - public static final short TAG_SUBJECT_LOCATION = (short) 0xA214; - public static final short TAG_EXPOSURE_INDEX = (short) 0xA215; - public static final short TAG_SENSING_METHOD = (short) 0xA217; - public static final short TAG_FILE_SOURCE = (short) 0xA300; - public static final short TAG_SCENE_TYPE = (short) 0xA301; - public static final short TAG_CFA_PATTERN = (short) 0xA302; - public static final short TAG_CUSTOM_RENDERED = (short) 0xA401; - public static final short TAG_EXPOSURE_MODE = (short) 0xA402; - public static final short TAG_WHITE_BALANCE = (short) 0xA403; - public static final short TAG_DIGITAL_ZOOM_RATIO = (short) 0xA404; - public static final short TAG_FOCAL_LENGTH_IN_35_MM_FILE = (short) 0xA405; - public static final short TAG_SCENE_CAPTURE_TYPE = (short) 0xA406; - public static final short TAG_GAIN_CONTROL = (short) 0xA407; - public static final short TAG_CONTRAST = (short) 0xA408; - public static final short TAG_SATURATION = (short) 0xA409; - public static final short TAG_SHARPNESS = (short) 0xA40A; - public static final short TAG_DEVICE_SETTING_DESCRIPTION = (short) 0xA40B; - public static final short TAG_SUBJECT_DISTANCE_RANGE = (short) 0xA40C; - public static final short TAG_IMAGE_UNIQUE_ID = (short) 0xA420; - - // GPS tags - public static final short TAG_GPS_VERSION_ID = 0; - public static final short TAG_GPS_LATITUDE_REF = 1; - public static final short TAG_GPS_LATITUDE = 2; - public static final short TAG_GPS_LONGITUDE_REF = 3; - public static final short TAG_GPS_LONGITUDE = 4; - public static final short TAG_GPS_ALTITUDE_REF = 5; - public static final short TAG_GPS_ALTITUDE = 6; - public static final short TAG_GPS_TIME_STAMP = 7; - public static final short TAG_GPS_SATTELLITES = 8; - public static final short TAG_GPS_STATUS = 9; - public static final short TAG_GPS_MEASURE_MODE = 10; - public static final short TAG_GPS_DOP = 11; - public static final short TAG_GPS_SPEED_REF = 12; - public static final short TAG_GPS_SPEED = 13; - public static final short TAG_GPS_TRACK_REF = 14; - public static final short TAG_GPS_TRACK = 15; - public static final short TAG_GPS_IMG_DIRECTION_REF = 16; - public static final short TAG_GPS_IMG_DIRECTION = 17; - public static final short TAG_GPS_MAP_DATUM = 18; - public static final short TAG_GPS_DEST_LATITUDE_REF = 19; - public static final short TAG_GPS_DEST_LATITUDE = 20; - public static final short TAG_GPS_DEST_LONGITUDE_REF = 21; - public static final short TAG_GPS_DEST_LONGITUDE = 22; - public static final short TAG_GPS_DEST_BEARING_REF = 23; - public static final short TAG_GPS_DEST_BEARING = 24; - public static final short TAG_GPS_DEST_DISTANCE_REF = 25; - public static final short TAG_GPS_DEST_DISTANCE = 26; - public static final short TAG_GPS_PROCESSING_METHOD = 27; - public static final short TAG_GPS_AREA_INFORMATION = 28; - public static final short TAG_GPS_DATE_STAMP = 29; - public static final short TAG_GPS_DIFFERENTIAL = 30; - - // Interoperability tag - public static final short TAG_INTEROPERABILITY_INDEX = 1; - - /** - * Constants for {@link #TAG_ORIENTATION} - */ - public static interface Orientation { - public static final short TOP_LEFT = 1; - public static final short TOP_RIGHT = 2; - public static final short BOTTOM_LEFT = 3; - public static final short BOTTOM_RIGHT = 4; - public static final short LEFT_TOP = 5; - public static final short RIGHT_TOP = 6; - public static final short LEFT_BOTTOM = 7; - public static final short RIGHT_BOTTOM = 8; - } - - /** - * Constants for {@link #TAG_Y_CB_CR_POSITIONING} - */ - public static interface YCbCrPositioning { - public static final short CENTERED = 1; - public static final short CO_SITED = 2; - } - - /** - * Constants for {@link #TAG_COMPRESSION} - */ - public static interface Compression { - public static final short UNCOMPRESSION = 1; - public static final short JPEG = 6; - } - - /** - * Constants for {@link #TAG_RESOLUTION_UNIT} - */ - public static interface ResolutionUnit { - public static final short INCHES = 2; - public static final short CENTIMETERS = 3; - } - - /** - * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION} - */ - public static interface PhotometricInterpretation { - public static final short RGB = 2; - public static final short YCBCR = 6; - } - - /** - * Constants for {@link #TAG_PLANAR_CONFIGURATION} - */ - public static interface PlanarConfiguration { - public static final short CHUNKY = 1; - public static final short PLANAR = 2; - } - - /** - * Constants for {@link #TAG_EXPOSURE_PROGRAM} - */ - public static interface ExposureProgram { - public static final short NOT_DEFINED = 0; - public static final short MANUAL = 1; - public static final short NORMAL_PROGRAM = 2; - public static final short APERTURE_PRIORITY = 3; - public static final short SHUTTER_PRIORITY = 4; - public static final short CREATIVE_PROGRAM = 5; - public static final short ACTION_PROGRAM = 6; - public static final short PROTRAIT_MODE = 7; - public static final short LANDSCAPE_MODE = 8; - } - - /** - * Constants for {@link #TAG_METERING_MODE} - */ - public static interface MeteringMode { - public static final short UNKNOWN = 0; - public static final short AVERAGE = 1; - public static final short CENTER_WEIGHTED_AVERAGE = 2; - public static final short SPOT = 3; - public static final short MULTISPOT = 4; - public static final short PATTERN = 5; - public static final short PARTAIL = 6; - public static final short OTHER = 255; - } - - /** - * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2 standard, we can - * treat this constant as bitwise flag. - *

- * e.g. - *

- * short flash = FIRED | RETURN_STROBE_RETURN_LIGHT_DETECTED | MODE_AUTO_MODE - */ - public static interface Flash { - // LSB - public static final short DID_NOT_FIRED = 0; - public static final short FIRED = 1; - // 1st~2nd bits - public static final short RETURN_NO_STROBE_RETURN_DETECTION_FUNCTION = 0 << 1; - public static final short RETURN_STROBE_RETURN_LIGHT_NOT_DETECTED = 2 << 1; - public static final short RETURN_STROBE_RETURN_LIGHT_DETECTED = 3 << 1; - // 3rd~4th bits - public static final short MODE_UNKNOWN = 0 << 3; - public static final short MODE_COMPULSORY_FLASH_FIRING = 1 << 3; - public static final short MODE_COMPULSORY_FLASH_SUPPRESSION = 2 << 3; - public static final short MODE_AUTO_MODE = 3 << 3; - // 5th bit - public static final short FUNCTION_PRESENT = 0 << 5; - public static final short FUNCTION_NO_FUNCTION = 1 << 5; - // 6th bit - public static final short RED_EYE_REDUCTION_NO_OR_UNKNOWN = 0 << 6; - public static final short RED_EYE_REDUCTION_SUPPORT = 1 << 6; - } - - /** - * Constants for {@link #TAG_COLOR_SPACE} - */ - public static interface ColorSpace { - public static final short SRGB = 1; - public static final short UNCALIBRATED = (short) 0xFFFF; - } - - /** - * Constants for {@link #TAG_EXPOSURE_MODE} - */ - public static interface ExposureMode { - public static final short AUTO_EXPOSURE = 0; - public static final short MANUAL_EXPOSURE = 1; - public static final short AUTO_BRACKET = 2; - } - - /** - * Constants for {@link #TAG_WHITE_BALANCE} - */ - public static interface WhiteBalance { - public static final short AUTO = 0; - public static final short MANUAL = 1; - } - - /** - * Constants for {@link #TAG_SCENE_CAPTURE_TYPE} - */ - public static interface SceneCapture { - public static final short STANDARD = 0; - public static final short LANDSCAPE = 1; - public static final short PROTRAIT = 2; - public static final short NIGHT_SCENE = 3; - } - - /** - * Constants for {@link #TAG_COMPONENTS_CONFIGURATION} - */ - public static interface ComponentsConfiguration { - public static final short NOT_EXIST = 0; - public static final short Y = 1; - public static final short CB = 2; - public static final short CR = 3; - public static final short R = 4; - public static final short G = 5; - public static final short B = 6; - } - - /** - * Constants for {@link #TAG_LIGHT_SOURCE} - */ - public static interface LightSource { - public static final short UNKNOWN = 0; - public static final short DAYLIGHT = 1; - public static final short FLUORESCENT = 2; - public static final short TUNGSTEN = 3; - public static final short FLASH = 4; - public static final short FINE_WEATHER = 9; - public static final short CLOUDY_WEATHER = 10; - public static final short SHADE = 11; - public static final short DAYLIGHT_FLUORESCENT = 12; - public static final short DAY_WHITE_FLUORESCENT = 13; - public static final short COOL_WHITE_FLUORESCENT = 14; - public static final short WHITE_FLUORESCENT = 15; - public static final short STANDARD_LIGHT_A = 17; - public static final short STANDARD_LIGHT_B = 18; - public static final short STANDARD_LIGHT_C = 19; - public static final short D55 = 20; - public static final short D65 = 21; - public static final short D75 = 22; - public static final short D50 = 23; - public static final short ISO_STUDIO_TUNGSTEN = 24; - public static final short OTHER = 255; - } - - /** - * Constants for {@link #TAG_SENSING_METHOD} - */ - public static interface SensingMethod { - public static final short NOT_DEFINED = 1; - public static final short ONE_CHIP_COLOR = 2; - public static final short TWO_CHIP_COLOR = 3; - public static final short THREE_CHIP_COLOR = 4; - public static final short COLOR_SEQUENTIAL_AREA = 5; - public static final short TRILINEAR = 7; - public static final short COLOR_SEQUENTIAL_LINEAR = 8; - } - - /** - * Constants for {@link #TAG_FILE_SOURCE} - */ - public static interface FileSource { - public static final short DSC = 3; - } - - /** - * Constants for {@link #TAG_SCENE_TYPE} - */ - public static interface SceneType { - public static final short DIRECT_PHOTOGRAPHED = 1; - } - - /** - * Constants for {@link #TAG_GAIN_CONTROL} - */ - public static interface GainControl { - public static final short NONE = 0; - public static final short LOW_UP = 1; - public static final short HIGH_UP = 2; - public static final short LOW_DOWN = 3; - public static final short HIGH_DOWN = 4; - } - - /** - * Constants for {@link #TAG_CONTRAST} - */ - public static interface Contrast { - public static final short NORMAL = 0; - public static final short SOFT = 1; - public static final short HARD = 2; - } - - /** - * Constants for {@link #TAG_SATURATION} - */ - public static interface Saturation { - public static final short NORMAL = 0; - public static final short LOW = 1; - public static final short HIGH = 2; - } - - /** - * Constants for {@link #TAG_SHARPNESS} - */ - public static interface Sharpness { - public static final short NORMAL = 0; - public static final short SOFT = 1; - public static final short HARD = 2; - } - - /** - * Constants for {@link #TAG_SUBJECT_DISTANCE} - */ - public static interface SubjectDistance { - public static final short UNKNOWN = 0; - public static final short MACRO = 1; - public static final short CLOSE_VIEW = 2; - public static final short DISTANT_VIEW = 3; - } - - /** - * Constants for {@link #TAG_GPS_LATITUDE_REF}, {@link #TAG_GPS_DEST_LATITUDE_REF} - */ - public static interface GpsLatitudeRef { - public static final String NORTH = "N"; - public static final String SOUTH = "S"; - } - - /** - * Constants for {@link #TAG_GPS_LONGITUDE_REF}, {@link #TAG_GPS_DEST_LONGITUDE_REF} - */ - public static interface GpsLongitudeRef { - public static final String EAST = "E"; - public static final String WEST = "W"; - } - - /** - * Constants for {@link #TAG_GPS_ALTITUDE_REF} - */ - public static interface GpsAltitudeRef { - public static final short SEA_LEVEL = 0; - public static final short SEA_LEVEL_NEGATIVE = 1; - } - - /** - * Constants for {@link #TAG_GPS_STATUS} - */ - public static interface GpsStatus { - public static final String IN_PROGRESS = "A"; - public static final String INTEROPERABILITY = "V"; - } - - /** - * Constants for {@link #TAG_GPS_MEASURE_MODE} - */ - public static interface GpsMeasureMode { - public static final String MODE_2_DIMENSIONAL = "2"; - public static final String MODE_3_DIMENSIONAL = "3"; - } - - /** - * Constants for {@link #TAG_GPS_SPEED_REF}, {@link #TAG_GPS_DEST_DISTANCE_REF} - */ - public static interface GpsSpeedRef { - public static final String KILOMETERS = "K"; - public static final String MILES = "M"; - public static final String KNOTS = "N"; - } - - /** - * Constants for {@link #TAG_GPS_TRACK_REF}, {@link #TAG_GPS_IMG_DIRECTION_REF}, - * {@link #TAG_GPS_DEST_BEARING_REF} - */ - public static interface GpsTrackRef { - public static final String TRUE_DIRECTION = "T"; - public static final String MAGNETIC_DIRECTION = "M"; - } - - /** - * Constants for {@link #TAG_GPS_DIFFERENTIAL} - */ - public static interface GpsDifferential { - public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0; - public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1; - } - /** * The BYTE type in the EXIF standard. An 8-bit unsigned integer. */ public static final short TYPE_UNSIGNED_BYTE = 1; /** - * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit ASCII code. - * The final byte is terminated with NULL. + * The ASCII type in the EXIF standard. An 8-bit byte containing one 7-bit + * ASCII code. The final byte is terminated with NULL. */ public static final short TYPE_ASCII = 2; /** @@ -509,13 +47,13 @@ public class ExifTag { */ public static final short TYPE_UNSIGNED_LONG = 4; /** - * The RATIONAL type of EXIF standard. It consists of two LONGs. The first one is the numerator - * and the second one expresses the denominator. + * The RATIONAL type of EXIF standard. It consists of two LONGs. The first + * one is the numerator and the second one expresses the denominator. */ public static final short TYPE_UNSIGNED_RATIONAL = 5; /** - * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any value - * depending on the field definition. + * The UNDEFINED type in the EXIF standard. An 8-bit byte that can take any + * value depending on the field definition. */ public static final short TYPE_UNDEFINED = 7; /** @@ -524,12 +62,18 @@ public class ExifTag { */ public static final short TYPE_LONG = 9; /** - * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first one is the - * numerator and the second one is the denominator. + * The SRATIONAL type of EXIF standard. It consists of two SLONGs. The first + * one is the numerator and the second one is the denominator. */ public static final short TYPE_RATIONAL = 10; + private static Charset US_ASCII = Charset.forName("US-ASCII"); private static final int TYPE_TO_SIZE_MAP[] = new int[11]; + private static final int UNSIGNED_SHORT_MAX = 65535; + private static final long UNSIGNED_LONG_MAX = 4294967295L; + private static final long LONG_MAX = Integer.MAX_VALUE; + private static final long LONG_MIN = Integer.MIN_VALUE; + static { TYPE_TO_SIZE_MAP[TYPE_UNSIGNED_BYTE] = 1; TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1; @@ -541,391 +85,69 @@ public class ExifTag { TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8; } - /** - * Gets the element size of the given data type. - * - * @see #TYPE_ASCII - * @see #TYPE_LONG - * @see #TYPE_RATIONAL - * @see #TYPE_UNDEFINED - * @see #TYPE_UNSIGNED_BYTE - * @see #TYPE_UNSIGNED_LONG - * @see #TYPE_UNSIGNED_RATIONAL - * @see #TYPE_UNSIGNED_SHORT - */ - public static int getElementSize(short type) { - return TYPE_TO_SIZE_MAP[type]; - } - - private static volatile SparseArray sTagInfo = null; - private static volatile SparseArray sInteroperTagInfo = null; - private static final int SIZE_UNDEFINED = 0; - - private static SparseArray getTagInfo() { - if (sTagInfo == null) { - synchronized(ExifTag.class) { - if (sTagInfo == null) { - sTagInfo = new SparseArray(); - initTagInfo(); - } - } - } - return sTagInfo; - } - - private static SparseArray getInteroperTagInfo() { - if (sInteroperTagInfo == null) { - synchronized(ExifTag.class) { - if (sInteroperTagInfo == null) { - sInteroperTagInfo = new SparseArray(); - sInteroperTagInfo.put(TAG_INTEROPERABILITY_INDEX, - (IfdId.TYPE_IFD_INTEROPERABILITY << 24) - | TYPE_ASCII << 16 | SIZE_UNDEFINED); - } - } - } - return sInteroperTagInfo; - } - - private static void initTagInfo() { - /** - * We put tag information in a 4-bytes integer. The first byte is the - * IFD of the tag, and the second byte is the default data type. The - * last two byte are a short value indicating the component count of this - * tag. - */ - sTagInfo.put(TAG_MAKE, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_IMAGE_WIDTH, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_IMAGE_LENGTH, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_BITS_PER_SAMPLE, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3); - sTagInfo.put(TAG_COMPRESSION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_PHOTOMETRIC_INTERPRETATION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_ORIENTATION, (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_SAMPLES_PER_PIXEL, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_PLANAR_CONFIGURATION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_Y_CB_CR_SUB_SAMPLING, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 2); - sTagInfo.put(TAG_Y_CB_CR_POSITIONING, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_X_RESOLUTION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_Y_RESOLUTION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_RESOLUTION_UNIT, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_STRIP_OFFSETS, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_ROWS_PER_STRIP, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_STRIP_BYTE_COUNTS, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_TRANSFER_FUNCTION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_SHORT << 16 | 3 * 256); - sTagInfo.put(TAG_WHITE_POINT, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 2); - sTagInfo.put(TAG_PRIMARY_CHROMATICITIES, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6); - sTagInfo.put(TAG_Y_CB_CR_COEFFICIENTS, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3); - sTagInfo.put(TAG_REFERENCE_BLACK_WHITE, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 6); - sTagInfo.put(TAG_DATE_TIME, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | 20); - sTagInfo.put(TAG_IMAGE_DESCRIPTION, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_MAKE, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_MODEL, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_SOFTWARE, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_ARTIST, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_COPYRIGHT, - (IfdId.TYPE_IFD_0 << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_EXIF_IFD, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_GPS_IFD, - (IfdId.TYPE_IFD_0 << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - - // EXIF TAG - sTagInfo.put(TAG_EXIF_VERSION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4); - sTagInfo.put(TAG_FLASHPIX_VERSION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4); - sTagInfo.put(TAG_COLOR_SPACE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_COMPONENTS_CONFIGURATION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 4); - sTagInfo.put(TAG_COMPRESSED_BITS_PER_PIXEL, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_PIXEL_X_DIMENSION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_PIXEL_Y_DIMENSION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_LONG << 16 | 1); - sTagInfo.put(TAG_MAKER_NOTE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_USER_COMMENT, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_RELATED_SOUND_FILE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 13); - sTagInfo.put(TAG_DATE_TIME_ORIGINAL, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20); - sTagInfo.put(TAG_DATE_TIME_DIGITIZED, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 20); - sTagInfo.put(TAG_SUB_SEC_TIME, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_SUB_SEC_TIME_ORIGINAL, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_SUB_SEC_TIME_DIGITIZED, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_IMAGE_UNIQUE_ID, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | 33); - sTagInfo.put(TAG_EXPOSURE_TIME, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_F_NUMBER, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_EXPOSURE_PROGRAM, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_SPECTRAL_SENSITIVITY, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_ISO_SPEED_RATINGS, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_OECF, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_SHUTTER_SPEED_VALUE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1); - sTagInfo.put(TAG_APERTURE_VALUE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_BRIGHTNESS_VALUE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1); - sTagInfo.put(TAG_EXPOSURE_BIAS_VALUE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_RATIONAL << 16 | 1); - sTagInfo.put(TAG_MAX_APERTURE_VALUE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_SUBJECT_DISTANCE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_METERING_MODE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_LIGHT_SOURCE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_FLASH, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_FOCAL_LENGTH, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_SUBJECT_AREA, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_FLASH_ENERGY, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_SPATIAL_FREQUENCY_RESPONSE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_FOCAL_PLANE_X_RESOLUTION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_FOCAL_PLANE_Y_RESOLUTION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_SUBJECT_LOCATION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 2); - sTagInfo.put(TAG_EXPOSURE_INDEX, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_SENSING_METHOD, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_FILE_SOURCE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1); - sTagInfo.put(TAG_SCENE_TYPE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | 1); - sTagInfo.put(TAG_CFA_PATTERN, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_CUSTOM_RENDERED, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_EXPOSURE_MODE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_WHITE_BALANCE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_DIGITAL_ZOOM_RATIO, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_FOCAL_LENGTH_IN_35_MM_FILE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_SCENE_CAPTURE_TYPE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_GAIN_CONTROL, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_CONTRAST, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_SATURATION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_SHARPNESS, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - sTagInfo.put(TAG_DEVICE_SETTING_DESCRIPTION, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_SUBJECT_DISTANCE_RANGE, - (IfdId.TYPE_IFD_EXIF << 24) | TYPE_UNSIGNED_SHORT << 16 | 1); - // GPS tag - sTagInfo.put(TAG_GPS_VERSION_ID, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 4); - sTagInfo.put(TAG_GPS_LATITUDE_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_LONGITUDE_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_LATITUDE, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3); - sTagInfo.put(TAG_GPS_LONGITUDE, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_RATIONAL << 16 | 3); - sTagInfo.put(TAG_GPS_ALTITUDE_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_BYTE << 16 | 1); - sTagInfo.put(TAG_GPS_ALTITUDE, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_TIME_STAMP, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 3); - sTagInfo.put(TAG_GPS_SATTELLITES, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_GPS_STATUS, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_MEASURE_MODE, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_DOP, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_SPEED_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_SPEED, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_TRACK_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_TRACK, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_IMG_DIRECTION_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_IMG_DIRECTION, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_MAP_DATUM, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_GPS_DEST_LATITUDE_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_DEST_LATITUDE, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_DEST_BEARING_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_DEST_BEARING, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_DEST_DISTANCE_REF, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 2); - sTagInfo.put(TAG_GPS_DEST_DISTANCE, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_RATIONAL << 16 | 1); - sTagInfo.put(TAG_GPS_PROCESSING_METHOD, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_GPS_AREA_INFORMATION, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNDEFINED << 16 | SIZE_UNDEFINED); - sTagInfo.put(TAG_GPS_DATE_STAMP, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_ASCII << 16 | 11); - sTagInfo.put(TAG_GPS_DIFFERENTIAL, - (IfdId.TYPE_IFD_GPS << 24) | TYPE_UNSIGNED_SHORT << 16 | 11); - } + static final int SIZE_UNDEFINED = 0; - private static Charset US_ASCII = Charset.forName("US-ASCII"); + // Exif TagId private final short mTagId; + // Exif Tag Type private final short mDataType; - private final int mIfd; - private final boolean mComponentCountDefined; - private int mComponentCount; + // If tag has defined count + private boolean mHasDefinedDefaultComponentCount; + // Actual data count in tag (should be number of elements in value array) + private int mComponentCountActual; + // The ifd that this tag should be put in + private int mIfd; + // The value (array of elements of type Tag Type) private Object mValue; + // Value offset in exif header. private int mOffset; - static private short getTypeFromInfo(int info) { - return (short) ((info >> 16) & 0xff); - } - - static private int getComponentCountFromInfo(int info) { - return info & 0xffff; - } - - static private int getIfdIdFromInfo(int info) { - return (info >> 24) & 0xff; - } - - static private boolean getComponentCountDefined(short tagId, int ifd) { - Integer info = (ifd == IfdId.TYPE_IFD_INTEROPERABILITY) ? - getInteroperTagInfo().get(tagId) : getTagInfo().get(tagId); - if (info == null) return false; - return getComponentCountFromInfo(info) != SIZE_UNDEFINED; - } - - static int getIfdIdFromTagId(short tagId) { - Integer info = getTagInfo().get(tagId); - if (info == null) { - throw new IllegalArgumentException("Unknown Tag ID: " + tagId); - } - return getIfdIdFromInfo(info); - } - - /** - * Create a tag with given ID. For tags related to interoperability and thumbnail, call - * {@link #buildInteroperabilityTag(short)} and {@link #buildThumbnailTag(short)} respectively. - * @exception IllegalArgumentException If the ID is invalid. - */ - static public ExifTag buildTag(short tagId) { - Integer info = getTagInfo().get(tagId); - if (info == null) { - throw new IllegalArgumentException("Unknown Tag ID: " + tagId); - } - return new ExifTag(tagId, getTypeFromInfo(info), - getComponentCountFromInfo(info), - getIfdIdFromInfo(info)); - } + private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); /** - * Create a tag related to thumbnail with given ID. - * @exception IllegalArgumentException If the ID is invalid. + * Returns true if the given IFD is a valid IFD. */ - static public ExifTag buildThumbnailTag(short tagId) { - Integer info = getTagInfo().get(tagId); - if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_0) { - throw new IllegalArgumentException("Unknown Thumnail Tag ID: " + tagId); - } - return new ExifTag(tagId, getTypeFromInfo(info), - getComponentCountFromInfo(info), - IfdId.TYPE_IFD_1); + public static boolean isValidIfd(int ifdId) { + return ifdId == IfdId.TYPE_IFD_0 || ifdId == IfdId.TYPE_IFD_1 + || ifdId == IfdId.TYPE_IFD_EXIF || ifdId == IfdId.TYPE_IFD_INTEROPERABILITY + || ifdId == IfdId.TYPE_IFD_GPS; } /** - * Create a tag related to interoperability with given ID. - * @exception IllegalArgumentException If the ID is invalid. + * Returns true if a given type is a valid tag type. */ - static public ExifTag buildInteroperabilityTag(short tagId) { - Integer info = getInteroperTagInfo().get(tagId); - if (info == null || getIfdIdFromInfo(info) != IfdId.TYPE_IFD_INTEROPERABILITY) { - throw new RuntimeException("Unknown Interoperability Tag ID: " + tagId); - } - return new ExifTag(tagId, getTypeFromInfo(info), - getComponentCountFromInfo(info), - IfdId.TYPE_IFD_INTEROPERABILITY); - } - - static boolean isValidType(short type) { + public static boolean isValidType(short type) { return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII || type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG || type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED || type == TYPE_LONG || type == TYPE_RATIONAL; } - ExifTag(short tagId, short type, int componentCount, int ifd) { + // Use builtTag in ExifInterface instead of constructor. + ExifTag(short tagId, short type, int componentCount, int ifd, + boolean hasDefinedComponentCount) { mTagId = tagId; mDataType = type; - mComponentCount = componentCount; - mComponentCountDefined = getComponentCountDefined(tagId, ifd); + mComponentCountActual = componentCount; + mHasDefinedDefaultComponentCount = hasDefinedComponentCount; mIfd = ifd; + mValue = null; + } + + /** + * Gets the element size of the given data type in bytes. + * + * @see #TYPE_ASCII + * @see #TYPE_LONG + * @see #TYPE_RATIONAL + * @see #TYPE_UNDEFINED + * @see #TYPE_UNSIGNED_BYTE + * @see #TYPE_UNSIGNED_LONG + * @see #TYPE_UNSIGNED_RATIONAL + * @see #TYPE_UNSIGNED_SHORT + */ + public static int getElementSize(short type) { + return TYPE_TO_SIZE_MAP[type]; } /** @@ -941,8 +163,12 @@ public class ExifTag { return mIfd; } + protected void setIfd(int ifdId) { + mIfd = ifdId; + } + /** - * Gets the ID of this tag. + * Gets the TID of this tag. */ public short getTagId() { return mTagId; @@ -974,349 +200,589 @@ public class ExifTag { /** * Gets the component count of this tag. */ + + // TODO: fix integer overflows with this public int getComponentCount() { - return mComponentCount; + return mComponentCountActual; } /** - * Sets the component count of this tag. - * Call this function before setValue() if the length of value does not - * match the component count. + * Sets the component count of this tag. Call this function before + * setValue() if the length of value does not match the component count. */ - public void setComponentCount(int count) { - mComponentCount = count; + protected void forceSetComponentCount(int count) { + mComponentCountActual = count; } /** - * Returns true if this ExifTag contains value; otherwise, this tag will contain an offset value - * that links to the area where the actual value is located. - * - * @see #getOffset() + * Returns true if this ExifTag contains value; otherwise, this tag will + * contain an offset value that is determined when the tag is written. */ public boolean hasValue() { return mValue != null; } /** - * Gets the offset of this tag. This is only valid if this data size > 4 and contains an offset - * to the location of the actual value. + * Sets integer values into this tag. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_SHORT}. This method will fail if: + *

    + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, + * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
  • + *
  • The value overflows.
  • + *
  • The value.length does NOT match the component count in the definition + * for this tag.
  • + *
*/ - public int getOffset() { - return mOffset; + public boolean setValue(int[] value) { + if (checkBadComponentCount(value.length)) { + return false; + } + if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG && + mDataType != TYPE_UNSIGNED_LONG) { + return false; + } + if (mDataType == TYPE_UNSIGNED_SHORT && checkOverflowForUnsignedShort(value)) { + return false; + } else if (mDataType == TYPE_UNSIGNED_LONG && checkOverflowForUnsignedLong(value)) { + return false; + } + + long[] data = new long[value.length]; + System.arraycopy(value, 0, data, 0, value.length); + mValue = data; + mComponentCountActual = value.length; + return true; } /** - * Sets the offset of this tag. + * Sets integer value into this tag. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_SHORT}, or {@link #TYPE_LONG}. This method + * will fail if: + *
    + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, + * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
  • + *
  • The value overflows.
  • + *
  • The component count in the definition of this tag is not 1.
  • + *
*/ - void setOffset(int offset) { - mOffset = offset; + public boolean setValue(int value) { + return setValue(new int[] { + value + }); } - private void checkComponentCountOrThrow(int count) - throws IllegalArgumentException { - if (mComponentCountDefined && (mComponentCount != count)) { - throw new IllegalArgumentException("Tag " + mTagId + ": Required " - + mComponentCount + " components but was given " + count - + " component(s)"); + /** + * Sets long values into this tag. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: + *
    + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
  • + *
  • The value overflows.
  • + *
  • The value.length does NOT match the component count in the definition + * for this tag.
  • + *
+ */ + public boolean setValue(long[] value) { + if (checkBadComponentCount(value.length) || mDataType != TYPE_UNSIGNED_LONG) { + return false; + } + if (checkOverflowForUnsignedLong(value)) { + return false; } + mValue = value; + mComponentCountActual = value.length; + return true; } - private void throwTypeNotMatchedException(String className) - throws IllegalArgumentException { - throw new IllegalArgumentException("Tag " + mTagId + ": expect type " + - convertTypeToString(mDataType) + " but got " + className); + /** + * Sets long values into this tag. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_LONG}. This method will fail if: + *
    + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
  • + *
  • The value overflows.
  • + *
  • The component count in the definition for this tag is not 1.
  • + *
+ */ + public boolean setValue(long value) { + return setValue(new long[] { + value + }); } - private static String convertTypeToString(short type) { - switch (type) { - case TYPE_UNSIGNED_BYTE: - return "UNSIGNED_BYTE"; - case TYPE_ASCII: - return "ASCII"; - case TYPE_UNSIGNED_SHORT: - return "UNSIGNED_SHORT"; - case TYPE_UNSIGNED_LONG: - return "UNSIGNED_LONG"; - case TYPE_UNSIGNED_RATIONAL: - return "UNSIGNED_RATIONAL"; - case TYPE_UNDEFINED: - return "UNDEFINED"; - case TYPE_LONG: - return "LONG"; - case TYPE_RATIONAL: - return "RATIONAL"; - default: - return ""; + /** + * Sets a string value into this tag. This method should be used for tags of + * type {@link #TYPE_ASCII}. The string is converted to an ASCII string. + * Characters that cannot be converted are replaced with '?'. The length of + * the string must be equal to either (component count -1) or (component + * count). The final byte will be set to the string null terminator '\0', + * overwriting the last character in the string if the value.length is equal + * to the component count. This method will fail if: + *
    + *
  • The data type is not {@link #TYPE_ASCII} or {@link #TYPE_UNDEFINED}.
  • + *
  • The length of the string is not equal to (component count -1) or + * (component count) in the definition for this tag.
  • + *
+ */ + public boolean setValue(String value) { + if (mDataType != TYPE_ASCII && mDataType != TYPE_UNDEFINED) { + return false; } - } - private static final int UNSIGNED_SHORT_MAX = 65535; - private static final long UNSIGNED_LONG_MAX = 4294967295L; - private static final long LONG_MAX = Integer.MAX_VALUE; - private static final long LONG_MIN = Integer.MIN_VALUE; - - private void checkOverflowForUnsignedShort(int[] value) { - for (int v : value) { - if (v > UNSIGNED_SHORT_MAX || v < 0) { - throw new IllegalArgumentException( - "Tag " + mTagId+ ": Value" + v + - " is illegal for type UNSIGNED_SHORT"); - } + byte[] buf = value.getBytes(US_ASCII); + byte[] finalBuf = (buf[buf.length - 1] == 0 || mDataType == TYPE_UNDEFINED) ? buf : Arrays + .copyOf(buf, buf.length + 1); + int count = finalBuf.length; + if (checkBadComponentCount(count)) { + return false; } + mComponentCountActual = count; + mValue = finalBuf; + return true; } - private void checkOverflowForUnsignedLong(long[] value) { - for (long v: value) { - if (v < 0 || v > UNSIGNED_LONG_MAX) { - throw new IllegalArgumentException( - "Tag " + mTagId+ ": Value" + v + - " is illegal for type UNSIGNED_LONG"); - } + /** + * Sets Rational values into this tag. This method should be used for tags + * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This + * method will fail if: + *
    + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} + * or {@link #TYPE_RATIONAL}.
  • + *
  • The value overflows.
  • + *
  • The value.length does NOT match the component count in the definition + * for this tag.
  • + *
+ * + * @see Rational + */ + public boolean setValue(Rational[] value) { + if (checkBadComponentCount(value.length)) { + return false; } - } - - private void checkOverflowForUnsignedLong(int[] value) { - for (int v: value) { - if (v < 0) { - throw new IllegalArgumentException( - "Tag " + mTagId+ ": Value" + v + - " is illegal for type UNSIGNED_LONG"); - } + if (mDataType != TYPE_UNSIGNED_RATIONAL && mDataType != TYPE_RATIONAL) { + return false; } - } - - private void checkOverflowForUnsignedRational(Rational[] value) { - for (Rational v: value) { - if (v.getNominator() < 0 || v.getDenominator() < 0 - || v.getNominator() > UNSIGNED_LONG_MAX - || v.getDenominator() > UNSIGNED_LONG_MAX) { - throw new IllegalArgumentException( - "Tag " + mTagId+ ": Value" + v + - " is illegal for type UNSIGNED_RATIONAL"); - } + if (mDataType == TYPE_UNSIGNED_RATIONAL && checkOverflowForUnsignedRational(value)) { + return false; + } else if (mDataType == TYPE_RATIONAL && checkOverflowForRational(value)) { + return false; } + + mValue = value; + mComponentCountActual = value.length; + return true; } - private void checkOverflowForRational(Rational[] value) { - for (Rational v: value) { - if (v.getNominator() < LONG_MIN || v.getDenominator() < LONG_MIN - || v.getNominator() > LONG_MAX - || v.getDenominator() > LONG_MAX) { - throw new IllegalArgumentException( - "Tag " + mTagId+ ": Value" + v + - " is illegal for type RATIONAL"); - } - } + /** + * Sets a Rational value into this tag. This method should be used for tags + * of type {@link #TYPE_UNSIGNED_RATIONAL}, or {@link #TYPE_RATIONAL}. This + * method will fail if: + *
    + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} + * or {@link #TYPE_RATIONAL}.
  • + *
  • The value overflows.
  • + *
  • The component count in the definition for this tag is not 1.
  • + *
+ * + * @see Rational + */ + public boolean setValue(Rational value) { + return setValue(new Rational[] { + value + }); } /** - * Sets integer values into this tag. - * @exception IllegalArgumentException for the following situation: + * Sets byte values into this tag. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method + * will fail if: *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
  • - *
  • The value overflows.
  • - *
  • The value.length does NOT match the definition of component count in - * EXIF standard.
  • + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or + * {@link #TYPE_UNDEFINED} .
  • + *
  • The length does NOT match the component count in the definition for + * this tag.
  • *
*/ - public void setValue(int[] value) { - checkComponentCountOrThrow(value.length); - if (mDataType != TYPE_UNSIGNED_SHORT && mDataType != TYPE_LONG && - mDataType != TYPE_UNSIGNED_LONG) { - throwTypeNotMatchedException("int"); + public boolean setValue(byte[] value, int offset, int length) { + if (checkBadComponentCount(length)) { + return false; } - if (mDataType == TYPE_UNSIGNED_SHORT) { - checkOverflowForUnsignedShort(value); - } else if (mDataType == TYPE_UNSIGNED_LONG) { - checkOverflowForUnsignedLong(value); + if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) { + return false; } + mValue = new byte[length]; + System.arraycopy(value, offset, mValue, 0, length); + mComponentCountActual = length; + return true; + } - long[] data = new long[value.length]; - for (int i = 0; i < value.length; i++) { - data[i] = value[i]; - } - mValue = data; - mComponentCount = value.length; + /** + * Equivalent to setValue(value, 0, value.length). + */ + public boolean setValue(byte[] value) { + return setValue(value, 0, value.length); } /** - * Sets integer values into this tag. - * @exception IllegalArgumentException For the following situation: + * Sets byte value into this tag. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_BYTE} or {@link #TYPE_UNDEFINED}. This method + * will fail if: *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_SHORT}, - * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_LONG}.
  • - *
  • The value overflows.
  • - *
  • The component count in the definition of EXIF standard is not 1.
  • + *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or + * {@link #TYPE_UNDEFINED} .
  • + *
  • The component count in the definition for this tag is not 1.
  • *
*/ - public void setValue(int value) { - checkComponentCountOrThrow(1); - setValue(new int[] {value}); + public boolean setValue(byte value) { + return setValue(new byte[] { + value + }); } /** - * Sets long values into this tag. - * @exception IllegalArgumentException For the following situation: + * Sets the value for this tag using an appropriate setValue method for the + * given object. This method will fail if: *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
  • - *
  • The value overflows.
  • - *
  • The value.length does NOT match the definition of component count in - * EXIF standard.
  • + *
  • The corresponding setValue method for the class of the object passed + * in would fail.
  • + *
  • There is no obvious way to cast the object passed in into an EXIF tag + * type.
  • *
*/ - public void setValue(long[] value) { - checkComponentCountOrThrow(value.length); - if (mDataType != TYPE_UNSIGNED_LONG) { - throwTypeNotMatchedException("long"); + public boolean setValue(Object obj) { + if (obj == null) { + return false; + } else if (obj instanceof Short) { + return setValue(((Short) obj).shortValue() & 0x0ffff); + } else if (obj instanceof String) { + return setValue((String) obj); + } else if (obj instanceof int[]) { + return setValue((int[]) obj); + } else if (obj instanceof long[]) { + return setValue((long[]) obj); + } else if (obj instanceof Rational) { + return setValue((Rational) obj); + } else if (obj instanceof Rational[]) { + return setValue((Rational[]) obj); + } else if (obj instanceof byte[]) { + return setValue((byte[]) obj); + } else if (obj instanceof Integer) { + return setValue(((Integer) obj).intValue()); + } else if (obj instanceof Long) { + return setValue(((Long) obj).longValue()); + } else if (obj instanceof Byte) { + return setValue(((Byte) obj).byteValue()); + } else if (obj instanceof Short[]) { + // Nulls in this array are treated as zeroes. + Short[] arr = (Short[]) obj; + int[] fin = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + fin[i] = (arr[i] == null) ? 0 : arr[i].shortValue() & 0x0ffff; + } + return setValue(fin); + } else if (obj instanceof Integer[]) { + // Nulls in this array are treated as zeroes. + Integer[] arr = (Integer[]) obj; + int[] fin = new int[arr.length]; + for (int i = 0; i < arr.length; i++) { + fin[i] = (arr[i] == null) ? 0 : arr[i].intValue(); + } + return setValue(fin); + } else if (obj instanceof Long[]) { + // Nulls in this array are treated as zeroes. + Long[] arr = (Long[]) obj; + long[] fin = new long[arr.length]; + for (int i = 0; i < arr.length; i++) { + fin[i] = (arr[i] == null) ? 0 : arr[i].longValue(); + } + return setValue(fin); + } else if (obj instanceof Byte[]) { + // Nulls in this array are treated as zeroes. + Byte[] arr = (Byte[]) obj; + byte[] fin = new byte[arr.length]; + for (int i = 0; i < arr.length; i++) { + fin[i] = (arr[i] == null) ? 0 : arr[i].byteValue(); + } + return setValue(fin); + } else { + return false; } - checkOverflowForUnsignedLong(value); - mValue = value; - mComponentCount = value.length; } /** - * Sets long values into this tag. - * @exception IllegalArgumentException For the following situation: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_LONG}.
  • - *
  • The value overflows.
  • - *
  • The component count in the definition of EXIF standard is not 1.
  • - *
+ * Sets a timestamp to this tag. The method converts the timestamp with the + * format of "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. This + * method will fail if the data type is not {@link #TYPE_ASCII} or the + * component count of this tag is not 20 or undefined. + * + * @param time the number of milliseconds since Jan. 1, 1970 GMT + * @return true on success */ - public void setValue(long value) { - setValue(new long[] {value}); + public boolean setTimeValue(long time) { + // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe + synchronized (TIME_FORMAT) { + return setValue(TIME_FORMAT.format(new Date(time))); + } } /** - * Sets a string value into this tag. The value is treated as an ASCII string where we only - * preserve the lower byte of each character. The length of the string should be equal - * to either (component count -1) or (component count). A "0" byte will be appeneded while - * written to the EXIF file. If the length equals (component count), the final byte will be - * replaced by a "0" byte. + * Gets the value as a String. This method should be used for tags of type + * {@link #TYPE_ASCII}. * - * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII} - * or the length of the string is not equal to (component count -1) and (component count) - */ - public void setValue(String value) { - if (mDataType != TYPE_ASCII) { - throwTypeNotMatchedException("String"); + * @return the value as a String, or null if the tag's value does not exist + * or cannot be converted to a String. + */ + public String getValueAsString() { + if (mValue == null) { + return null; + } else if (mValue instanceof String) { + return (String) mValue; + } else if (mValue instanceof byte[]) { + return new String((byte[]) mValue, US_ASCII); } + return null; + } - byte[] buf = new byte[value.length()]; - for (int i = 0, n = value.length(); i < n; i++) { - buf[i] = (byte) value.charAt(i); + /** + * Gets the value as a String. This method should be used for tags of type + * {@link #TYPE_ASCII}. + * + * @param defaultValue the String to return if the tag's value does not + * exist or cannot be converted to a String. + * @return the tag's value as a String, or the defaultValue. + */ + public String getValueAsString(String defaultValue) { + String s = getValueAsString(); + if (s == null) { + return defaultValue; } + return s; + } - int count = buf.length; - if (mComponentCountDefined) { - if (mComponentCount != count && mComponentCount != count + 1) { - throw new IllegalArgumentException("Tag " + mTagId + ": Required " - + mComponentCount + " or " + (mComponentCount + 1) - + " components but was given " + count - + " component(s)"); - } - } else { - mComponentCount = buf[count - 1] == 0 ? count : count + 1; + /** + * Gets the value as a byte array. This method should be used for tags of + * type {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. + * + * @return the value as a byte array, or null if the tag's value does not + * exist or cannot be converted to a byte array. + */ + public byte[] getValueAsBytes() { + if (mValue instanceof byte[]) { + return (byte[]) mValue; } - mValue = buf; + return null; } /** - * Sets Rational values into this tag. - * @exception IllegalArgumentException For the following situation: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or - * {@link #TYPE_RATIONAL} .
  • - *
  • The value overflows.
  • - *
  • The value.length does NOT match the definition of component count in - * EXIF standard.
  • - *
+ * Gets the value as a byte. If there are more than 1 bytes in this value, + * gets the first byte. This method should be used for tags of type + * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. + * + * @param defaultValue the byte to return if tag's value does not exist or + * cannot be converted to a byte. + * @return the tag's value as a byte, or the defaultValue. + */ + public byte getValueAsByte(byte defaultValue) { + byte[] b = getValueAsBytes(); + if (b == null || b.length < 1) { + return defaultValue; + } + return b[0]; + } + + /** + * Gets the value as an array of Rationals. This method should be used for + * tags of type {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. + * + * @return the value as as an array of Rationals, or null if the tag's value + * does not exist or cannot be converted to an array of Rationals. */ - public void setValue(Rational[] value) { - if (mDataType == TYPE_UNSIGNED_RATIONAL) { - checkOverflowForUnsignedRational(value); - } else if (mDataType == TYPE_RATIONAL) { - checkOverflowForRational(value); - } else { - throwTypeNotMatchedException("Rational"); + public Rational[] getValueAsRationals() { + if (mValue instanceof Rational[]) { + return (Rational[]) mValue; } - checkComponentCountOrThrow(value.length); - mValue = value; - mComponentCount = value.length; + return null; } /** - * Sets Rational values into this tag. - * @exception IllegalArgumentException For the following situation: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_RATIONAL} or - * {@link #TYPE_RATIONAL} .
  • - *
  • The value overflows.
  • - *
  • The component count in the definition of EXIF standard is not 1.
  • - *
- * */ - public void setValue(Rational value) { - setValue(new Rational[] {value}); + * Gets the value as a Rational. If there are more than 1 Rationals in this + * value, gets the first one. This method should be used for tags of type + * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. + * + * @param defaultValue the Rational to return if tag's value does not exist + * or cannot be converted to a Rational. + * @return the tag's value as a Rational, or the defaultValue. + */ + public Rational getValueAsRational(Rational defaultValue) { + Rational[] r = getValueAsRationals(); + if (r == null || r.length < 1) { + return defaultValue; + } + return r[0]; } /** - * Sets byte values into this tag. - * @exception IllegalArgumentException For the following situation: - *
    - *
  • The component type of this tag is not {@link #TYPE_UNSIGNED_BYTE} or - * {@link #TYPE_UNDEFINED} .
  • - *
  • The length does NOT match the definition of component count in EXIF standard.
  • - *
- * */ - public void setValue(byte[] value, int offset, int length) { - checkComponentCountOrThrow(length); - if (mDataType != TYPE_UNSIGNED_BYTE && mDataType != TYPE_UNDEFINED) { - throwTypeNotMatchedException("byte"); + * Gets the value as a Rational. If there are more than 1 Rationals in this + * value, gets the first one. This method should be used for tags of type + * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. + * + * @param defaultValue the numerator of the Rational to return if tag's + * value does not exist or cannot be converted to a Rational (the + * denominator will be 1). + * @return the tag's value as a Rational, or the defaultValue. + */ + public Rational getValueAsRational(long defaultValue) { + Rational defaultVal = new Rational(defaultValue, 1); + return getValueAsRational(defaultVal); + } + + /** + * Gets the value as an array of ints. This method should be used for tags + * of type {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}. + * + * @return the value as as an array of ints, or null if the tag's value does + * not exist or cannot be converted to an array of ints. + */ + public int[] getValueAsInts() { + if (mValue == null) { + return null; + } else if (mValue instanceof long[]) { + long[] val = (long[]) mValue; + int[] arr = new int[val.length]; + for (int i = 0; i < val.length; i++) { + arr[i] = (int) val[i]; // Truncates + } + return arr; } - mValue = new byte[length]; - System.arraycopy(value, offset, mValue, 0, length); - mComponentCount = length; + return null; } /** - * Equivalent to setValue(value, 0, value.length). + * Gets the value as an int. If there are more than 1 ints in this value, + * gets the first one. This method should be used for tags of type + * {@link #TYPE_UNSIGNED_SHORT}, {@link #TYPE_UNSIGNED_LONG}. + * + * @param defaultValue the int to return if tag's value does not exist or + * cannot be converted to an int. + * @return the tag's value as a int, or the defaultValue. + */ + public int getValueAsInt(int defaultValue) { + int[] i = getValueAsInts(); + if (i == null || i.length < 1) { + return defaultValue; + } + return i[0]; + } + + /** + * Gets the value as an array of longs. This method should be used for tags + * of type {@link #TYPE_UNSIGNED_LONG}. + * + * @return the value as as an array of longs, or null if the tag's value + * does not exist or cannot be converted to an array of longs. */ - public void setValue(byte[] value) { - setValue(value, 0, value.length); + public long[] getValueAsLongs() { + if (mValue instanceof long[]) { + return (long[]) mValue; + } + return null; } - private static final SimpleDateFormat TIME_FORMAT = - new SimpleDateFormat("yyyy:MM:dd kk:mm:ss"); + /** + * Gets the value or null if none exists. If there are more than 1 longs in + * this value, gets the first one. This method should be used for tags of + * type {@link #TYPE_UNSIGNED_LONG}. + * + * @param defaultValue the long to return if tag's value does not exist or + * cannot be converted to a long. + * @return the tag's value as a long, or the defaultValue. + */ + public long getValueAsLong(long defaultValue) { + long[] l = getValueAsLongs(); + if (l == null || l.length < 1) { + return defaultValue; + } + return l[0]; + } /** - * Sets a timestamp to this tag. The method converts the timestamp with the format of - * "yyyy:MM:dd kk:mm:ss" and calls {@link #setValue(String)}. + * Gets the tag's value or null if none exists. + */ + public Object getValue() { + return mValue; + } + + /** + * Gets a long representation of the value. * - * @param time the number of milliseconds since Jan. 1, 1970 GMT - * @exception IllegalArgumentException If the data type is not {@link #TYPE_ASCII} - * or the component count of this tag is not 20 or undefined + * @param defaultValue value to return if there is no value or value is a + * rational with a denominator of 0. + * @return the tag's value as a long, or defaultValue if no representation + * exists. + */ + public long forceGetValueAsLong(long defaultValue) { + long[] l = getValueAsLongs(); + if (l != null && l.length >= 1) { + return l[0]; + } + byte[] b = getValueAsBytes(); + if (b != null && b.length >= 1) { + return b[0]; + } + Rational[] r = getValueAsRationals(); + if (r != null && r.length >= 1 && r[0].getDenominator() != 0) { + return (long) r[0].toDouble(); + } + return defaultValue; + } + + /** + * Gets a string representation of the value. */ - public void setTimeValue(long time) { - // synchronized on TIME_FORMAT as SimpleDateFormat is not thread safe - synchronized (TIME_FORMAT) { - setValue(TIME_FORMAT.format(new Date(time))); + public String forceGetValueAsString() { + if (mValue == null) { + return ""; + } else if (mValue instanceof byte[]) { + if (mDataType == TYPE_ASCII) { + return new String((byte[]) mValue, US_ASCII); + } else { + return Arrays.toString((byte[]) mValue); + } + } else if (mValue instanceof long[]) { + if (((long[]) mValue).length == 1) { + return String.valueOf(((long[]) mValue)[0]); + } else { + return Arrays.toString((long[]) mValue); + } + } else if (mValue instanceof Object[]) { + if (((Object[]) mValue).length == 1) { + Object val = ((Object[]) mValue)[0]; + if (val == null) { + return ""; + } else { + return val.toString(); + } + } else { + return Arrays.toString((Object[]) mValue); + } + } else { + return mValue.toString(); } } + /** * Gets the value for type {@link #TYPE_ASCII}, {@link #TYPE_LONG}, - * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE}, {@link #TYPE_UNSIGNED_LONG}, or - * {@link #TYPE_UNSIGNED_SHORT}. For {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, - * call {@link #getRational(int)} instead. + * {@link #TYPE_UNDEFINED}, {@link #TYPE_UNSIGNED_BYTE}, + * {@link #TYPE_UNSIGNED_LONG}, or {@link #TYPE_UNSIGNED_SHORT}. For + * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}, call + * {@link #getRational(int)} instead. * - * @exception IllegalArgumentException if the data type is {@link #TYPE_RATIONAL} or - * {@link #TYPE_UNSIGNED_RATIONAL}. + * @exception IllegalArgumentException if the data type is + * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. */ - public long getValueAt(int index) { + protected long getValueAt(int index) { if (mValue instanceof long[]) { - return ((long[]) mValue) [index]; + return ((long[]) mValue)[index]; } else if (mValue instanceof byte[]) { - return ((byte[]) mValue) [index]; + return ((byte[]) mValue)[index]; } throw new IllegalArgumentException("Cannot get integer value from " + convertTypeToString(mDataType)); @@ -1324,9 +790,11 @@ public class ExifTag { /** * Gets the {@link #TYPE_ASCII} data. - * @exception IllegalArgumentException If the type is NOT {@link #TYPE_ASCII}. + * + * @exception IllegalArgumentException If the type is NOT + * {@link #TYPE_ASCII}. */ - public String getString() { + protected String getString() { if (mDataType != TYPE_ASCII) { throw new IllegalArgumentException("Cannot get ASCII value from " + convertTypeToString(mDataType)); @@ -1337,27 +805,28 @@ public class ExifTag { /* * Get the converted ascii byte. Used by ExifOutputStream. */ - byte[] getStringByte() { + protected byte[] getStringByte() { return (byte[]) mValue; } /** * Gets the {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL} data. - * @exception IllegalArgumentException If the type is NOT {@link #TYPE_RATIONAL} or - * {@link #TYPE_UNSIGNED_RATIONAL}. + * + * @exception IllegalArgumentException If the type is NOT + * {@link #TYPE_RATIONAL} or {@link #TYPE_UNSIGNED_RATIONAL}. */ - public Rational getRational(int index) { + protected Rational getRational(int index) { if ((mDataType != TYPE_RATIONAL) && (mDataType != TYPE_UNSIGNED_RATIONAL)) { throw new IllegalArgumentException("Cannot get RATIONAL value from " + convertTypeToString(mDataType)); } - return ((Rational[]) mValue) [index]; + return ((Rational[]) mValue)[index]; } /** * Equivalent to getBytes(buffer, 0, buffer.length). */ - public void getBytes(byte[] buf) { + protected void getBytes(byte[] buf) { getBytes(buf, 0, buf.length); } @@ -1366,56 +835,147 @@ public class ExifTag { * * @param buf the byte array in which to store the bytes read. * @param offset the initial position in buffer to store the bytes. - * @param length the maximum number of bytes to store in buffer. If length > component count, - * only the valid bytes will be stored. - * - * @exception IllegalArgumentException If the type is NOT {@link #TYPE_UNDEFINED} or - * {@link #TYPE_UNSIGNED_BYTE}. + * @param length the maximum number of bytes to store in buffer. If length > + * component count, only the valid bytes will be stored. + * @exception IllegalArgumentException If the type is NOT + * {@link #TYPE_UNDEFINED} or {@link #TYPE_UNSIGNED_BYTE}. */ - public void getBytes(byte[] buf, int offset, int length) { + protected void getBytes(byte[] buf, int offset, int length) { if ((mDataType != TYPE_UNDEFINED) && (mDataType != TYPE_UNSIGNED_BYTE)) { throw new IllegalArgumentException("Cannot get BYTE value from " + convertTypeToString(mDataType)); } System.arraycopy(mValue, 0, buf, offset, - (length > mComponentCount) ? mComponentCount : length); + (length > mComponentCountActual) ? mComponentCountActual : length); } /** - * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD}, - * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT}, - * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD} + * Gets the offset of this tag. This is only valid if this data size > 4 and + * contains an offset to the location of the actual value. */ - static boolean isOffsetTag(short tagId) { - return tagId == TAG_EXIF_IFD - || tagId == TAG_GPS_IFD - || tagId == TAG_JPEG_INTERCHANGE_FORMAT - || tagId == TAG_STRIP_OFFSETS - || tagId == TAG_INTEROPERABILITY_IFD; + protected int getOffset() { + return mOffset; } /** - * Returns true if the ID is one of the following: {@link #TAG_EXIF_IFD}, - * {@link #TAG_GPS_IFD}, {@link #TAG_INTEROPERABILITY_IFD} + * Sets the offset of this tag. */ - static boolean isSubIfdOffsetTag(short tagId) { - return tagId == TAG_EXIF_IFD - || tagId == TAG_GPS_IFD - || tagId == TAG_INTEROPERABILITY_IFD; + protected void setOffset(int offset) { + mOffset = offset; + } + + protected void setHasDefinedCount(boolean d) { + mHasDefinedDefaultComponentCount = d; + } + + private boolean checkBadComponentCount(int count) { + if (mHasDefinedDefaultComponentCount && (mComponentCountActual != count)) { + return true; + } + return false; + } + + private static String convertTypeToString(short type) { + switch (type) { + case TYPE_UNSIGNED_BYTE: + return "UNSIGNED_BYTE"; + case TYPE_ASCII: + return "ASCII"; + case TYPE_UNSIGNED_SHORT: + return "UNSIGNED_SHORT"; + case TYPE_UNSIGNED_LONG: + return "UNSIGNED_LONG"; + case TYPE_UNSIGNED_RATIONAL: + return "UNSIGNED_RATIONAL"; + case TYPE_UNDEFINED: + return "UNDEFINED"; + case TYPE_LONG: + return "LONG"; + case TYPE_RATIONAL: + return "RATIONAL"; + default: + return ""; + } + } + + private boolean checkOverflowForUnsignedShort(int[] value) { + for (int v : value) { + if (v > UNSIGNED_SHORT_MAX || v < 0) { + return true; + } + } + return false; } + + private boolean checkOverflowForUnsignedLong(long[] value) { + for (long v : value) { + if (v < 0 || v > UNSIGNED_LONG_MAX) { + return true; + } + } + return false; + } + + private boolean checkOverflowForUnsignedLong(int[] value) { + for (int v : value) { + if (v < 0) { + return true; + } + } + return false; + } + + private boolean checkOverflowForUnsignedRational(Rational[] value) { + for (Rational v : value) { + if (v.getNumerator() < 0 || v.getDenominator() < 0 + || v.getNumerator() > UNSIGNED_LONG_MAX + || v.getDenominator() > UNSIGNED_LONG_MAX) { + return true; + } + } + return false; + } + + private boolean checkOverflowForRational(Rational[] value) { + for (Rational v : value) { + if (v.getNumerator() < LONG_MIN || v.getDenominator() < LONG_MIN + || v.getNumerator() > LONG_MAX + || v.getDenominator() > LONG_MAX) { + return true; + } + } + return false; + } + @Override public boolean equals(Object obj) { + if (obj == null) { + return false; + } if (obj instanceof ExifTag) { ExifTag tag = (ExifTag) obj; + if (tag.mTagId != this.mTagId + || tag.mComponentCountActual != this.mComponentCountActual + || tag.mDataType != this.mDataType) { + return false; + } if (mValue != null) { - if (mValue instanceof long[]) { - if (!(tag.mValue instanceof long[])) return false; + if (tag.mValue == null) { + return false; + } else if (mValue instanceof long[]) { + if (!(tag.mValue instanceof long[])) { + return false; + } return Arrays.equals((long[]) mValue, (long[]) tag.mValue); } else if (mValue instanceof Rational[]) { - if (!(tag.mValue instanceof Rational[])) return false; + if (!(tag.mValue instanceof Rational[])) { + return false; + } return Arrays.equals((Rational[]) mValue, (Rational[]) tag.mValue); } else if (mValue instanceof byte[]) { - if (!(tag.mValue instanceof byte[])) return false; + if (!(tag.mValue instanceof byte[])) { + return false; + } return Arrays.equals((byte[]) mValue, (byte[]) tag.mValue); } else { return mValue.equals(tag.mValue); @@ -1426,4 +986,12 @@ public class ExifTag { } return false; } + + @Override + public String toString() { + return String.format("tag id: %04X\n", mTagId) + "ifd id: " + mIfd + "\ntype: " + + convertTypeToString(mDataType) + "\ncount: " + mComponentCountActual + + "\noffset: " + mOffset + "\nvalue: " + forceGetValueAsString() + "\n"; + } + } diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java index 633604940..093944aec 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java +++ b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java @@ -30,7 +30,10 @@ class IfdData { private final int mIfdId; private final Map mExifTags = new HashMap(); private int mOffsetToNextIfd = 0; - + private static final int[] sIfds = { + IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF, + IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS + }; /** * Creates an IfdData with given IFD ID. * @@ -40,14 +43,18 @@ class IfdData { * @see IfdId#TYPE_IFD_GPS * @see IfdId#TYPE_IFD_INTEROPERABILITY */ - public IfdData(int ifdId) { + IfdData(int ifdId) { mIfdId = ifdId; } + static protected int[] getIfds() { + return sIfds; + } + /** * Get a array the contains all {@link ExifTag} in this IFD. */ - public ExifTag[] getAllTags() { + protected ExifTag[] getAllTags() { return mExifTags.values().toArray(new ExifTag[mExifTags.size()]); } @@ -60,70 +67,86 @@ class IfdData { * @see IfdId#TYPE_IFD_GPS * @see IfdId#TYPE_IFD_INTEROPERABILITY */ - public int getId() { + protected int getId() { return mIfdId; } /** - * Gets the {@link ExifTag} with given tag id. Return null if there is no such tag. + * Gets the {@link ExifTag} with given tag id. Return null if there is no + * such tag. */ - public ExifTag getTag(short tagId) { + protected ExifTag getTag(short tagId) { return mExifTags.get(tagId); } /** * Adds or replaces a {@link ExifTag}. */ - public void setTag(ExifTag tag) { - mExifTags.put(tag.getTagId(), tag); + protected ExifTag setTag(ExifTag tag) { + tag.setIfd(mIfdId); + return mExifTags.put(tag.getTagId(), tag); + } + + protected boolean checkCollision(short tagId) { + return mExifTags.get(tagId) != null; } /** * Removes the tag of the given ID */ - public void removeTag(short tagId) { + protected void removeTag(short tagId) { mExifTags.remove(tagId); } /** * Gets the tags count in the IFD. */ - public int getTagCount() { + protected int getTagCount() { return mExifTags.size(); } /** * Sets the offset of next IFD. */ - void setOffsetToNextIfd(int offset) { + protected void setOffsetToNextIfd(int offset) { mOffsetToNextIfd = offset; } /** * Gets the offset of next IFD. */ - int getOffsetToNextIfd() { + protected int getOffsetToNextIfd() { return mOffsetToNextIfd; } /** - * Returns true if all tags in this two IFDs are equal. Note that tags of IFDs offset or - * thumbnail offset will be ignored. + * Returns true if all tags in this two IFDs are equal. Note that tags of + * IFDs offset or thumbnail offset will be ignored. */ @Override public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } if (obj instanceof IfdData) { IfdData data = (IfdData) obj; if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) { ExifTag[] tags = data.getAllTags(); - for (ExifTag tag: tags) { - if (ExifTag.isOffsetTag(tag.getTagId())) continue; + for (ExifTag tag : tags) { + if (ExifInterface.isOffsetTag(tag.getTagId())) { + continue; + } ExifTag tag2 = mExifTags.get(tag.getTagId()); - if (!tag.equals(tag2)) return false; + if (!tag.equals(tag2)) { + return false; + } } return true; } } return false; } -} \ No newline at end of file +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java index c05fb02eb..7842edbd4 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/IfdId.java +++ b/gallerycommon/src/com/android/gallery3d/exif/IfdId.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.android.gallery3d.exif; /** @@ -26,4 +27,5 @@ public interface IfdId { public static final int TYPE_IFD_GPS = 4; /* This is used in ExifData to allocate enough IfdData */ static final int TYPE_IFD_COUNT = 5; + } diff --git a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java index 4f785a889..428e6b9fc 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java +++ b/gallerycommon/src/com/android/gallery3d/exif/OrderedDataOutputStream.java @@ -29,24 +29,28 @@ class OrderedDataOutputStream extends FilterOutputStream { super(out); } - public void setByteOrder(ByteOrder order) { + public OrderedDataOutputStream setByteOrder(ByteOrder order) { mByteBuffer.order(order); + return this; } - public void writeShort(short value) throws IOException { + public OrderedDataOutputStream writeShort(short value) throws IOException { mByteBuffer.rewind(); mByteBuffer.putShort(value); out.write(mByteBuffer.array(), 0, 2); - } + return this; + } - public void writeInt(int value) throws IOException { + public OrderedDataOutputStream writeInt(int value) throws IOException { mByteBuffer.rewind(); mByteBuffer.putInt(value); out.write(mByteBuffer.array()); + return this; } - public void writeRational(Rational rational) throws IOException { - writeInt((int) rational.getNominator()); + public OrderedDataOutputStream writeRational(Rational rational) throws IOException { + writeInt((int) rational.getNumerator()); writeInt((int) rational.getDenominator()); + return this; } } diff --git a/gallerycommon/src/com/android/gallery3d/exif/Rational.java b/gallerycommon/src/com/android/gallery3d/exif/Rational.java index 3f8c18a88..591d63faf 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/Rational.java +++ b/gallerycommon/src/com/android/gallery3d/exif/Rational.java @@ -17,50 +17,72 @@ package com.android.gallery3d.exif; /** - * The rational data type of EXIF tag. + * The rational data type of EXIF tag. Contains a pair of longs representing the + * numerator and denominator of a Rational number. */ public class Rational { - private final long mNominator; + private final long mNumerator; private final long mDenominator; + /** + * Create a Rational with a given numerator and denominator. + * + * @param nominator + * @param denominator + */ public Rational(long nominator, long denominator) { - mNominator = nominator; + mNumerator = nominator; mDenominator = denominator; } - /* - * Gets the nominator of the rational. + /** + * Create a copy of a Rational. + */ + public Rational(Rational r) { + mNumerator = r.mNumerator; + mDenominator = r.mDenominator; + } + + /** + * Gets the numerator of the rational. */ - public long getNominator() { - return mNominator; + public long getNumerator() { + return mNumerator; } - /* + /** * Gets the denominator of the rational */ public long getDenominator() { return mDenominator; } + /** + * Gets the rational value as type double. Will cause a divide-by-zero error + * if the denominator is 0. + */ + public double toDouble() { + return mNumerator / (double) mDenominator; + } + @Override public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (this == obj) { + return true; + } if (obj instanceof Rational) { Rational data = (Rational) obj; - return mNominator == data.mNominator && mDenominator == data.mDenominator; + return mNumerator == data.mNumerator && mDenominator == data.mDenominator; } return false; } @Override public String toString() { - return mNominator + "/" + mDenominator; - } - - /* - * Gets the rational value as type double. - */ - public double toDouble() { - return mNominator / (double) mDenominator; + return mNumerator + "/" + mDenominator; } -} \ No newline at end of file +} -- cgit v1.2.3