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.camera.exif;
     18 
     19 import com.android.camera.debug.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 Log.Tag TAG = new Log.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 
     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      */
     61     protected byte[] getCompressedThumbnail() {
     62         return mThumbnail;
     63     }
     64 
     65     /**
     66      * Sets the compressed thumbnail.
     67      */
     68     protected void setCompressedThumbnail(byte[] thumbnail) {
     69         mThumbnail = thumbnail;
     70     }
     71 
     72     /**
     73      * Returns true it this header contains a compressed thumbnail.
     74      */
     75     protected boolean hasCompressedThumbnail() {
     76         return mThumbnail != null;
     77     }
     78 
     79     /**
     80      * Adds an uncompressed strip.
     81      */
     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      */
     96     protected int getStripCount() {
     97         return mStripBytes.size();
     98     }
     99 
    100     /**
    101      * Gets the strip at the specified index.
    102      *
    103      * @exceptions #IndexOutOfBoundException
    104      */
    105     protected byte[] getStrip(int index) {
    106         return mStripBytes.get(index);
    107     }
    108 
    109     /**
    110      * Returns true if this header contains uncompressed strip.
    111      */
    112     protected boolean hasUncompressedStrip() {
    113         return mStripBytes.size() != 0;
    114     }
    115 
    116     /**
    117      * Gets the byte order.
    118      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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() {
    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.
    253      */
    254     protected List<ExifTag> getAllTags() {
    255         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
    256         for (IfdData d : mIfdDatas) {
    257             if (d != null) {
    258                 ExifTag[] tags = d.getAllTags();
    259                 if (tags != null) {
    260                     for (ExifTag t : tags) {
    261                         ret.add(t);
    262                     }
    263                 }
    264             }
    265         }
    266         return ret;
    267     }
    268 
    269     /**
    270      * Returns a list of all {@link ExifTag}s in a given IFD or null if there
    271      * are none.
    272      */
    273     protected List<ExifTag> getAllTagsForIfd(int ifd) {
    274         IfdData d = mIfdDatas[ifd];
    275         if (d == null) {
    276             return null;
    277         }
    278         ExifTag[] tags = d.getAllTags();
    279         if (tags == null) {
    280             return null;
    281         }
    282         ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
    283         for (ExifTag t : tags) {
    284             ret.add(t);
    285         }
    286         if (ret.size() == 0) {
    287             return null;
    288         }
    289         return ret;
    290     }
    291 
    292     /**
    293      * Returns a list of all {@link ExifTag}s with a given TID or null if there
    294      * are none.
    295      */
    296     protected List<ExifTag> getAllTagsForTagId(short tag) {
    297         ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
    298         for (IfdData d : mIfdDatas) {
    299             if (d != null) {
    300                 ExifTag t = d.getTag(tag);
    301                 if (t != null) {
    302                     ret.add(t);
    303                 }
    304             }
    305         }
    306         if (ret.size() == 0) {
    307             return null;
    308         }
    309         return ret;
    310     }
    311 
    312     @Override
    313     public boolean equals(Object obj) {
    314         if (this == obj) {
    315             return true;
    316         }
    317         if (obj == null) {
    318             return false;
    319         }
    320         if (obj instanceof ExifData) {
    321             ExifData data = (ExifData) obj;
    322             if (data.mByteOrder != mByteOrder ||
    323                     data.mStripBytes.size() != mStripBytes.size() ||
    324                     !Arrays.equals(data.mThumbnail, mThumbnail)) {
    325                 return false;
    326             }
    327             for (int i = 0; i < mStripBytes.size(); i++) {
    328                 if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
    329                     return false;
    330                 }
    331             }
    332             for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
    333                 IfdData ifd1 = data.getIfdData(i);
    334                 IfdData ifd2 = getIfdData(i);
    335                 if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
    336                     return false;
    337                 }
    338             }
    339             return true;
    340         }
    341         return false;
    342     }
    343 
    344 }
    345