Home | History | Annotate | Download | only in exif
      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.messaging.util.exif;
     18 
     19 import android.util.Log;
     20 import com.android.messaging.util.LogUtil;
     21 
     22 import java.io.UnsupportedEncodingException;
     23 import java.nio.ByteOrder;
     24 import java.util.ArrayList;
     25 import java.util.Arrays;
     26 import java.util.List;
     27 
     28 /**
     29  * This class stores the EXIF header in IFDs according to the JPEG
     30  * specification. It is the result produced by {@link ExifReader}.
     31  *
     32  * @see ExifReader
     33  * @see IfdData
     34  */
     35 class ExifData {
     36     private static final String TAG = LogUtil.BUGLE_TAG;
     37     private static final byte[] USER_COMMENT_ASCII = {
     38             0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
     39     };
     40     private static final byte[] USER_COMMENT_JIS = {
     41             0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
     42     };
     43     private static final byte[] USER_COMMENT_UNICODE = {
     44             0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
     45     };
     46 
     47     private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
     48     private byte[] mThumbnail;
     49     private final ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
     50     private final ByteOrder mByteOrder;
     51 
     52     ExifData(ByteOrder order) {
     53         mByteOrder = order;
     54     }
     55 
     56     /**
     57      * Gets the compressed thumbnail. Returns null if there is no compressed
     58      * thumbnail.
     59      *
     60      * @see #hasCompressedThumbnail()
     61      */
     62     protected byte[] getCompressedThumbnail() {
     63         return mThumbnail;
     64     }
     65 
     66     /**
     67      * Sets the compressed thumbnail.
     68      */
     69     protected void setCompressedThumbnail(byte[] thumbnail) {
     70         mThumbnail = thumbnail;
     71     }
     72 
     73     /**
     74      * Returns true it this header contains a compressed thumbnail.
     75      */
     76     protected boolean hasCompressedThumbnail() {
     77         return mThumbnail != null;
     78     }
     79 
     80     /**
     81      * Adds an uncompressed strip.
     82      */
     83     protected void setStripBytes(int index, byte[] strip) {
     84         if (index < mStripBytes.size()) {
     85             mStripBytes.set(index, strip);
     86         } else {
     87             for (int i = mStripBytes.size(); i < index; i++) {
     88                 mStripBytes.add(null);
     89             }
     90             mStripBytes.add(strip);
     91         }
     92     }
     93 
     94     /**
     95      * Gets the strip count.
     96      */
     97     protected int getStripCount() {
     98         return mStripBytes.size();
     99     }
    100 
    101     /**
    102      * Gets the strip at the specified index.
    103      *
    104      * @exceptions #IndexOutOfBoundException
    105      */
    106     protected byte[] getStrip(int index) {
    107         return mStripBytes.get(index);
    108     }
    109 
    110     /**
    111      * Returns true if this header contains uncompressed strip.
    112      */
    113     protected boolean hasUncompressedStrip() {
    114         return mStripBytes.size() != 0;
    115     }
    116 
    117     /**
    118      * Gets the byte order.
    119      */
    120     protected ByteOrder getByteOrder() {
    121         return mByteOrder;
    122     }
    123 
    124     /**
    125      * Returns the {@link IfdData} object corresponding to a given IFD if it
    126      * exists or null.
    127      */
    128     protected IfdData getIfdData(int ifdId) {
    129         if (ExifTag.isValidIfd(ifdId)) {
    130             return mIfdDatas[ifdId];
    131         }
    132         return null;
    133     }
    134 
    135     /**
    136      * Adds IFD data. If IFD data of the same type already exists, it will be
    137      * replaced by the new data.
    138      */
    139     protected void addIfdData(IfdData data) {
    140         mIfdDatas[data.getId()] = data;
    141     }
    142 
    143     /**
    144      * Returns the {@link IfdData} object corresponding to a given IFD or
    145      * generates one if none exist.
    146      */
    147     protected IfdData getOrCreateIfdData(int ifdId) {
    148         IfdData ifdData = mIfdDatas[ifdId];
    149         if (ifdData == null) {
    150             ifdData = new IfdData(ifdId);
    151             mIfdDatas[ifdId] = ifdData;
    152         }
    153         return ifdData;
    154     }
    155 
    156     /**
    157      * Returns the tag with a given TID in the given IFD if the tag exists.
    158      * Otherwise returns null.
    159      */
    160     protected ExifTag getTag(short tag, int ifd) {
    161         IfdData ifdData = mIfdDatas[ifd];
    162         return (ifdData == null) ? null : ifdData.getTag(tag);
    163     }
    164 
    165     /**
    166      * Adds the given ExifTag to its default IFD and returns an existing ExifTag
    167      * with the same TID or null if none exist.
    168      */
    169     protected ExifTag addTag(ExifTag tag) {
    170         if (tag != null) {
    171             int ifd = tag.getIfd();
    172             return addTag(tag, ifd);
    173         }
    174         return null;
    175     }
    176 
    177     /**
    178      * Adds the given ExifTag to the given IFD and returns an existing ExifTag
    179      * with the same TID or null if none exist.
    180      */
    181     protected ExifTag addTag(ExifTag tag, int ifdId) {
    182         if (tag != null && ExifTag.isValidIfd(ifdId)) {
    183             IfdData ifdData = getOrCreateIfdData(ifdId);
    184             return ifdData.setTag(tag);
    185         }
    186         return null;
    187     }
    188 
    189     protected void clearThumbnailAndStrips() {
    190         mThumbnail = null;
    191         mStripBytes.clear();
    192     }
    193 
    194     /**
    195      * Removes the thumbnail and its related tags. IFD1 will be removed.
    196      */
    197     protected void removeThumbnailData() {
    198         clearThumbnailAndStrips();
    199         mIfdDatas[IfdId.TYPE_IFD_1] = null;
    200     }
    201 
    202     /**
    203      * Removes the tag with a given TID and IFD.
    204      */
    205     protected void removeTag(short tagId, int ifdId) {
    206         IfdData ifdData = mIfdDatas[ifdId];
    207         if (ifdData == null) {
    208             return;
    209         }
    210         ifdData.removeTag(tagId);
    211     }
    212 
    213     /**
    214      * Decodes the user comment tag into string as specified in the EXIF
    215      * standard. Returns null if decoding failed.
    216      */
    217     protected String getUserComment() {
    218         IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
    219         if (ifdData == null) {
    220             return null;
    221         }
    222         ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
    223         if (tag == null) {
    224             return null;
    225         }
    226         if (tag.getComponentCount() < 8) {
    227             return null;
    228         }
    229 
    230         byte[] buf = new byte[tag.getComponentCount()];
    231         tag.getBytes(buf);
    232 
    233         byte[] code = new byte[8];
    234         System.arraycopy(buf, 0, code, 0, 8);
    235 
    236         try {
    237             if (Arrays.equals(code, USER_COMMENT_ASCII)) {
    238                 return new String(buf, 8, buf.length - 8, "US-ASCII");
    239             } else if (Arrays.equals(code, USER_COMMENT_JIS)) {
    240                 return new String(buf, 8, buf.length - 8, "EUC-JP");
    241             } else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
    242                 return new String(buf, 8, buf.length - 8, "UTF-16");
    243             } else {
    244                 return null;
    245             }
    246         } catch (UnsupportedEncodingException e) {
    247             Log.w(TAG, "Failed to decode the user comment");
    248             return null;
    249         }
    250     }
    251 
    252     /**
    253      * Returns a list of all {@link ExifTag}s in the ExifData or null if there
    254      * are none.
    255      */
    256     protected List<ExifTag> getAllTags() {
    257         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
    258         for (IfdData d : mIfdDatas) {
    259             if (d != null) {
    260                 ExifTag[] tags = d.getAllTags();
    261                 if (tags != null) {
    262                     for (ExifTag t : tags) {
    263                         ret.add(t);
    264                     }
    265                 }
    266             }
    267         }
    268         if (ret.size() == 0) {
    269             return null;
    270         }
    271         return ret;
    272     }
    273 
    274     /**
    275      * Returns a list of all {@link ExifTag}s in a given IFD or null if there
    276      * are none.
    277      */
    278     protected List<ExifTag> getAllTagsForIfd(int ifd) {
    279         IfdData d = mIfdDatas[ifd];
    280         if (d == null) {
    281             return null;
    282         }
    283         ExifTag[] tags = d.getAllTags();
    284         if (tags == null) {
    285             return null;
    286         }
    287         ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
    288         for (ExifTag t : tags) {
    289             ret.add(t);
    290         }
    291         if (ret.size() == 0) {
    292             return null;
    293         }
    294         return ret;
    295     }
    296 
    297     /**
    298      * Returns a list of all {@link ExifTag}s with a given TID or null if there
    299      * are none.
    300      */
    301     protected List<ExifTag> getAllTagsForTagId(short tag) {
    302         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
    303         for (IfdData d : mIfdDatas) {
    304             if (d != null) {
    305                 ExifTag t = d.getTag(tag);
    306                 if (t != null) {
    307                     ret.add(t);
    308                 }
    309             }
    310         }
    311         if (ret.size() == 0) {
    312             return null;
    313         }
    314         return ret;
    315     }
    316 
    317     @Override
    318     public boolean equals(Object obj) {
    319         if (this == obj) {
    320             return true;
    321         }
    322         if (obj == null) {
    323             return false;
    324         }
    325         if (obj instanceof ExifData) {
    326             ExifData data = (ExifData) obj;
    327             if (data.mByteOrder != mByteOrder ||
    328                     data.mStripBytes.size() != mStripBytes.size() ||
    329                     !Arrays.equals(data.mThumbnail, mThumbnail)) {
    330                 return false;
    331             }
    332             for (int i = 0; i < mStripBytes.size(); i++) {
    333                 if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
    334                     return false;
    335                 }
    336             }
    337             for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
    338                 IfdData ifd1 = data.getIfdData(i);
    339                 IfdData ifd2 = getIfdData(i);
    340                 if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
    341                     return false;
    342                 }
    343             }
    344             return true;
    345         }
    346         return false;
    347     }
    348 
    349 }
    350