Home | History | Annotate | Download | only in exif
      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> sOffsetTags = new HashSet<>();
     65 
     66   static {
     67     sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
     68     sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
     69     sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
     70     sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
     71     sOffsetTags.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 mData = new ExifData();
     79 
     80   @SuppressLint("SimpleDateFormat")
     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    */
     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    */
    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     mData = d;
    114   }
    115 
    116   /** Returns the TID for a tag constant. */
    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. */
    123   private static int defineTag(int ifdId, short tagId) {
    124     return (tagId & 0x0000ffff) | (ifdId << 16);
    125   }
    126 
    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 
    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    */
    153   static boolean isOffsetTag(short tag) {
    154     return sOffsetTags.contains(tag);
    155   }
    156 
    157   private SparseIntArray mTagInfo = null;
    158 
    159   SparseIntArray getTagInfo() {
    160     if (mTagInfo == null) {
    161       mTagInfo = new SparseIntArray();
    162       initTagInfo();
    163     }
    164     return mTagInfo;
    165   }
    166 
    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     mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
    177     mTagInfo.put(ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
    178     mTagInfo.put(ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
    179     mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
    180     mTagInfo.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     mTagInfo.put(
    185         ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT,
    186         ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
    187     mTagInfo.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     mTagInfo.put(
    194         ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
    195   }
    196 
    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 
    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 
    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. */
    231   public ExifTag getTag(int tagId, int ifdId) {
    232     if (!ExifTag.isValidIfd(ifdId)) {
    233       return null;
    234     }
    235     return mData.getTag(getTrueTagKey(tagId), ifdId);
    236   }
    237 
    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    */
    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. */
    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 
    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. */
    330   public void clearExif() {
    331     mData = 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    */
    342   public ExifTag setTag(ExifTag tag) {
    343     return mData.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    */
    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    */
    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