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.gallery3d.exif;
     18 
     19 import java.io.FilterOutputStream;
     20 import java.io.IOException;
     21 import java.io.OutputStream;
     22 import java.nio.ByteBuffer;
     23 import java.nio.ByteOrder;
     24 
     25 public class ExifOutputStream extends FilterOutputStream {
     26     private static final String TAG = "ExifOutputStream";
     27 
     28     private static final int STATE_SOI = 0;
     29     private static final int STATE_FRAME_HEADER = 1;
     30     private static final int STATE_JPEG_DATA = 2;
     31 
     32     private static final int EXIF_HEADER = 0x45786966;
     33     private static final short TIFF_HEADER = 0x002A;
     34     private static final short TIFF_BIG_ENDIAN = 0x4d4d;
     35     private static final short TIFF_LITTLE_ENDIAN = 0x4949;
     36     private static final short TAG_SIZE = 12;
     37     private static final short TIFF_HEADER_SIZE = 8;
     38 
     39     private ExifData mExifData;
     40     private int mState = STATE_SOI;
     41     private int mByteToSkip;
     42     private int mByteToCopy;
     43     private ByteBuffer mBuffer = ByteBuffer.allocate(4);
     44 
     45     public ExifOutputStream(OutputStream ou) {
     46         super(ou);
     47     }
     48 
     49     public void setExifData(ExifData exifData) {
     50         mExifData = exifData;
     51     }
     52 
     53     public ExifData getExifData() {
     54         return mExifData;
     55     }
     56 
     57     private int requestByteToBuffer(int requestByteCount, byte[] buffer
     58             , int offset, int length) {
     59         int byteNeeded = requestByteCount - mBuffer.position();
     60         int byteToRead = length > byteNeeded ? byteNeeded : length;
     61         mBuffer.put(buffer, offset, byteToRead);
     62         return byteToRead;
     63     }
     64 
     65     @Override
     66     public void write(byte[] buffer, int offset, int length) throws IOException {
     67         while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
     68                 && length > 0) {
     69             if (mByteToSkip > 0) {
     70                 int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
     71                 length -= byteToProcess;
     72                 mByteToSkip -= byteToProcess;
     73                 offset += byteToProcess;
     74             }
     75             if (mByteToCopy > 0) {
     76                 int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
     77                 out.write(buffer, offset, byteToProcess);
     78                 length -= byteToProcess;
     79                 mByteToCopy -= byteToProcess;
     80                 offset += byteToProcess;
     81             }
     82             if (length == 0) return;
     83             switch (mState) {
     84                 case STATE_SOI:
     85                     int byteRead = requestByteToBuffer(2, buffer, offset, length);
     86                     offset += byteRead;
     87                     length -= byteRead;
     88                     if (mBuffer.position() < 2) return;
     89                     mBuffer.rewind();
     90                     assert(mBuffer.getShort() == JpegHeader.SOI);
     91                     out.write(mBuffer.array(), 0 ,2);
     92                     mState = STATE_FRAME_HEADER;
     93                     mBuffer.rewind();
     94                     writeExifData();
     95                     break;
     96                 case STATE_FRAME_HEADER:
     97                     // We ignore the APP1 segment and copy all other segments until SOF tag.
     98                     byteRead = requestByteToBuffer(4, buffer, offset, length);
     99                     offset += byteRead;
    100                     length -= byteRead;
    101                     // Check if this image data doesn't contain SOF.
    102                     if (mBuffer.position() == 2) {
    103                         short tag = mBuffer.getShort();
    104                         if (tag == JpegHeader.EOI) {
    105                             out.write(mBuffer.array(), 0, 2);
    106                             mBuffer.rewind();
    107                         }
    108                     }
    109                     if (mBuffer.position() < 4) return;
    110                     mBuffer.rewind();
    111                     short marker = mBuffer.getShort();
    112                     if (marker == JpegHeader.APP1) {
    113                         mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
    114                         mState = STATE_JPEG_DATA;
    115                     } else if (!JpegHeader.isSofMarker(marker)) {
    116                         out.write(mBuffer.array(), 0, 4);
    117                         mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
    118                     } else {
    119                         out.write(mBuffer.array(), 0, 4);
    120                         mState = STATE_JPEG_DATA;
    121                     }
    122                     mBuffer.rewind();
    123             }
    124         }
    125         if (length > 0) {
    126             out.write(buffer, offset, length);
    127         }
    128     }
    129 
    130     @Override
    131     public void write(int oneByte) throws IOException {
    132         byte[] buf = new byte[] {(byte) (0xff & oneByte)};
    133         write(buf);
    134     }
    135 
    136     @Override
    137     public void write(byte[] buffer) throws IOException {
    138         write(buffer, 0, buffer.length);
    139     }
    140 
    141     private void writeExifData() throws IOException {
    142         createRequiredIfdAndTag();
    143         int exifSize = calculateAllOffset();
    144         OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
    145         dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
    146         dataOutputStream.writeShort(JpegHeader.APP1);
    147         dataOutputStream.writeShort((short) (exifSize + 8));
    148         dataOutputStream.writeInt(EXIF_HEADER);
    149         dataOutputStream.writeShort((short) 0x0000);
    150         if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
    151             dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
    152         } else {
    153             dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
    154         }
    155         dataOutputStream.setByteOrder(mExifData.getByteOrder());
    156         dataOutputStream.writeShort(TIFF_HEADER);
    157         dataOutputStream.writeInt(8);
    158         writeAllTags(dataOutputStream);
    159         writeThumbnail(dataOutputStream);
    160     }
    161 
    162     private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
    163         if (mExifData.hasCompressedThumbnail()) {
    164             dataOutputStream.write(mExifData.getCompressedThumbnail());
    165         } else if (mExifData.hasUncompressedStrip()) {
    166             for (int i = 0; i < mExifData.getStripCount(); i++) {
    167                 dataOutputStream.write(mExifData.getStrip(i));
    168             }
    169         }
    170     }
    171 
    172     private void writeAllTags(OrderedDataOutputStream dataOutputStream) throws IOException {
    173         writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
    174         writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
    175         IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
    176         if (interoperabilityIfd != null) {
    177             writeIfd(interoperabilityIfd, dataOutputStream);
    178         }
    179         IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
    180         if (gpsIfd != null) {
    181             writeIfd(gpsIfd, dataOutputStream);
    182         }
    183         IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
    184         if (ifd1 != null) {
    185             writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
    186         }
    187     }
    188 
    189     private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
    190             throws IOException {
    191         ExifTag[] tags = ifd.getAllTags();
    192         dataOutputStream.writeShort((short) tags.length);
    193         for (ExifTag tag: tags) {
    194             dataOutputStream.writeShort(tag.getTagId());
    195             dataOutputStream.writeShort(tag.getDataType());
    196             dataOutputStream.writeInt(tag.getComponentCount());
    197             if (tag.getDataSize() > 4) {
    198                 dataOutputStream.writeInt(tag.getOffset());
    199             } else {
    200                 writeTagValue(tag, dataOutputStream);
    201                 for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
    202                     dataOutputStream.write(0);
    203                 }
    204             }
    205         }
    206         dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
    207         for (ExifTag tag: tags) {
    208             if (tag.getDataSize() > 4) {
    209                 writeTagValue(tag, dataOutputStream);
    210             }
    211         }
    212     }
    213 
    214     private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
    215             throws IOException {
    216         switch (tag.getDataType()) {
    217             case ExifTag.TYPE_ASCII:
    218                 dataOutputStream.write(tag.getString().getBytes());
    219                 int remain = tag.getComponentCount() - tag.getString().length();
    220                 for (int i = 0; i < remain; i++) {
    221                     dataOutputStream.write(0);
    222                 }
    223                 break;
    224             case ExifTag.TYPE_LONG:
    225                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
    226                     dataOutputStream.writeInt(tag.getLong(i));
    227                 }
    228                 break;
    229             case ExifTag.TYPE_RATIONAL:
    230             case ExifTag.TYPE_UNSIGNED_RATIONAL:
    231                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
    232                     dataOutputStream.writeRational(tag.getRational(i));
    233                 }
    234                 break;
    235             case ExifTag.TYPE_UNDEFINED:
    236             case ExifTag.TYPE_UNSIGNED_BYTE:
    237                 byte[] buf = new byte[tag.getComponentCount()];
    238                 tag.getBytes(buf);
    239                 dataOutputStream.write(buf);
    240                 break;
    241             case ExifTag.TYPE_UNSIGNED_LONG:
    242                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
    243                     dataOutputStream.writeInt((int) tag.getUnsignedLong(i));
    244                 }
    245                 break;
    246             case ExifTag.TYPE_UNSIGNED_SHORT:
    247                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
    248                     dataOutputStream.writeShort((short) tag.getUnsignedShort(i));
    249                 }
    250                 break;
    251         }
    252     }
    253 
    254     private int calculateOffsetOfIfd(IfdData ifd, int offset) {
    255         offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
    256         ExifTag[] tags = ifd.getAllTags();
    257         for(ExifTag tag: tags) {
    258             if (tag.getDataSize() > 4) {
    259                 tag.setOffset(offset);
    260                 offset += tag.getDataSize();
    261             }
    262         }
    263         return offset;
    264     }
    265 
    266     private void createRequiredIfdAndTag() {
    267         // IFD0 is required for all file
    268         IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
    269         if (ifd0 == null) {
    270             ifd0 = new IfdData(IfdId.TYPE_IFD_0);
    271             mExifData.addIfdData(ifd0);
    272         }
    273         ExifTag exifOffsetTag = new ExifTag(ExifTag.TAG_EXIF_IFD,
    274                 ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
    275         ifd0.setTag(exifOffsetTag);
    276 
    277         // Exif IFD is required for all file.
    278         IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
    279         if (exifIfd == null) {
    280             exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
    281             mExifData.addIfdData(exifIfd);
    282         }
    283 
    284         // GPS IFD
    285         IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
    286         if (gpsIfd != null) {
    287             ExifTag gpsOffsetTag = new ExifTag(ExifTag.TAG_GPS_IFD,
    288                     ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_0);
    289             ifd0.setTag(gpsOffsetTag);
    290         }
    291 
    292         // Interoperability IFD
    293         IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
    294         if (interIfd != null) {
    295             ExifTag interOffsetTag = new ExifTag(ExifTag.TAG_INTEROPERABILITY_IFD,
    296                     ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_EXIF);
    297             exifIfd.setTag(interOffsetTag);
    298         }
    299 
    300         IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
    301 
    302         // thumbnail
    303         if (mExifData.hasCompressedThumbnail()) {
    304             if (ifd1 == null) {
    305                 ifd1 = new IfdData(IfdId.TYPE_IFD_1);
    306                 mExifData.addIfdData(ifd1);
    307             }
    308             ExifTag offsetTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT,
    309                     ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
    310             ifd1.setTag(offsetTag);
    311             ExifTag lengthTag = new ExifTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
    312                     ExifTag.TYPE_UNSIGNED_LONG, 1, IfdId.TYPE_IFD_1);
    313             lengthTag.setValue(mExifData.getCompressedThumbnail().length);
    314             ifd1.setTag(lengthTag);
    315         } else if (mExifData.hasUncompressedStrip()){
    316             if (ifd1 == null) {
    317                 ifd1 = new IfdData(IfdId.TYPE_IFD_1);
    318                 mExifData.addIfdData(ifd1);
    319             }
    320             int stripCount = mExifData.getStripCount();
    321             ExifTag offsetTag = new ExifTag(ExifTag.TAG_STRIP_OFFSETS,
    322                     ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
    323             ExifTag lengthTag = new ExifTag(ExifTag.TAG_STRIP_BYTE_COUNTS,
    324                     ExifTag.TYPE_UNSIGNED_LONG, stripCount, IfdId.TYPE_IFD_1);
    325             long[] lengths = new long[stripCount];
    326             for (int i = 0; i < mExifData.getStripCount(); i++) {
    327                 lengths[i] = mExifData.getStrip(i).length;
    328             }
    329             lengthTag.setValue(lengths);
    330             ifd1.setTag(offsetTag);
    331             ifd1.setTag(lengthTag);
    332         }
    333     }
    334 
    335     private int calculateAllOffset() {
    336         int offset = TIFF_HEADER_SIZE;
    337         IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
    338         offset = calculateOffsetOfIfd(ifd0, offset);
    339         ifd0.getTag(ExifTag.TAG_EXIF_IFD).setValue(offset);
    340 
    341         IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
    342         offset = calculateOffsetOfIfd(exifIfd, offset);
    343 
    344         IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
    345         if (interIfd != null) {
    346             exifIfd.getTag(ExifTag.TAG_INTEROPERABILITY_IFD).setValue(offset);
    347             offset = calculateOffsetOfIfd(interIfd, offset);
    348         }
    349 
    350         IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
    351         if (gpsIfd != null) {
    352             ifd0.getTag(ExifTag.TAG_GPS_IFD).setValue(offset);
    353             offset = calculateOffsetOfIfd(gpsIfd, offset);
    354         }
    355 
    356         IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
    357         if (ifd1 != null) {
    358             ifd0.setOffsetToNextIfd(offset);
    359             offset = calculateOffsetOfIfd(ifd1, offset);
    360         }
    361 
    362         // thumbnail
    363         if (mExifData.hasCompressedThumbnail()) {
    364             ifd1.getTag(ExifTag.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
    365             offset += mExifData.getCompressedThumbnail().length;
    366         } else if (mExifData.hasUncompressedStrip()){
    367             int stripCount = mExifData.getStripCount();
    368             long[] offsets = new long[stripCount];
    369             for (int i = 0; i < mExifData.getStripCount(); i++) {
    370                 offsets[i] = offset;
    371                 offset += mExifData.getStrip(i).length;
    372             }
    373             ifd1.getTag(ExifTag.TAG_STRIP_OFFSETS).setValue(offsets);
    374         }
    375         return offset;
    376     }
    377 }