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.IOException;
22 import java.io.InputStream;
23 import java.nio.ByteBuffer;
24 import java.nio.ByteOrder;
25 import java.util.ArrayList;
26 import java.util.List;
27 
28 class ExifModifier {
29     public static final Log.Tag TAG = new Log.Tag("ExifModifier");
30     public static final boolean DEBUG = false;
31     private final ByteBuffer mByteBuffer;
32     private final ExifData mTagToModified;
33     private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
34     private final ExifInterface mInterface;
35     private int mOffsetBase;
36 
37     private static class TagOffset {
38         final int mOffset;
39         final ExifTag mTag;
40 
TagOffset(ExifTag tag, int offset)41         TagOffset(ExifTag tag, int offset) {
42             mTag = tag;
43             mOffset = offset;
44         }
45     }
46 
ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef)47     protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
48             ExifInvalidFormatException {
49         mByteBuffer = byteBuffer;
50         mOffsetBase = byteBuffer.position();
51         mInterface = iRef;
52         InputStream is = null;
53         try {
54             is = new ByteBufferInputStream(byteBuffer);
55             // Do not require any IFD;
56             ExifParser parser = ExifParser.parse(is, mInterface);
57             mTagToModified = new ExifData(parser.getByteOrder());
58             mOffsetBase += parser.getTiffStartPosition();
59             mByteBuffer.position(0);
60         } finally {
61             ExifInterface.closeSilently(is);
62         }
63     }
64 
getByteOrder()65     protected ByteOrder getByteOrder() {
66         return mTagToModified.getByteOrder();
67     }
68 
commit()69     protected boolean commit() throws IOException, ExifInvalidFormatException {
70         InputStream is = null;
71         try {
72             is = new ByteBufferInputStream(mByteBuffer);
73             int flag = 0;
74             IfdData[] ifdDatas = new IfdData[] {
75                     mTagToModified.getIfdData(IfdId.TYPE_IFD_0),
76                     mTagToModified.getIfdData(IfdId.TYPE_IFD_1),
77                     mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF),
78                     mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY),
79                     mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS)
80             };
81 
82             if (ifdDatas[IfdId.TYPE_IFD_0] != null) {
83                 flag |= ExifParser.OPTION_IFD_0;
84             }
85             if (ifdDatas[IfdId.TYPE_IFD_1] != null) {
86                 flag |= ExifParser.OPTION_IFD_1;
87             }
88             if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) {
89                 flag |= ExifParser.OPTION_IFD_EXIF;
90             }
91             if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) {
92                 flag |= ExifParser.OPTION_IFD_GPS;
93             }
94             if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) {
95                 flag |= ExifParser.OPTION_IFD_INTEROPERABILITY;
96             }
97 
98             ExifParser parser = ExifParser.parse(is, flag, mInterface);
99             int event = parser.next();
100             IfdData currIfd = null;
101             while (event != ExifParser.EVENT_END) {
102                 switch (event) {
103                     case ExifParser.EVENT_START_OF_IFD:
104                         currIfd = ifdDatas[parser.getCurrentIfd()];
105                         if (currIfd == null) {
106                             parser.skipRemainingTagsInCurrentIfd();
107                         }
108                         break;
109                     case ExifParser.EVENT_NEW_TAG:
110                         ExifTag oldTag = parser.getTag();
111                         ExifTag newTag = currIfd.getTag(oldTag.getTagId());
112                         if (newTag != null) {
113                             if (newTag.getComponentCount() != oldTag.getComponentCount()
114                                     || newTag.getDataType() != oldTag.getDataType()) {
115                                 return false;
116                             } else {
117                                 mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset()));
118                                 currIfd.removeTag(oldTag.getTagId());
119                                 if (currIfd.getTagCount() == 0) {
120                                     parser.skipRemainingTagsInCurrentIfd();
121                                 }
122                             }
123                         }
124                         break;
125                 }
126                 event = parser.next();
127             }
128             for (IfdData ifd : ifdDatas) {
129                 if (ifd != null && ifd.getTagCount() > 0) {
130                     return false;
131                 }
132             }
133             modify();
134         } finally {
135             ExifInterface.closeSilently(is);
136         }
137         return true;
138     }
139 
modify()140     private void modify() {
141         mByteBuffer.order(getByteOrder());
142         for (TagOffset tagOffset : mTagOffsets) {
143             writeTagValue(tagOffset.mTag, tagOffset.mOffset);
144         }
145     }
146 
writeTagValue(ExifTag tag, int offset)147     private void writeTagValue(ExifTag tag, int offset) {
148         if (DEBUG) {
149             Log.v(TAG, "modifying tag to: \n" + tag.toString());
150             Log.v(TAG, "at offset: " + offset);
151         }
152         mByteBuffer.position(offset + mOffsetBase);
153         switch (tag.getDataType()) {
154             case ExifTag.TYPE_ASCII:
155                 byte buf[] = tag.getStringByte();
156                 if (buf.length == tag.getComponentCount()) {
157                     buf[buf.length - 1] = 0;
158                     mByteBuffer.put(buf);
159                 } else {
160                     mByteBuffer.put(buf);
161                     mByteBuffer.put((byte) 0);
162                 }
163                 break;
164             case ExifTag.TYPE_LONG:
165             case ExifTag.TYPE_UNSIGNED_LONG:
166                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
167                     mByteBuffer.putInt((int) tag.getValueAt(i));
168                 }
169                 break;
170             case ExifTag.TYPE_RATIONAL:
171             case ExifTag.TYPE_UNSIGNED_RATIONAL:
172                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
173                     Rational v = tag.getRational(i);
174                     mByteBuffer.putInt((int) v.getNumerator());
175                     mByteBuffer.putInt((int) v.getDenominator());
176                 }
177                 break;
178             case ExifTag.TYPE_UNDEFINED:
179             case ExifTag.TYPE_UNSIGNED_BYTE:
180                 buf = new byte[tag.getComponentCount()];
181                 tag.getBytes(buf);
182                 mByteBuffer.put(buf);
183                 break;
184             case ExifTag.TYPE_UNSIGNED_SHORT:
185                 for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
186                     mByteBuffer.putShort((short) tag.getValueAt(i));
187                 }
188                 break;
189         }
190     }
191 
modifyTag(ExifTag tag)192     public void modifyTag(ExifTag tag) {
193         mTagToModified.addTag(tag);
194     }
195 }
196