summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEarl Ou <shunhsingou@google.com>2012-08-28 18:39:49 +0800
committerEarl Ou <shunhsingou@google.com>2012-08-30 14:50:19 +0800
commit4729f2992f820d5c03011253cd5f528bf0b40129 (patch)
treecdcfa9ea9dce8ab49c4ac2a618ab95ef7fb12179 /src
parent1d3dfbf55bb320dc4d1d4379c385b629c107f713 (diff)
downloadandroid_packages_apps_Snap-4729f2992f820d5c03011253cd5f528bf0b40129.tar.gz
android_packages_apps_Snap-4729f2992f820d5c03011253cd5f528bf0b40129.tar.bz2
android_packages_apps_Snap-4729f2992f820d5c03011253cd5f528bf0b40129.zip
ExifOutputStream
Change-Id: I9f2de77e87a502ccdadba0b18658621028c538c9
Diffstat (limited to 'src')
-rw-r--r--src/com/android/gallery3d/exif/ExifData.java7
-rw-r--r--src/com/android/gallery3d/exif/ExifOutputStream.java370
-rw-r--r--src/com/android/gallery3d/exif/OrderedDataOutputStream.java52
-rw-r--r--src/com/android/gallery3d/exif/Util.java11
4 files changed, 440 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/exif/ExifData.java b/src/com/android/gallery3d/exif/ExifData.java
index eca819ea0..776f77a65 100644
--- a/src/com/android/gallery3d/exif/ExifData.java
+++ b/src/com/android/gallery3d/exif/ExifData.java
@@ -116,6 +116,13 @@ public class ExifData {
return mByteOrder;
}
+ /**
+ * Returns true if this header contains compressed strip of thumbnail.
+ */
+ public boolean hasUncompressedStrip() {
+ return mStripBytes.size() != 0;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof ExifData) {
diff --git a/src/com/android/gallery3d/exif/ExifOutputStream.java b/src/com/android/gallery3d/exif/ExifOutputStream.java
new file mode 100644
index 000000000..1c0baf251
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifOutputStream.java
@@ -0,0 +1,370 @@
+/*
+ * 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.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class ExifOutputStream extends FilterOutputStream {
+ private static final String TAG = "ExifOutputStream";
+
+ private static final int STATE_SOI = 0;
+ private static final int STATE_APP1 = 1;
+ private static final int STATE_JPEG_DATA = 2;
+
+ private static final short SOI = (short) 0xFFD8;
+ private static final short APP0 = (short) 0xFFE0;
+ private static final short APP1 = (short) 0xFFE1;
+ private static final int EXIF_HEADER = 0x45786966;
+ private static final short TIFF_HEADER = 0x002A;
+ private static final short TIFF_BIG_ENDIAN = 0x4d4d;
+ private static final short TIFF_LITTLE_ENDIAN = 0x4949;
+ private static final short TAG_SIZE = 12;
+ private static final short TIFF_HEADER_SIZE = 8;
+
+ private ExifData mExifData;
+ private int mState;
+ private int mByteToSkip;
+ private int mByteToCopy;
+ private ByteBuffer mBuffer = ByteBuffer.allocate(4);
+
+ public ExifOutputStream(OutputStream ou) {
+ super(ou);
+ }
+
+ public void setExifData(ExifData exifData) {
+ mExifData = exifData;
+ }
+
+ public ExifData getExifData() {
+ return mExifData;
+ }
+
+ private int requestByteToBuffer(int requestByteCount, byte[] buffer
+ , int offset, int length) {
+ int byteNeeded = requestByteCount - mBuffer.position();
+ int byteToRead = length > byteNeeded ? byteNeeded : length;
+ mBuffer.put(buffer, offset, byteToRead);
+ return byteToRead;
+ }
+
+ @Override
+ public void write(byte[] buffer, int offset, int length) throws IOException {
+ while((mByteToSkip > 0 || mByteToCopy > 0 || mState != STATE_JPEG_DATA)
+ && length > 0) {
+ if (mByteToSkip > 0) {
+ int byteToProcess = length > mByteToSkip ? mByteToSkip : length;
+ length -= byteToProcess;
+ mByteToSkip -= byteToProcess;
+ offset += byteToProcess;
+ }
+ if (mByteToCopy > 0) {
+ int byteToProcess = length > mByteToCopy ? mByteToCopy : length;
+ out.write(buffer, offset, byteToProcess);
+ length -= byteToProcess;
+ mByteToCopy -= byteToProcess;
+ offset += byteToProcess;
+ }
+ switch (mState) {
+ case STATE_SOI:
+ int byteRead = requestByteToBuffer(2, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 2) return;
+ mBuffer.rewind();
+ assert(mBuffer.getShort() == SOI);
+ out.write(mBuffer.array(), 0 ,2);
+ mState = STATE_APP1;
+ mBuffer.rewind();
+ break;
+ case STATE_APP1:
+ byteRead = requestByteToBuffer(4, buffer, offset, length);
+ offset += byteRead;
+ length -= byteRead;
+ if (mBuffer.position() < 4) return;
+ mBuffer.rewind();
+ if (mBuffer.getShort() == APP0) {
+ out.write(mBuffer.array(), 0 ,4);
+ mByteToCopy = (mBuffer.getShort() & 0xff) - 2;
+ } else if (mBuffer.getShort() == APP1) {
+ writeExifData();
+ mByteToSkip = (mBuffer.getShort() & 0xff) - 2;
+ mState = STATE_JPEG_DATA;
+ } else {
+ writeExifData();
+ out.write(mBuffer.array(), 0, 4);
+ mState = STATE_JPEG_DATA;
+ }
+ mBuffer.rewind();
+ break;
+ }
+ }
+ if (length > 0) {
+ out.write(buffer, offset, length);
+ }
+ }
+
+ @Override
+ public void write(int oneByte) throws IOException {
+ byte[] buf = new byte[] {(byte) (0xff & oneByte)};
+ write(buf);
+ }
+
+ @Override
+ public void write(byte[] buffer) throws IOException {
+ write(buffer, 0, buffer.length);
+ }
+
+ private void writeExifData() throws IOException {
+ createRequiredIfdAndTag();
+ int exifSize = calculateAllOffset();
+ OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
+ dataOutputStream.writeShort(APP1);
+ dataOutputStream.writeShort((short) (exifSize + 8));
+ dataOutputStream.writeInt(EXIF_HEADER);
+ dataOutputStream.writeShort((short) 0x0000);
+ if (mExifData.getByteOrder() == ByteOrder.BIG_ENDIAN) {
+ dataOutputStream.writeShort(TIFF_BIG_ENDIAN);
+ } else {
+ dataOutputStream.writeShort(TIFF_LITTLE_ENDIAN);
+ }
+ dataOutputStream.setByteOrder(mExifData.getByteOrder());
+ dataOutputStream.writeShort(TIFF_HEADER);
+ dataOutputStream.writeInt(8);
+ writeAllTag(dataOutputStream);
+ writeThumbnail(dataOutputStream);
+ }
+
+ private void writeThumbnail(OrderedDataOutputStream dataOutputStream) throws IOException {
+ if (mExifData.hasCompressedThumbnail()) {
+ dataOutputStream.write(mExifData.getCompressedThumbnail());
+ } else if (mExifData.hasUncompressedStrip()) {
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ dataOutputStream.write(mExifData.getStrip(i));
+ }
+ }
+ }
+
+ private void writeAllTag(OrderedDataOutputStream dataOutputStream) throws IOException {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_0), dataOutputStream);
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_EXIF), dataOutputStream);
+ IfdData interoperabilityIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interoperabilityIfd != null) {
+ writeIfd(interoperabilityIfd, dataOutputStream);
+ }
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ writeIfd(gpsIfd, dataOutputStream);
+ }
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ writeIfd(mExifData.getIfdData(IfdId.TYPE_IFD_1), dataOutputStream);
+ }
+ }
+
+ private void writeIfd(IfdData ifd, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ ExifTag[] tags = ifd.getAllTags(new ExifTag[] {});
+ dataOutputStream.writeShort((short) tags.length);
+ for (ExifTag tag: tags) {
+ dataOutputStream.writeShort(tag.getTagId());
+ dataOutputStream.writeShort(tag.getDataType());
+ dataOutputStream.writeInt(tag.getComponentCount());
+ if (tag.getDataSize() > 4) {
+ dataOutputStream.writeInt(tag.getOffset());
+ } else {
+ writeTagValue(tag, dataOutputStream);
+ for (int i = 0; i < 4 - tag.getDataSize(); i++) {
+ dataOutputStream.write(0);
+ }
+ }
+ }
+ dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
+ for (ExifTag tag: tags) {
+ if (tag.getDataSize() > 4) {
+ writeTagValue(tag, dataOutputStream);
+ }
+ }
+ }
+
+ private void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
+ throws IOException {
+ switch (tag.getDataType()) {
+ case ExifTag.TYPE_ASCII:
+ dataOutputStream.write(tag.getString().getBytes());
+ int remain = tag.getComponentCount() - tag.getString().getBytes().length;
+ for (int i = 0; i < remain; i++) {
+ dataOutputStream.write(0);
+ }
+ break;
+ case ExifTag.TYPE_INT:
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ dataOutputStream.writeInt(tag.getInt(i));
+ }
+ break;
+ case ExifTag.TYPE_RATIONAL:
+ case ExifTag.TYPE_UNSIGNED_RATIONAL:
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ dataOutputStream.writeRational(tag.getRational(i));
+ }
+ break;
+ case ExifTag.TYPE_UNDEFINED:
+ case ExifTag.TYPE_UNSIGNED_BYTE:
+ byte[] buf = new byte[tag.getComponentCount()];
+ tag.getBytes(buf);
+ dataOutputStream.write(buf);
+ break;
+ case ExifTag.TYPE_UNSIGNED_INT:
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ dataOutputStream.writeInt((int) tag.getUnsignedInt(i));
+ }
+ break;
+ case ExifTag.TYPE_UNSIGNED_SHORT:
+ for (int i = 0; i < tag.getComponentCount(); i++) {
+ dataOutputStream.writeShort((short) tag.getUnsignedShort(i));
+ }
+ break;
+ }
+ }
+
+ private int calculateOffsetOfIfd(IfdData ifd, int offset) {
+ offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
+ ExifTag[] tags = ifd.getAllTags(new ExifTag[] {});
+ for(ExifTag tag: tags) {
+ if (tag.getDataSize() > 4) {
+ tag.setOffset(offset);
+ offset += tag.getDataSize();
+ }
+ }
+ return offset;
+ }
+
+ private void createRequiredIfdAndTag() {
+ // IFD0 is required for all file
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ if (ifd0 == null) {
+ ifd0 = new IfdData(IfdId.TYPE_IFD_0);
+ mExifData.addIfdData(ifd0);
+ }
+ ExifTag exifOffsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_EXIF_IFD
+ , ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_0);
+ ifd0.setTag(exifOffsetTag);
+
+ // Exif IFD is required for all file.
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ if (exifIfd == null) {
+ exifIfd = new IfdData(IfdId.TYPE_IFD_EXIF);
+ mExifData.addIfdData(exifIfd);
+ }
+
+ // GPS IFD
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ExifTag gpsOffsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_GPS_IFD,
+ ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_0);
+ ifd0.setTag(gpsOffsetTag);
+ }
+
+ // Interoperability IFD
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ ExifTag interOffsetTag = new ExifTag(ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD,
+ ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_EXIF);
+ exifIfd.setTag(interOffsetTag);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+ ExifTag offsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT,
+ ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_1);
+ ifd1.setTag(offsetTag);
+ ExifTag lengthTag = new ExifTag(ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH,
+ ExifTag.TYPE_UNSIGNED_INT, 1, IfdId.TYPE_IFD_1);
+ lengthTag.setValue(mExifData.getCompressedThumbnail().length);
+ ifd1.setTag(lengthTag);
+ } else if (mExifData.hasUncompressedStrip()){
+ if (ifd1 == null) {
+ ifd1 = new IfdData(IfdId.TYPE_IFD_1);
+ mExifData.addIfdData(ifd1);
+ }
+ int stripCount = mExifData.getStripCount();
+ ExifTag offsetTag = new ExifTag(ExifTag.TIFF_TAG.TAG_STRIP_OFFSETS,
+ ExifTag.TYPE_UNSIGNED_INT, stripCount, IfdId.TYPE_IFD_1);
+ ExifTag lengthTag = new ExifTag(ExifTag.TIFF_TAG.TAG_STRIP_BYTE_COUNTS,
+ ExifTag.TYPE_UNSIGNED_INT, stripCount, IfdId.TYPE_IFD_1);
+ long[] lengths = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ lengths[i] = mExifData.getStrip(i).length;
+ }
+ lengthTag.setValue(lengths);
+ ifd1.setTag(offsetTag);
+ ifd1.setTag(lengthTag);
+ }
+ }
+
+ private int calculateAllOffset() {
+ int offset = TIFF_HEADER_SIZE;
+ IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
+ offset = calculateOffsetOfIfd(ifd0, offset);
+ ifd0.getTag(ExifTag.TIFF_TAG.TAG_EXIF_IFD).setValue(offset);
+
+ IfdData exifIfd = mExifData.getIfdData(IfdId.TYPE_IFD_EXIF);
+ offset = calculateOffsetOfIfd(exifIfd, offset);
+
+ IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
+ if (interIfd != null) {
+ exifIfd.getTag(ExifTag.EXIF_TAG.TAG_INTEROPERABILITY_IFD).setValue(offset);
+ offset = calculateOffsetOfIfd(interIfd, offset);
+ }
+
+ IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
+ if (gpsIfd != null) {
+ ifd0.getTag(ExifTag.TIFF_TAG.TAG_GPS_IFD).setValue(offset);
+ offset = calculateOffsetOfIfd(gpsIfd, offset);
+ }
+
+ IfdData ifd1 = mExifData.getIfdData(IfdId.TYPE_IFD_1);
+ if (ifd1 != null) {
+ ifd0.setOffsetToNextIfd(offset);
+ offset = calculateOffsetOfIfd(ifd1, offset);
+ }
+
+ // thumbnail
+ if (mExifData.hasCompressedThumbnail()) {
+ ifd1.getTag(ExifTag.TIFF_TAG.TAG_JPEG_INTERCHANGE_FORMAT).setValue(offset);
+ offset += mExifData.getCompressedThumbnail().length;
+ } else if (mExifData.hasUncompressedStrip()){
+ int stripCount = mExifData.getStripCount();
+ long[] offsets = new long[stripCount];
+ for (int i = 0; i < mExifData.getStripCount(); i++) {
+ offsets[i] = offset;
+ offset += mExifData.getStrip(i).length;
+ }
+ ifd1.getTag(ExifTag.TIFF_TAG.TAG_STRIP_OFFSETS).setValue(offsets);
+ }
+ return offset;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/OrderedDataOutputStream.java b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
new file mode 100644
index 000000000..9244be329
--- /dev/null
+++ b/src/com/android/gallery3d/exif/OrderedDataOutputStream.java
@@ -0,0 +1,52 @@
+/*
+ * 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.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+public class OrderedDataOutputStream extends FilterOutputStream {
+ private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
+
+ public OrderedDataOutputStream(OutputStream out) {
+ super(out);
+ }
+
+ public void setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ }
+
+ public void writeShort(short value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putShort(value);
+ out.write(mByteBuffer.array(), 0, 2);
+ }
+
+ public void writeInt(int value) throws IOException {
+ mByteBuffer.rewind();
+ mByteBuffer.putInt(value);
+ out.write(mByteBuffer.array());
+ }
+
+ public void writeRational(Rational rational) throws IOException {
+ writeInt((int) rational.getNominator());
+ writeInt((int) rational.getDenominator());
+ }
+}
diff --git a/src/com/android/gallery3d/exif/Util.java b/src/com/android/gallery3d/exif/Util.java
index c7d9ce842..594d6fc7f 100644
--- a/src/com/android/gallery3d/exif/Util.java
+++ b/src/com/android/gallery3d/exif/Util.java
@@ -16,8 +16,19 @@
package com.android.gallery3d.exif;
+import java.io.Closeable;
+
class Util {
public static boolean equals(Object a, Object b) {
return (a == b) || (a == null ? false : a.equals(b));
}
+
+ public static void closeSilently(Closeable c) {
+ if (c == null) return;
+ try {
+ c.close();
+ } catch (Throwable t) {
+ // do nothing
+ }
+ }
}