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.camera.exif; 18 19 import com.android.camera.debug.Log; 20 21 import java.io.UnsupportedEncodingException; 22 import java.nio.ByteOrder; 23 import java.util.ArrayList; 24 import java.util.Arrays; 25 import java.util.List; 26 27 /** 28 * This class stores the EXIF header in IFDs according to the JPEG 29 * specification. It is the result produced by {@link ExifReader}. 30 * 31 * @see ExifReader 32 * @see IfdData 33 */ 34 class ExifData { 35 private static final Log.Tag TAG = new Log.Tag("ExifData"); 36 private static final byte[] USER_COMMENT_ASCII = { 37 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 38 }; 39 private static final byte[] USER_COMMENT_JIS = { 40 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 41 }; 42 private static final byte[] USER_COMMENT_UNICODE = { 43 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 44 }; 45 46 private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT]; 47 private byte[] mThumbnail; 48 private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>(); 49 private final ByteOrder mByteOrder; 50 ExifData(ByteOrder order)51 ExifData(ByteOrder order) { 52 mByteOrder = order; 53 } 54 55 /** 56 * Gets the compressed thumbnail. Returns null if there is no compressed 57 * thumbnail. 58 * 59 * @see #hasCompressedThumbnail() 60 */ getCompressedThumbnail()61 protected byte[] getCompressedThumbnail() { 62 return mThumbnail; 63 } 64 65 /** 66 * Sets the compressed thumbnail. 67 */ setCompressedThumbnail(byte[] thumbnail)68 protected void setCompressedThumbnail(byte[] thumbnail) { 69 mThumbnail = thumbnail; 70 } 71 72 /** 73 * Returns true it this header contains a compressed thumbnail. 74 */ hasCompressedThumbnail()75 protected boolean hasCompressedThumbnail() { 76 return mThumbnail != null; 77 } 78 79 /** 80 * Adds an uncompressed strip. 81 */ setStripBytes(int index, byte[] strip)82 protected void setStripBytes(int index, byte[] strip) { 83 if (index < mStripBytes.size()) { 84 mStripBytes.set(index, strip); 85 } else { 86 for (int i = mStripBytes.size(); i < index; i++) { 87 mStripBytes.add(null); 88 } 89 mStripBytes.add(strip); 90 } 91 } 92 93 /** 94 * Gets the strip count. 95 */ getStripCount()96 protected int getStripCount() { 97 return mStripBytes.size(); 98 } 99 100 /** 101 * Gets the strip at the specified index. 102 * 103 * @exceptions #IndexOutOfBoundException 104 */ getStrip(int index)105 protected byte[] getStrip(int index) { 106 return mStripBytes.get(index); 107 } 108 109 /** 110 * Returns true if this header contains uncompressed strip. 111 */ hasUncompressedStrip()112 protected boolean hasUncompressedStrip() { 113 return mStripBytes.size() != 0; 114 } 115 116 /** 117 * Gets the byte order. 118 */ getByteOrder()119 protected ByteOrder getByteOrder() { 120 return mByteOrder; 121 } 122 123 /** 124 * Returns the {@link IfdData} object corresponding to a given IFD if it 125 * exists or null. 126 */ getIfdData(int ifdId)127 protected IfdData getIfdData(int ifdId) { 128 if (ExifTag.isValidIfd(ifdId)) { 129 return mIfdDatas[ifdId]; 130 } 131 return null; 132 } 133 134 /** 135 * Adds IFD data. If IFD data of the same type already exists, it will be 136 * replaced by the new data. 137 */ addIfdData(IfdData data)138 protected void addIfdData(IfdData data) { 139 mIfdDatas[data.getId()] = data; 140 } 141 142 /** 143 * Returns the {@link IfdData} object corresponding to a given IFD or 144 * generates one if none exist. 145 */ getOrCreateIfdData(int ifdId)146 protected IfdData getOrCreateIfdData(int ifdId) { 147 IfdData ifdData = mIfdDatas[ifdId]; 148 if (ifdData == null) { 149 ifdData = new IfdData(ifdId); 150 mIfdDatas[ifdId] = ifdData; 151 } 152 return ifdData; 153 } 154 155 /** 156 * Returns the tag with a given TID in the given IFD if the tag exists. 157 * Otherwise returns null. 158 */ getTag(short tag, int ifd)159 protected ExifTag getTag(short tag, int ifd) { 160 IfdData ifdData = mIfdDatas[ifd]; 161 return (ifdData == null) ? null : ifdData.getTag(tag); 162 } 163 164 /** 165 * Adds the given ExifTag to its default IFD and returns an existing ExifTag 166 * with the same TID or null if none exist. 167 */ addTag(ExifTag tag)168 protected ExifTag addTag(ExifTag tag) { 169 if (tag != null) { 170 int ifd = tag.getIfd(); 171 return addTag(tag, ifd); 172 } 173 return null; 174 } 175 176 /** 177 * Adds the given ExifTag to the given IFD and returns an existing ExifTag 178 * with the same TID or null if none exist. 179 */ addTag(ExifTag tag, int ifdId)180 protected ExifTag addTag(ExifTag tag, int ifdId) { 181 if (tag != null && ExifTag.isValidIfd(ifdId)) { 182 IfdData ifdData = getOrCreateIfdData(ifdId); 183 return ifdData.setTag(tag); 184 } 185 return null; 186 } 187 clearThumbnailAndStrips()188 protected void clearThumbnailAndStrips() { 189 mThumbnail = null; 190 mStripBytes.clear(); 191 } 192 193 /** 194 * Removes the thumbnail and its related tags. IFD1 will be removed. 195 */ removeThumbnailData()196 protected void removeThumbnailData() { 197 clearThumbnailAndStrips(); 198 mIfdDatas[IfdId.TYPE_IFD_1] = null; 199 } 200 201 /** 202 * Removes the tag with a given TID and IFD. 203 */ removeTag(short tagId, int ifdId)204 protected void removeTag(short tagId, int ifdId) { 205 IfdData ifdData = mIfdDatas[ifdId]; 206 if (ifdData == null) { 207 return; 208 } 209 ifdData.removeTag(tagId); 210 } 211 212 /** 213 * Decodes the user comment tag into string as specified in the EXIF 214 * standard. Returns null if decoding failed. 215 */ getUserComment()216 protected String getUserComment() { 217 IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0]; 218 if (ifdData == null) { 219 return null; 220 } 221 ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT)); 222 if (tag == null) { 223 return null; 224 } 225 if (tag.getComponentCount() < 8) { 226 return null; 227 } 228 229 byte[] buf = new byte[tag.getComponentCount()]; 230 tag.getBytes(buf); 231 232 byte[] code = new byte[8]; 233 System.arraycopy(buf, 0, code, 0, 8); 234 235 try { 236 if (Arrays.equals(code, USER_COMMENT_ASCII)) { 237 return new String(buf, 8, buf.length - 8, "US-ASCII"); 238 } else if (Arrays.equals(code, USER_COMMENT_JIS)) { 239 return new String(buf, 8, buf.length - 8, "EUC-JP"); 240 } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) { 241 return new String(buf, 8, buf.length - 8, "UTF-16"); 242 } else { 243 return null; 244 } 245 } catch (UnsupportedEncodingException e) { 246 Log.w(TAG, "Failed to decode the user comment"); 247 return null; 248 } 249 } 250 251 /** 252 * Returns a list of all {@link ExifTag}s in the ExifData. 253 */ getAllTags()254 protected List<ExifTag> getAllTags() { 255 ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); 256 for (IfdData d : mIfdDatas) { 257 if (d != null) { 258 ExifTag[] tags = d.getAllTags(); 259 if (tags != null) { 260 for (ExifTag t : tags) { 261 ret.add(t); 262 } 263 } 264 } 265 } 266 return ret; 267 } 268 269 /** 270 * Returns a list of all {@link ExifTag}s in a given IFD or null if there 271 * are none. 272 */ getAllTagsForIfd(int ifd)273 protected List<ExifTag> getAllTagsForIfd(int ifd) { 274 IfdData d = mIfdDatas[ifd]; 275 if (d == null) { 276 return null; 277 } 278 ExifTag[] tags = d.getAllTags(); 279 if (tags == null) { 280 return null; 281 } 282 ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length); 283 for (ExifTag t : tags) { 284 ret.add(t); 285 } 286 if (ret.size() == 0) { 287 return null; 288 } 289 return ret; 290 } 291 292 /** 293 * Returns a list of all {@link ExifTag}s with a given TID or null if there 294 * are none. 295 */ getAllTagsForTagId(short tag)296 protected List<ExifTag> getAllTagsForTagId(short tag) { 297 ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); 298 for (IfdData d : mIfdDatas) { 299 if (d != null) { 300 ExifTag t = d.getTag(tag); 301 if (t != null) { 302 ret.add(t); 303 } 304 } 305 } 306 if (ret.size() == 0) { 307 return null; 308 } 309 return ret; 310 } 311 312 @Override equals(Object obj)313 public boolean equals(Object obj) { 314 if (this == obj) { 315 return true; 316 } 317 if (obj == null) { 318 return false; 319 } 320 if (obj instanceof ExifData) { 321 ExifData data = (ExifData) obj; 322 if (data.mByteOrder != mByteOrder || 323 data.mStripBytes.size() != mStripBytes.size() || 324 !Arrays.equals(data.mThumbnail, mThumbnail)) { 325 return false; 326 } 327 for (int i = 0; i < mStripBytes.size(); i++) { 328 if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) { 329 return false; 330 } 331 } 332 for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { 333 IfdData ifd1 = data.getIfdData(i); 334 IfdData ifd2 = getIfdData(i); 335 if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) { 336 return false; 337 } 338 } 339 return true; 340 } 341 return false; 342 } 343 344 } 345