From cf6255422bf2751d160c1384895ddae3eeea66b4 Mon Sep 17 00:00:00 2001 From: Earl Ou Date: Wed, 28 Nov 2012 13:58:30 +0800 Subject: ExifModifier Change-Id: I001b24d8ba004a4080cd898f0d26d706b1839425 --- .../gallery3d/exif/ByteBufferInputStream.java | 48 ++++++ .../src/com/android/gallery3d/exif/ExifData.java | 8 + .../com/android/gallery3d/exif/ExifModifier.java | 186 +++++++++++++++++++++ .../src/com/android/gallery3d/exif/ExifParser.java | 101 ++++++----- .../src/com/android/gallery3d/exif/ExifTag.java | 6 +- .../src/com/android/gallery3d/exif/IfdData.java | 7 + 6 files changed, 308 insertions(+), 48 deletions(-) create mode 100644 gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java create mode 100644 gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java (limited to 'gallerycommon/src/com/android/gallery3d') diff --git a/gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java b/gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java new file mode 100644 index 000000000..7fb9f22cc --- /dev/null +++ b/gallerycommon/src/com/android/gallery3d/exif/ByteBufferInputStream.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.exif; + +import java.io.InputStream; +import java.nio.ByteBuffer; + +class ByteBufferInputStream extends InputStream { + + private ByteBuffer mBuf; + + public ByteBufferInputStream(ByteBuffer buf) { + mBuf = buf; + } + + @Override + public int read() { + if (!mBuf.hasRemaining()) { + return -1; + } + return mBuf.get() & 0xFF; + } + + @Override + public int read(byte[] bytes, int off, int len) { + if (!mBuf.hasRemaining()) { + return -1; + } + + len = Math.min(len, mBuf.remaining()); + mBuf.get(bytes, off, len); + return len; + } +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java index 6e5c227d5..b226dd4fe 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifData.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifData.java @@ -286,6 +286,14 @@ public class ExifData { return tag; } + /** + * Adds the given ExifTag to its corresponding IFD. + */ + public void addTag(ExifTag tag) { + IfdData ifdData = getOrCreateIfdData(tag.getIfd()); + ifdData.setTag(tag); + } + /** * Adds a thumbnail-related tag with the given tag ID. If the tag of the given ID * already exists, the original tag will be returned. Otherwise, a new ExifTag will diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java new file mode 100644 index 000000000..da31a29db --- /dev/null +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifModifier.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.gallery3d.exif; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; +import java.util.List; + +public class ExifModifier { + private final ByteBuffer mByteBuffer; + private final ExifData mTagToModified; + private final List mTagOffsets = new ArrayList(); + private int mOffsetBase; + + private static class TagOffset { + final int mOffset; + final ExifTag mTag; + + TagOffset(ExifTag tag, int offset) { + mTag = tag; + mOffset = offset; + } + + public ExifTag getTag() { + return mTag; + } + } + + public ExifModifier(ByteBuffer byteBuffer) throws IOException, ExifInvalidFormatException { + mByteBuffer = byteBuffer; + mOffsetBase = byteBuffer.position(); + InputStream is = null; + try { + is = new ByteBufferInputStream(byteBuffer); + // Do not require any IFD; + ExifParser parser = ExifParser.parse(is, 0); + mTagToModified = new ExifData(parser.getByteOrder()); + mOffsetBase += parser.getTiffStartPosition(); + mByteBuffer.position(0); + } finally { + closeSilently(is); + } + } + + public ByteOrder getByteOrder() { + return mTagToModified.getByteOrder(); + } + + public boolean commit() throws IOException, ExifInvalidFormatException { + InputStream is = null; + try { + is = new ByteBufferInputStream(mByteBuffer); + int flag = 0; + IfdData[] ifdDatas = new IfdData[] { + mTagToModified.getIfdData(IfdId.TYPE_IFD_0), + mTagToModified.getIfdData(IfdId.TYPE_IFD_1), + mTagToModified.getIfdData(IfdId.TYPE_IFD_EXIF), + mTagToModified.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY), + mTagToModified.getIfdData(IfdId.TYPE_IFD_GPS) + }; + + if (ifdDatas[IfdId.TYPE_IFD_0] != null) flag |= ExifParser.OPTION_IFD_0; + if (ifdDatas[IfdId.TYPE_IFD_1] != null) flag |= ExifParser.OPTION_IFD_1; + if (ifdDatas[IfdId.TYPE_IFD_EXIF] != null) flag |= ExifParser.OPTION_IFD_EXIF; + if (ifdDatas[IfdId.TYPE_IFD_GPS] != null) flag |= ExifParser.OPTION_IFD_GPS; + if (ifdDatas[IfdId.TYPE_IFD_INTEROPERABILITY] != null) { + flag |= ExifParser.OPTION_IFD_INTEROPERABILITY; + } + + ExifParser parser = ExifParser.parse(is, flag); + int event = parser.next(); + IfdData currIfd = null; + while (event != ExifParser.EVENT_END) { + switch (event) { + case ExifParser.EVENT_START_OF_IFD: + currIfd = ifdDatas[parser.getCurrentIfd()]; + if (currIfd == null) parser.skipRemainingTagsInCurrentIfd(); + break; + case ExifParser.EVENT_NEW_TAG: + ExifTag oldTag = parser.getTag(); + ExifTag newTag = currIfd.getTag(oldTag.getTagId()); + if (newTag != null) { + if (newTag.getComponentCount() != oldTag.getComponentCount() + || newTag.getDataType() != oldTag.getDataType()) { + return false; + } else { + mTagOffsets.add(new TagOffset(newTag, oldTag.getOffset())); + currIfd.removeTag(oldTag.getTagId()); + if (currIfd.getTagCount() == 0) { + parser.skipRemainingTagsInCurrentIfd(); + } + } + } + break; + } + event = parser.next(); + } + for (IfdData ifd: ifdDatas) { + if (ifd != null && ifd.getTagCount() > 0) return false; + } + modify(); + } finally { + closeSilently(is); + } + return true; + } + + private void modify() { + mByteBuffer.order(getByteOrder()); + for (TagOffset tagOffset: mTagOffsets) { + writeTagValue(tagOffset.mTag, tagOffset.mOffset); + } + } + + private void writeTagValue(ExifTag tag, int offset) { + mByteBuffer.position(offset + mOffsetBase); + switch (tag.getDataType()) { + case ExifTag.TYPE_ASCII: + byte buf[] = tag.getStringByte(); + if (buf.length == tag.getComponentCount()) { + buf[buf.length - 1] = 0; + mByteBuffer.put(buf); + } else { + mByteBuffer.put(buf); + mByteBuffer.put((byte) 0); + } + break; + case ExifTag.TYPE_LONG: + case ExifTag.TYPE_UNSIGNED_LONG: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + mByteBuffer.putInt((int) tag.getValueAt(i)); + } + break; + case ExifTag.TYPE_RATIONAL: + case ExifTag.TYPE_UNSIGNED_RATIONAL: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + Rational v = tag.getRational(i); + mByteBuffer.putInt((int) v.getNominator()); + mByteBuffer.putInt((int) v.getDenominator()); + } + break; + case ExifTag.TYPE_UNDEFINED: + case ExifTag.TYPE_UNSIGNED_BYTE: + buf = new byte[tag.getComponentCount()]; + tag.getBytes(buf); + mByteBuffer.put(buf); + break; + case ExifTag.TYPE_UNSIGNED_SHORT: + for (int i = 0, n = tag.getComponentCount(); i < n; i++) { + mByteBuffer.putShort((short) tag.getValueAt(i)); + } + break; + } + } + + public void modifyTag(ExifTag tag) { + mTagToModified.addTag(tag); + } + + private static void closeSilently(Closeable c) { + if (c == null) return; + try { + c.close(); + } catch (Throwable t) { + // do nothing + } + } +} diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java index 2cff12a3d..a1ce13e69 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifParser.java @@ -18,7 +18,6 @@ package com.android.gallery3d.exif; import android.util.Log; -import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteOrder; @@ -154,6 +153,7 @@ public class ExifParser { private int mApp1End; private byte[] mDataAboveIfd0; private int mIfd0Position; + private int mTiffStartPosition; private final TreeMap mCorrespondingEvent = new TreeMap(); @@ -190,10 +190,13 @@ public class ExifParser { throw new ExifInvalidFormatException("Invalid offset " + offset); } mIfd0Position = (int) offset; - registerIfd(IfdId.TYPE_IFD_0, offset); - if (offset != DEFAULT_IFD0_OFFSET) { - mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; - read(mDataAboveIfd0); + mIfdType = IfdId.TYPE_IFD_0; + if (isIfdRequested(IfdId.TYPE_IFD_0) || needToParseOffsetsInCurrentIfd()) { + registerIfd(IfdId.TYPE_IFD_0, offset); + if (offset != DEFAULT_IFD0_OFFSET) { + mDataAboveIfd0 = new byte[(int) offset - DEFAULT_IFD0_OFFSET]; + read(mDataAboveIfd0); + } } } @@ -203,8 +206,8 @@ public class ExifParser { * @exception ExifInvalidFormatException */ public static ExifParser parse(InputStream inputStream, int options) - throws IOException, ExifInvalidFormatException { - return new ExifParser(inputStream, options); + throws IOException, ExifInvalidFormatException { + return new ExifParser(inputStream, options); } /** @@ -353,7 +356,8 @@ public class ExifParser { switch (mIfdType) { case IfdId.TYPE_IFD_0: return isIfdRequested(IfdId.TYPE_IFD_EXIF) || isIfdRequested(IfdId.TYPE_IFD_GPS) - || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY); + || isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY) + || isIfdRequested(IfdId.TYPE_IFD_1); case IfdId.TYPE_IFD_1: return isThumbnailRequested(); case IfdId.TYPE_IFD_EXIF: @@ -515,6 +519,8 @@ public class ExifParser { } else { readFullTagValue(tag); mTiffStream.skip(4 - dataSize); + // Set the offset to the position of value. + tag.setOffset(mTiffStream.getReadByteCount() - 4); } return tag; } @@ -617,42 +623,42 @@ public class ExifParser { tag.setValue(value); } break; - case ExifTag.TYPE_UNSIGNED_RATIONAL: - { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedRational(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_UNSIGNED_SHORT: - { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readUnsignedShort(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_LONG: - { - int value[] = new int[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readLong(); - } - tag.setValue(value); - } - break; - case ExifTag.TYPE_RATIONAL: - { - Rational value[] = new Rational[tag.getComponentCount()]; - for (int i = 0, n = value.length; i < n; i++) { - value[i] = readRational(); - } - tag.setValue(value); - } - break; + case ExifTag.TYPE_UNSIGNED_RATIONAL: + { + Rational value[] = new Rational[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedRational(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_UNSIGNED_SHORT: + { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readUnsignedShort(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_LONG: + { + int value[] = new int[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readLong(); + } + tag.setValue(value); + } + break; + case ExifTag.TYPE_RATIONAL: + { + Rational value[] = new Rational[tag.getComponentCount()]; + for (int i = 0, n = value.length; i < n; i++) { + value[i] = readRational(); + } + tag.setValue(value); + } + break; } } @@ -675,7 +681,7 @@ public class ExifParser { private boolean seekTiffData(InputStream inputStream) throws IOException, ExifInvalidFormatException { - DataInputStream dataStream = new DataInputStream(inputStream); + CountedDataInputStream dataStream = new CountedDataInputStream(inputStream); if (dataStream.readShort() != JpegHeader.SOI) { throw new ExifInvalidFormatException("Invalid JPEG format"); @@ -695,6 +701,7 @@ public class ExifParser { headerTail = dataStream.readShort(); length -= 6; if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) { + mTiffStartPosition = dataStream.getReadByteCount(); mApp1End = length; return true; } @@ -709,6 +716,10 @@ public class ExifParser { return false; } + int getTiffStartPosition() { + return mTiffStartPosition; + } + /** * Reads bytes from the InputStream. */ diff --git a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java index cda67c2e2..753b18ce8 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java +++ b/gallerycommon/src/com/android/gallery3d/exif/ExifTag.java @@ -915,9 +915,9 @@ public class ExifTag { static boolean isValidType(short type) { return type == TYPE_UNSIGNED_BYTE || type == TYPE_ASCII || - type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG || - type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED || - type == TYPE_LONG || type == TYPE_RATIONAL; + type == TYPE_UNSIGNED_SHORT || type == TYPE_UNSIGNED_LONG || + type == TYPE_UNSIGNED_RATIONAL || type == TYPE_UNDEFINED || + type == TYPE_LONG || type == TYPE_RATIONAL; } ExifTag(short tagId, short type, int componentCount, int ifd) { diff --git a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java index 78f9173cc..633604940 100644 --- a/gallerycommon/src/com/android/gallery3d/exif/IfdData.java +++ b/gallerycommon/src/com/android/gallery3d/exif/IfdData.java @@ -78,6 +78,13 @@ class IfdData { mExifTags.put(tag.getTagId(), tag); } + /** + * Removes the tag of the given ID + */ + public void removeTag(short tagId) { + mExifTags.remove(tagId); + } + /** * Gets the tags count in the IFD. */ -- cgit v1.2.3