1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.gallery3d.exif; 18 19 import java.nio.ByteOrder; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 23 /** 24 * This class stores the EXIF header in IFDs according to the JPEG specification. 25 * It is the result produced by {@link ExifReader}. 26 * @see ExifReader 27 * @see IfdData 28 */ 29 public class ExifData { 30 private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; 31 private byte[] mThumbnail; 32 private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>(); 33 private final ByteOrder mByteOrder; 34 ExifData(ByteOrder order)35 public ExifData(ByteOrder order) { 36 mByteOrder = order; 37 } 38 getIfdData(int ifdId)39 IfdData getIfdData(int ifdId) { 40 return mIfdDatas[ifdId]; 41 } 42 43 /** 44 * Adds IFD data. If IFD data of the same type already exists, 45 * it will be replaced by the new data. 46 */ addIfdData(IfdData data)47 void addIfdData(IfdData data) { 48 mIfdDatas[data.getId()] = data; 49 } 50 51 /** 52 * Gets the compressed thumbnail. Returns null if there is no compressed thumbnail. 53 * 54 * @see #hasCompressedThumbnail() 55 */ getCompressedThumbnail()56 public byte[] getCompressedThumbnail() { 57 return mThumbnail; 58 } 59 60 /** 61 * Sets the compressed thumbnail. 62 */ setCompressedThumbnail(byte[] thumbnail)63 public void setCompressedThumbnail(byte[] thumbnail) { 64 mThumbnail = thumbnail; 65 } 66 67 /** 68 * Returns true it this header contains a compressed thumbnail. 69 */ hasCompressedThumbnail()70 public boolean hasCompressedThumbnail() { 71 return mThumbnail != null; 72 } 73 74 /** 75 * Adds an uncompressed strip. 76 */ setStripBytes(int index, byte[] strip)77 public void setStripBytes(int index, byte[] strip) { 78 if (index < mStripBytes.size()) { 79 mStripBytes.set(index, strip); 80 } else { 81 for (int i = mStripBytes.size(); i < index; i++) { 82 mStripBytes.add(null); 83 } 84 mStripBytes.add(strip); 85 } 86 } 87 88 /** 89 * Gets the strip count. 90 */ getStripCount()91 public int getStripCount() { 92 return mStripBytes.size(); 93 } 94 95 /** 96 * Gets the strip at the specified index. 97 * @exceptions #IndexOutOfBoundException 98 */ getStrip(int index)99 public byte[] getStrip(int index) { 100 return mStripBytes.get(index); 101 } 102 103 /** 104 * Gets the byte order. 105 */ getByteOrder()106 public ByteOrder getByteOrder() { 107 return mByteOrder; 108 } 109 110 /** 111 * Returns true if this header contains uncompressed strip of thumbnail. 112 */ hasUncompressedStrip()113 public boolean hasUncompressedStrip() { 114 return mStripBytes.size() != 0; 115 } 116 117 @Override equals(Object obj)118 public boolean equals(Object obj) { 119 if (obj instanceof ExifData) { 120 ExifData data = (ExifData) obj; 121 if (data.mByteOrder != mByteOrder 122 || !Arrays.equals(data.mThumbnail, mThumbnail) 123 || data.mStripBytes.size() != mStripBytes.size()) return false; 124 125 for (int i = 0; i < mStripBytes.size(); i++) { 126 if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) return false; 127 } 128 129 for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { 130 if (!Util.equals(data.getIfdData(i), getIfdData(i))) return false; 131 } 132 return true; 133 } 134 return false; 135 } 136 137 /** 138 * Adds {@link ExifTag#TAG_GPS_LATITUDE}, {@link ExifTag#TAG_GPS_LONGITUDE}, 139 * {@link ExifTag#TAG_GPS_LATITUDE_REF} and {@link ExifTag#TAG_GPS_LONGITUDE_REF} with the 140 * given latitude and longitude. 141 */ addGpsTags(double latitude, double longitude)142 public void addGpsTags(double latitude, double longitude) { 143 IfdData gpsIfd = getIfdData(IfdId.TYPE_IFD_GPS); 144 if (gpsIfd == null) { 145 gpsIfd = new IfdData(IfdId.TYPE_IFD_GPS); 146 addIfdData(gpsIfd); 147 } 148 ExifTag latTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE, ExifTag.TYPE_RATIONAL, 149 3, IfdId.TYPE_IFD_GPS); 150 ExifTag longTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE, ExifTag.TYPE_RATIONAL, 151 3, IfdId.TYPE_IFD_GPS); 152 ExifTag latRefTag = new ExifTag(ExifTag.TAG_GPS_LATITUDE_REF, 153 ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS); 154 ExifTag longRefTag = new ExifTag(ExifTag.TAG_GPS_LONGITUDE_REF, 155 ExifTag.TYPE_ASCII, 2, IfdId.TYPE_IFD_GPS); 156 latTag.setValue(toExifLatLong(latitude)); 157 longTag.setValue(toExifLatLong(longitude)); 158 latRefTag.setValue(latitude >= 0 159 ? ExifTag.GpsLatitudeRef.NORTH 160 : ExifTag.GpsLatitudeRef.SOUTH); 161 longRefTag.setValue(longitude >= 0 162 ? ExifTag.GpsLongitudeRef.EAST 163 : ExifTag.GpsLongitudeRef.WEST); 164 gpsIfd.setTag(latTag); 165 gpsIfd.setTag(longTag); 166 gpsIfd.setTag(latRefTag); 167 gpsIfd.setTag(longRefTag); 168 } 169 toExifLatLong(double value)170 private static Rational[] toExifLatLong(double value) { 171 // convert to the format dd/1 mm/1 ssss/100 172 value = Math.abs(value); 173 int degrees = (int) value; 174 value = (value - degrees) * 60; 175 int minutes = (int) value; 176 value = (value - minutes) * 6000; 177 int seconds = (int) value; 178 return new Rational[] { 179 new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)}; 180 } 181 getOrCreateIfdData(int ifdId)182 private IfdData getOrCreateIfdData(int ifdId) { 183 IfdData ifdData = mIfdDatas[ifdId]; 184 if (ifdData == null) { 185 ifdData = new IfdData(ifdId); 186 mIfdDatas[ifdId] = ifdData; 187 } 188 return ifdData; 189 } 190 191 /** 192 * Gets the tag with the given tag ID. Returns null if the tag does not exist. For tags 193 * related to interoperability or thumbnail, call {@link #getInteroperabilityTag(short)} and 194 * {@link #getThumbnailTag(short)} respectively. 195 */ getTag(short tagId)196 public ExifTag getTag(short tagId) { 197 int ifdId = ExifTag.getIfdIdFromTagId(tagId); 198 IfdData ifdData = mIfdDatas[ifdId]; 199 return (ifdData == null) ? null : ifdData.getTag(tagId); 200 } 201 202 /** 203 * Gets the thumbnail-related tag with the given tag ID. 204 */ getThumbnailTag(short tagId)205 public ExifTag getThumbnailTag(short tagId) { 206 IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_1]; 207 return (ifdData == null) ? null : ifdData.getTag(tagId); 208 } 209 210 /** 211 * Gets the interoperability-related tag with the given tag ID. 212 */ getInteroperabilityTag(short tagId)213 public ExifTag getInteroperabilityTag(short tagId) { 214 IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_INTEROPERABILITY]; 215 return (ifdData == null) ? null : ifdData.getTag(tagId); 216 } 217 218 /** 219 * Adds a tag with the given tag ID. The original tag will be replaced by the new tag. For tags 220 * related to interoperability or thumbnail, call {@link #addInteroperabilityTag(short)} or 221 * {@link #addThumbnailTag(short)} respectively. 222 * @exception IllegalArgumentException if the tag ID is invalid. 223 */ addTag(short tagId)224 public ExifTag addTag(short tagId) { 225 int ifdId = ExifTag.getIfdIdFromTagId(tagId); 226 IfdData ifdData = getOrCreateIfdData(ifdId); 227 ExifTag tag = ExifTag.buildTag(tagId); 228 ifdData.setTag(tag); 229 return tag; 230 } 231 232 /** 233 * Adds a thumbnail-related tag with the given tag ID. The original tag will be replaced 234 * by the new tag. 235 * @exception IllegalArgumentException if the tag ID is invalid. 236 */ addThumbnailTag(short tagId)237 public ExifTag addThumbnailTag(short tagId) { 238 IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_1); 239 ExifTag tag = ExifTag.buildThumbnailTag(tagId); 240 ifdData.setTag(tag); 241 return tag; 242 } 243 244 /** 245 * Adds an interoperability-related tag with the given tag ID. The original tag will be 246 * replaced by the new tag. 247 * @exception IllegalArgumentException if the tag ID is invalid. 248 */ addInteroperabilityTag(short tagId)249 public ExifTag addInteroperabilityTag(short tagId) { 250 IfdData ifdData = getOrCreateIfdData(IfdId.TYPE_IFD_INTEROPERABILITY); 251 ExifTag tag = ExifTag.buildInteroperabilityTag(tagId); 252 ifdData.setTag(tag); 253 return tag; 254 } 255 removeThumbnailData()256 public void removeThumbnailData() { 257 mThumbnail = null; 258 mStripBytes.clear(); 259 mIfdDatas[IfdId.TYPE_IFD_1] = null; 260 } 261 }