Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 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 | |
ztenghui | a16e7b5 | 2013-08-23 11:47:56 -0700 | [diff] [blame] | 17 | package com.android.camera.exif; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 18 | |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 19 | import com.android.camera.debug.Log; |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 20 | |
| 21 | import java.io.UnsupportedEncodingException; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 22 | import java.nio.ByteOrder; |
| 23 | import java.util.ArrayList; |
| 24 | import java.util.Arrays; |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 25 | import java.util.List; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 26 | |
| 27 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 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 |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 33 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 34 | class ExifData { |
Angus Kong | 2bca210 | 2014-03-11 16:27:30 -0700 | [diff] [blame^] | 35 | private static final Log.Tag TAG = new Log.Tag("ExifData"); |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 36 | private static final byte[] USER_COMMENT_ASCII = { |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 37 | 0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00 |
| 38 | }; |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 39 | private static final byte[] USER_COMMENT_JIS = { |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 40 | 0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 41 | }; |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 42 | private static final byte[] USER_COMMENT_UNICODE = { |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 43 | 0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00 |
| 44 | }; |
Earl Ou | 670baca | 2012-11-07 17:56:11 +0800 | [diff] [blame] | 45 | |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 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 | |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 51 | ExifData(ByteOrder order) { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 52 | mByteOrder = order; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 53 | } |
| 54 | |
| 55 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 56 | * Gets the compressed thumbnail. Returns null if there is no compressed |
| 57 | * thumbnail. |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 58 | * |
| 59 | * @see #hasCompressedThumbnail() |
| 60 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 61 | protected byte[] getCompressedThumbnail() { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 62 | return mThumbnail; |
| 63 | } |
| 64 | |
| 65 | /** |
| 66 | * Sets the compressed thumbnail. |
| 67 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 68 | protected void setCompressedThumbnail(byte[] thumbnail) { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 69 | mThumbnail = thumbnail; |
| 70 | } |
| 71 | |
| 72 | /** |
| 73 | * Returns true it this header contains a compressed thumbnail. |
| 74 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 75 | protected boolean hasCompressedThumbnail() { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 76 | return mThumbnail != null; |
| 77 | } |
| 78 | |
| 79 | /** |
| 80 | * Adds an uncompressed strip. |
| 81 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 82 | protected void setStripBytes(int index, byte[] strip) { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 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 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 96 | protected int getStripCount() { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 97 | return mStripBytes.size(); |
| 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Gets the strip at the specified index. |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 102 | * |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 103 | * @exceptions #IndexOutOfBoundException |
| 104 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 105 | protected byte[] getStrip(int index) { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 106 | return mStripBytes.get(index); |
| 107 | } |
| 108 | |
| 109 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 110 | * Returns true if this header contains uncompressed strip. |
| 111 | */ |
| 112 | protected boolean hasUncompressedStrip() { |
| 113 | return mStripBytes.size() != 0; |
| 114 | } |
| 115 | |
| 116 | /** |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 117 | * Gets the byte order. |
| 118 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 119 | protected ByteOrder getByteOrder() { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 120 | return mByteOrder; |
| 121 | } |
| 122 | |
| 123 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 124 | * Returns the {@link IfdData} object corresponding to a given IFD if it |
| 125 | * exists or null. |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 126 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 127 | protected IfdData getIfdData(int ifdId) { |
| 128 | if (ExifTag.isValidIfd(ifdId)) { |
| 129 | return mIfdDatas[ifdId]; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 130 | } |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 131 | return null; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 132 | } |
| 133 | |
| 134 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 135 | * Adds IFD data. If IFD data of the same type already exists, it will be |
| 136 | * replaced by the new data. |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 137 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 138 | protected void addIfdData(IfdData data) { |
| 139 | mIfdDatas[data.getId()] = data; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 140 | } |
| 141 | |
Earl Ou | 670baca | 2012-11-07 17:56:11 +0800 | [diff] [blame] | 142 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 143 | * Returns the {@link IfdData} object corresponding to a given IFD or |
| 144 | * generates one if none exist. |
Earl Ou | 670baca | 2012-11-07 17:56:11 +0800 | [diff] [blame] | 145 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 146 | protected IfdData getOrCreateIfdData(int ifdId) { |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 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 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 156 | * Returns the tag with a given TID in the given IFD if the tag exists. |
| 157 | * Otherwise returns null. |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 158 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 159 | protected ExifTag getTag(short tag, int ifd) { |
| 160 | IfdData ifdData = mIfdDatas[ifd]; |
| 161 | return (ifdData == null) ? null : ifdData.getTag(tag); |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 162 | } |
| 163 | |
| 164 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 165 | * Adds the given ExifTag to its default IFD and returns an existing ExifTag |
| 166 | * with the same TID or null if none exist. |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 167 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 168 | protected ExifTag addTag(ExifTag tag) { |
| 169 | if (tag != null) { |
| 170 | int ifd = tag.getIfd(); |
| 171 | return addTag(tag, ifd); |
| 172 | } |
| 173 | return null; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 174 | } |
| 175 | |
| 176 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 177 | * Adds the given ExifTag to the given IFD and returns an existing ExifTag |
| 178 | * with the same TID or null if none exist. |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 179 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 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; |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 186 | } |
| 187 | |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 188 | protected void clearThumbnailAndStrips() { |
| 189 | mThumbnail = null; |
| 190 | mStripBytes.clear(); |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 191 | } |
| 192 | |
Earl Ou | cdeb788 | 2012-12-07 15:58:19 +0800 | [diff] [blame] | 193 | /** |
| 194 | * Removes the thumbnail and its related tags. IFD1 will be removed. |
| 195 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 196 | protected void removeThumbnailData() { |
| 197 | clearThumbnailAndStrips(); |
Hung-ying Tyan | 2523f43 | 2012-10-19 20:50:50 +0800 | [diff] [blame] | 198 | mIfdDatas[IfdId.TYPE_IFD_1] = null; |
| 199 | } |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 200 | |
| 201 | /** |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 202 | * Removes the tag with a given TID and IFD. |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 203 | */ |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 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() { |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 217 | IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0]; |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 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 | } |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 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 | } |
Earl Ou | 428afc0 | 2012-12-07 16:56:10 +0800 | [diff] [blame] | 249 | } |
Ruben Brunk | c274ded | 2013-03-11 19:00:12 -0700 | [diff] [blame] | 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 | |
Hung-ying Tyan | 1c515f1 | 2012-10-22 16:23:41 -0700 | [diff] [blame] | 348 | } |