blob: 6e5c227d55ae1bad06660b74b9d9afd8fb96afad [file] [log] [blame]
/*
* Copyright (C) 2012 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 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;
/**
* 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 {
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"));
private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
private byte[] mThumbnail;
private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
private final ByteOrder mByteOrder;
public 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.
*
* @see #hasCompressedThumbnail()
*/
public byte[] getCompressedThumbnail() {
return mThumbnail;
}
/**
* Sets the compressed thumbnail.
*/
public void setCompressedThumbnail(byte[] thumbnail) {
mThumbnail = thumbnail;
}
/**
* Returns true it this header contains a compressed thumbnail.
*/
public boolean hasCompressedThumbnail() {
return mThumbnail != null;
}
/**
* Adds an uncompressed strip.
*/
public void setStripBytes(int index, byte[] strip) {
if (index < mStripBytes.size()) {
mStripBytes.set(index, strip);
} else {
for (int i = mStripBytes.size(); i < index; i++) {
mStripBytes.add(null);
}
mStripBytes.add(strip);
}
}
/**
* Gets the strip count.
*/
public int getStripCount() {
return mStripBytes.size();
}
/**
* Gets the strip at the specified index.
* @exceptions #IndexOutOfBoundException
*/
public byte[] getStrip(int index) {
return mStripBytes.get(index);
}
/**
* Gets the byte order.
*/
public ByteOrder getByteOrder() {
return mByteOrder;
}
/**
* Returns true if this header contains uncompressed strip of thumbnail.
*/
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;
}
/**
* 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.
*/
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);
}
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);
}
/**
* 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.
*
*/
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));
}
}
/**
* 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.
*/
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) {
IfdData ifdData = mIfdDatas[ifdId];
if (ifdData == null) {
ifdData = new IfdData(ifdId);
mIfdDatas[ifdId] = ifdData;
}
return ifdData;
}
/**
* 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.
*/
public ExifTag getInteroperabilityTag(short tagId) {
IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY];
return (ifdData == null) ? null : ifdData.getTag(tagId);
}
/**
* Adds a tag with the given tag ID. If the tag of the given ID already exists,
* the original tag will be returned. Otherwise, a new ExifTag will be created. For tags
* related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or
* {@link #addThumbnailTag(short)} respectively.
* @exception IllegalArgumentException if the tag ID is invalid.
*/
public ExifTag addTag(short tagId) {
int ifdId = ExifTag.getIfdIdFromTagId(tagId);
IfdData ifdData = getOrCreateIfdData(ifdId);
ExifTag tag = ifdData.getTag(tagId);
if (tag == null) {
tag = ExifTag.buildTag(tagId);
ifdData.setTag(tag);
}
return tag;
}
/**
* Adds a thumbnail-related tag with the given tag ID. If the tag of the given ID
* already exists, the original tag will be returned. Otherwise, a new ExifTag will
* be created.
* @exception IllegalArgumentException if the tag ID is invalid.
*/
public ExifTag addThumbnailTag(short tagId) {
IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1);
ExifTag tag = ifdData.getTag(tagId);
if (tag == null) {
tag = ExifTag.buildThumbnailTag(tagId);
ifdData.setTag(tag);
}
return tag;
}
/**
* Adds an interoperability-related tag with the given tag ID. If the tag of the given ID
* already exists, the original tag will be returned. Otherwise, a new ExifTag will
* be created.
* @exception IllegalArgumentException if the tag ID is invalid.
*/
public ExifTag addInteroperabilityTag(short tagId) {
IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
ExifTag tag = ifdData.getTag(tagId);
if (tag == null) {
tag = ExifTag.buildInteroperabilityTag(tagId);
ifdData.setTag(tag);
}
return tag;
}
public void removeThumbnailData() {
mThumbnail = null;
mStripBytes.clear();
mIfdDatas[IfdId.TYPE_IFD_1] = null;
}
}