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 android.util.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 String 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 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 */ 61 protected byte[] getCompressedThumbnail() { 62 return mThumbnail; 63 } 64 65 /** 66 * Sets the compressed thumbnail. 67 */ 68 protected void setCompressedThumbnail(byte[] thumbnail) { 69 mThumbnail = thumbnail; 70 } 71 72 /** 73 * Returns true it this header contains a compressed thumbnail. 74 */ 75 protected boolean hasCompressedThumbnail() { 76 return mThumbnail != null; 77 } 78 79 /** 80 * Adds an uncompressed strip. 81 */ 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 */ 96 protected int getStripCount() { 97 return mStripBytes.size(); 98 } 99 100 /** 101 * Gets the strip at the specified index. 102 * 103 * @exceptions #IndexOutOfBoundException 104 */ 105 protected byte[] getStrip(int index) { 106 return mStripBytes.get(index); 107 } 108 109 /** 110 * Returns true if this header contains uncompressed strip. 111 */ 112 protected boolean hasUncompressedStrip() { 113 return mStripBytes.size() != 0; 114 } 115 116 /** 117 * Gets the byte order. 118 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 or null if there 253 * are none. 254 */ 255 protected List<ExifTag> getAllTags() { 256 ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); 257 for (IfdData d : mIfdDatas) { 258 if (d != null) { 259 ExifTag[] tags = d.getAllTags(); 260 if (tags != null) { 261 for (ExifTag t : tags) { 262 ret.add(t); 263 } 264 } 265 } 266 } 267 if (ret.size() == 0) { 268 return null; 269 } 270 return ret; 271 } 272 273 /** 274 * Returns a list of all {@link ExifTag}s in a given IFD or null if there 275 * are none. 276 */ 277 protected List<ExifTag> getAllTagsForIfd(int ifd) { 278 IfdData d = mIfdDatas[ifd]; 279 if (d == null) { 280 return null; 281 } 282 ExifTag[] tags = d.getAllTags(); 283 if (tags == null) { 284 return null; 285 } 286 ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length); 287 for (ExifTag t : tags) { 288 ret.add(t); 289 } 290 if (ret.size() == 0) { 291 return null; 292 } 293 return ret; 294 } 295 296 /** 297 * Returns a list of all {@link ExifTag}s with a given TID or null if there 298 * are none. 299 */ 300 protected List<ExifTag> getAllTagsForTagId(short tag) { 301 ArrayList<ExifTag> ret = new ArrayList<ExifTag>(); 302 for (IfdData d : mIfdDatas) { 303 if (d != null) { 304 ExifTag t = d.getTag(tag); 305 if (t != null) { 306 ret.add(t); 307 } 308 } 309 } 310 if (ret.size() == 0) { 311 return null; 312 } 313 return ret; 314 } 315 316 @Override 317 public boolean equals(Object obj) { 318 if (this == obj) { 319 return true; 320 } 321 if (obj == null) { 322 return false; 323 } 324 if (obj instanceof ExifData) { 325 ExifData data = (ExifData) obj; 326 if (data.mByteOrder != mByteOrder || 327 data.mStripBytes.size() != mStripBytes.size() || 328 !Arrays.equals(data.mThumbnail, mThumbnail)) { 329 return false; 330 } 331 for (int i = 0; i < mStripBytes.size(); i++) { 332 if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) { 333 return false; 334 } 335 } 336 for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) { 337 IfdData ifd1 = data.getIfdData(i); 338 IfdData ifd2 = getIfdData(i); 339 if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) { 340 return false; 341 } 342 } 343 return true; 344 } 345 return false; 346 } 347 348 } 349