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 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 
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 or null if there
253      * are none.
254      */
getAllTags()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      */
getAllTagsForIfd(int ifd)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      */
getAllTagsForTagId(short tag)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
equals(Object obj)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