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