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
17package com.android.gallery3d.exif;
18
19import android.util.Log;
20
21import java.io.Closeable;
22import java.io.IOException;
23import java.io.InputStream;
24import java.nio.ByteBuffer;
25import java.nio.ByteOrder;
26import java.util.ArrayList;
27import java.util.List;
28
29class 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
42        TagOffset(ExifTag tag, int offset) {
43            mTag = tag;
44            mOffset = offset;
45        }
46    }
47
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
66    protected ByteOrder getByteOrder() {
67        return mTagToModified.getByteOrder();
68    }
69
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
141    private void modify() {
142        mByteBuffer.order(getByteOrder());
143        for (TagOffset tagOffset : mTagOffsets) {
144            writeTagValue(tagOffset.mTag, tagOffset.mOffset);
145        }
146    }
147
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
193    public void modifyTag(ExifTag tag) {
194        mTagToModified.addTag(tag);
195    }
196}
197