• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.dialer.callcomposer.camera.exif;
18 
19 import android.annotation.SuppressLint;
20 import android.graphics.Bitmap;
21 import android.util.SparseIntArray;
22 import java.io.ByteArrayInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.OutputStream;
26 import java.text.DateFormat;
27 import java.text.SimpleDateFormat;
28 import java.util.HashSet;
29 import java.util.TimeZone;
30 
31 /**
32  * This class provides methods and constants for reading and writing jpeg file metadata. It contains
33  * a collection of ExifTags, and a collection of definitions for creating valid ExifTags. The
34  * collection of ExifTags can be updated by: reading new ones from a file, deleting or adding
35  * existing ones, or building new ExifTags from a tag definition. These ExifTags can be written to a
36  * valid jpeg image as exif metadata.
37  *
38  * <p>Each ExifTag has a tag ID (TID) and is stored in a specific image file directory (IFD) as
39  * specified by the exif standard. A tag definition can be looked up with a constant that is a
40  * combination of TID and IFD. This definition has information about the type, number of components,
41  * and valid IFDs for a tag.
42  *
43  * @see ExifTag
44  */
45 public class ExifInterface {
46   private static final int IFD_NULL = -1;
47   static final int DEFINITION_NULL = 0;
48 
49   /** Tag constants for Jeita EXIF 2.2 */
50   // IFD 0
51   public static final int TAG_ORIENTATION = defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
52 
53   static final int TAG_EXIF_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
54   static final int TAG_GPS_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
55   static final int TAG_STRIP_OFFSETS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
56   static final int TAG_STRIP_BYTE_COUNTS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
57   // IFD 1
58   static final int TAG_JPEG_INTERCHANGE_FORMAT = defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
59   static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
60   // IFD Exif Tags
61   static final int TAG_INTEROPERABILITY_IFD = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
62 
63   /** Tags that contain offset markers. These are included in the banned defines. */
64   private static HashSet<Short> offsetTags = new HashSet<>();
65 
66   static {
getTrueTagKey(TAG_GPS_IFD)67     offsetTags.add(getTrueTagKey(TAG_GPS_IFD));
getTrueTagKey(TAG_EXIF_IFD)68     offsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT)69     offsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
getTrueTagKey(TAG_INTEROPERABILITY_IFD)70     offsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
getTrueTagKey(TAG_STRIP_OFFSETS)71     offsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
72   }
73 
74   private static final String NULL_ARGUMENT_STRING = "Argument is null";
75 
76   private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
77 
78   private ExifData data = new ExifData();
79 
80   @SuppressLint("SimpleDateFormat")
ExifInterface()81   public ExifInterface() {
82     DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
83     mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
84   }
85 
86   /**
87    * Reads the exif tags from a byte array, clearing this ExifInterface object's existing exif tags.
88    *
89    * @param jpeg a byte array containing a jpeg compressed image.
90    * @throws java.io.IOException
91    */
readExif(byte[] jpeg)92   public void readExif(byte[] jpeg) throws IOException {
93     readExif(new ByteArrayInputStream(jpeg));
94   }
95 
96   /**
97    * Reads the exif tags from an InputStream, clearing this ExifInterface object's existing exif
98    * tags.
99    *
100    * @param inStream an InputStream containing a jpeg compressed image.
101    * @throws java.io.IOException
102    */
readExif(InputStream inStream)103   private void readExif(InputStream inStream) throws IOException {
104     if (inStream == null) {
105       throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
106     }
107     ExifData d;
108     try {
109       d = new ExifReader(this).read(inStream);
110     } catch (ExifInvalidFormatException e) {
111       throw new IOException("Invalid exif format : " + e);
112     }
113     data = d;
114   }
115 
116   /** Returns the TID for a tag constant. */
getTrueTagKey(int tag)117   static short getTrueTagKey(int tag) {
118     // Truncate
119     return (short) tag;
120   }
121 
122   /** Returns the constant representing a tag with a given TID and default IFD. */
defineTag(int ifdId, short tagId)123   private static int defineTag(int ifdId, short tagId) {
124     return (tagId & 0x0000ffff) | (ifdId << 16);
125   }
126 
isIfdAllowed(int info, int ifd)127   static boolean isIfdAllowed(int info, int ifd) {
128     int[] ifds = IfdData.getIfds();
129     int ifdFlags = getAllowedIfdFlagsFromInfo(info);
130     for (int i = 0; i < ifds.length; i++) {
131       if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
132         return true;
133       }
134     }
135     return false;
136   }
137 
getAllowedIfdFlagsFromInfo(int info)138   private static int getAllowedIfdFlagsFromInfo(int info) {
139     return info >>> 24;
140   }
141 
142   /**
143    * Returns true if tag TID is one of the following: {@code TAG_EXIF_IFD}, {@code TAG_GPS_IFD},
144    * {@code TAG_JPEG_INTERCHANGE_FORMAT}, {@code TAG_STRIP_OFFSETS}, {@code
145    * TAG_INTEROPERABILITY_IFD}
146    *
147    * <p>Note: defining tags with these TID's is disallowed.
148    *
149    * @param tag a tag's TID (can be obtained from a defined tag constant with {@link
150    *     #getTrueTagKey}).
151    * @return true if the TID is that of an offset tag.
152    */
isOffsetTag(short tag)153   static boolean isOffsetTag(short tag) {
154     return offsetTags.contains(tag);
155   }
156 
157   private SparseIntArray tagInfo = null;
158 
getTagInfo()159   SparseIntArray getTagInfo() {
160     if (tagInfo == null) {
161       tagInfo = new SparseIntArray();
162       initTagInfo();
163     }
164     return tagInfo;
165   }
166 
initTagInfo()167   private void initTagInfo() {
168     /**
169      * We put tag information in a 4-bytes integer. The first byte a bitmask representing the
170      * allowed IFDs of the tag, the second byte is the data type, and the last two byte are a short
171      * value indicating the default component count of this tag.
172      */
173     // IFD0 tags
174     int[] ifdAllowedIfds = {IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1};
175     int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
176     tagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
177     tagInfo.put(ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
178     tagInfo.put(ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
179     tagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
180     tagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
181     // IFD1 tags
182     int[] ifd1AllowedIfds = {IfdId.TYPE_IFD_1};
183     int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
184     tagInfo.put(
185         ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
186         ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
187     tagInfo.put(
188         ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
189         ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
190     // Exif tags
191     int[] exifAllowedIfds = {IfdId.TYPE_IFD_EXIF};
192     int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
193     tagInfo.put(
194         ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
195   }
196 
getFlagsFromAllowedIfds(int[] allowedIfds)197   private static int getFlagsFromAllowedIfds(int[] allowedIfds) {
198     if (allowedIfds == null || allowedIfds.length == 0) {
199       return 0;
200     }
201     int flags = 0;
202     int[] ifds = IfdData.getIfds();
203     for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
204       for (int j : allowedIfds) {
205         if (ifds[i] == j) {
206           flags |= 1 << i;
207           break;
208         }
209       }
210     }
211     return flags;
212   }
213 
getTagIntValue(int tagId, int ifdId)214   private Integer getTagIntValue(int tagId, int ifdId) {
215     int[] l = getTagIntValues(tagId, ifdId);
216     if (l == null || l.length <= 0) {
217       return null;
218     }
219     return l[0];
220   }
221 
getTagIntValues(int tagId, int ifdId)222   private int[] getTagIntValues(int tagId, int ifdId) {
223     ExifTag t = getTag(tagId, ifdId);
224     if (t == null) {
225       return null;
226     }
227     return t.getValueAsInts();
228   }
229 
230   /** Gets an ExifTag for an IFD other than the tag's default. */
getTag(int tagId, int ifdId)231   public ExifTag getTag(int tagId, int ifdId) {
232     if (!ExifTag.isValidIfd(ifdId)) {
233       return null;
234     }
235     return data.getTag(getTrueTagKey(tagId), ifdId);
236   }
237 
getTagIntValue(int tagId)238   public Integer getTagIntValue(int tagId) {
239     int ifdId = getDefinedTagDefaultIfd(tagId);
240     return getTagIntValue(tagId, ifdId);
241   }
242 
243   /**
244    * Gets the default IFD for a tag.
245    *
246    * @param tagId a defined tag constant, e.g. {@link #TAG_EXIF_IFD}.
247    * @return the default IFD for a tag definition or {@link #IFD_NULL} if no definition exists.
248    */
getDefinedTagDefaultIfd(int tagId)249   private int getDefinedTagDefaultIfd(int tagId) {
250     int info = getTagInfo().get(tagId);
251     if (info == DEFINITION_NULL) {
252       return IFD_NULL;
253     }
254     return getTrueIfd(tagId);
255   }
256 
257   /** Returns the default IFD for a tag constant. */
getTrueIfd(int tag)258   private static int getTrueIfd(int tag) {
259     return tag >>> 16;
260   }
261 
262   /**
263    * Constants for {@code TAG_ORIENTATION}. They can be interpreted as follows:
264    *
265    * <ul>
266    *   <li>TOP_LEFT is the normal orientation.
267    *   <li>TOP_RIGHT is a left-right mirror.
268    *   <li>BOTTOM_LEFT is a 180 degree rotation.
269    *   <li>BOTTOM_RIGHT is a top-bottom mirror.
270    *   <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.
271    *   <li>RIGHT_TOP is a 90 degree clockwise rotation.
272    *   <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.
273    *   <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.
274    * </ul>
275    */
276   interface Orientation {
277     short TOP_LEFT = 1;
278     short TOP_RIGHT = 2;
279     short BOTTOM_LEFT = 3;
280     short BOTTOM_RIGHT = 4;
281     short LEFT_TOP = 5;
282     short RIGHT_TOP = 6;
283     short LEFT_BOTTOM = 7;
284     short RIGHT_BOTTOM = 8;
285   }
286 
287   /** Wrapper class to define some orientation parameters. */
288   public static class OrientationParams {
289     public int rotation = 0;
290     int scaleX = 1;
291     int scaleY = 1;
292     public boolean invertDimensions = false;
293   }
294 
getOrientationParams(int orientation)295   public static OrientationParams getOrientationParams(int orientation) {
296     OrientationParams params = new OrientationParams();
297     switch (orientation) {
298       case Orientation.TOP_RIGHT: // Flip horizontal
299         params.scaleX = -1;
300         break;
301       case Orientation.BOTTOM_RIGHT: // Flip vertical
302         params.scaleY = -1;
303         break;
304       case Orientation.BOTTOM_LEFT: // Rotate 180
305         params.rotation = 180;
306         break;
307       case Orientation.RIGHT_BOTTOM: // Rotate 270
308         params.rotation = 270;
309         params.invertDimensions = true;
310         break;
311       case Orientation.RIGHT_TOP: // Rotate 90
312         params.rotation = 90;
313         params.invertDimensions = true;
314         break;
315       case Orientation.LEFT_TOP: // Transpose
316         params.rotation = 90;
317         params.scaleX = -1;
318         params.invertDimensions = true;
319         break;
320       case Orientation.LEFT_BOTTOM: // Transverse
321         params.rotation = 270;
322         params.scaleX = -1;
323         params.invertDimensions = true;
324         break;
325     }
326     return params;
327   }
328 
329   /** Clears this ExifInterface object's existing exif tags. */
clearExif()330   public void clearExif() {
331     data = new ExifData();
332   }
333 
334   /**
335    * Puts an ExifTag into this ExifInterface object's tags, removing a previous ExifTag with the
336    * same TID and IFD. The IFD it is put into will be the one the tag was created with in {@link
337    * #buildTag}.
338    *
339    * @param tag an ExifTag to put into this ExifInterface's tags.
340    * @return the previous ExifTag with the same TID and IFD or null if none exists.
341    */
setTag(ExifTag tag)342   public ExifTag setTag(ExifTag tag) {
343     return data.addTag(tag);
344   }
345 
346   /**
347    * Returns the ExifTag in that tag's default IFD for a defined tag constant or null if none
348    * exists.
349    *
350    * @param tagId a defined tag constant, e.g. {@link #TAG_EXIF_IFD}.
351    * @return an {@link ExifTag} or null if none exists.
352    */
getTag(int tagId)353   public ExifTag getTag(int tagId) {
354     int ifdId = getDefinedTagDefaultIfd(tagId);
355     return getTag(tagId, ifdId);
356   }
357 
358   /**
359    * Writes the tags from this ExifInterface object into a jpeg compressed bitmap, removing prior
360    * exif tags.
361    *
362    * @param bmap a bitmap to compress and write exif into.
363    * @param exifOutStream the OutputStream to which the jpeg image with added exif tags will be
364    *     written.
365    * @throws java.io.IOException
366    */
writeExif(Bitmap bmap, OutputStream exifOutStream)367   public void writeExif(Bitmap bmap, OutputStream exifOutStream) throws IOException {
368     if (bmap == null || exifOutStream == null) {
369       throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
370     }
371     bmap.compress(Bitmap.CompressFormat.JPEG, 90, exifOutStream);
372     exifOutStream.flush();
373   }
374 }
375