summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorEarl Ou <shunhsingou@google.com>2012-07-29 20:59:17 -0700
committerAndroid (Google) Code Review <android-gerrit@google.com>2012-07-29 20:59:17 -0700
commit9636c66cb89e7243de92eb46ae90d6223b3d79f5 (patch)
tree0812bdd344000fe42dfb7af51196e14bab702356 /src
parentdbc472eb08c19e78811823f8b83e59c3aeacc6d0 (diff)
parentea8759d2cf72a292a97264955bc895984d1ec257 (diff)
downloadandroid_packages_apps_Snap-9636c66cb89e7243de92eb46ae90d6223b3d79f5.tar.gz
android_packages_apps_Snap-9636c66cb89e7243de92eb46ae90d6223b3d79f5.tar.bz2
android_packages_apps_Snap-9636c66cb89e7243de92eb46ae90d6223b3d79f5.zip
Merge "Low-level exif parser" into gb-ub-photos-arches
Diffstat (limited to 'src')
-rw-r--r--src/com/android/gallery3d/exif/ExifInvalidFormatException.java23
-rw-r--r--src/com/android/gallery3d/exif/ExifParser.java98
-rw-r--r--src/com/android/gallery3d/exif/ExifTag.java80
-rw-r--r--src/com/android/gallery3d/exif/IfdParser.java181
-rw-r--r--src/com/android/gallery3d/exif/Rational.java36
-rw-r--r--src/com/android/gallery3d/exif/TiffInputStream.java134
6 files changed, 552 insertions, 0 deletions
diff --git a/src/com/android/gallery3d/exif/ExifInvalidFormatException.java b/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
new file mode 100644
index 000000000..bf923ec26
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifInvalidFormatException.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public class ExifInvalidFormatException extends Exception {
+ public ExifInvalidFormatException(String meg) {
+ super(meg);
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifParser.java b/src/com/android/gallery3d/exif/ExifParser.java
new file mode 100644
index 000000000..534f2f6d9
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifParser.java
@@ -0,0 +1,98 @@
+/*
+ * 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.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteOrder;
+
+public class ExifParser {
+
+ private static final String TAG = "ExifParser";
+
+ private static final short SOI = (short) 0xFFD8; // SOI marker of JPEG
+ private static final short APP1 = (short) 0xFFE1; // APP1 marker of JPEG
+
+ private static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
+ private static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
+
+ // TIFF header
+ private static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
+ private static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
+ private static final short TIFF_HEADER_TAIL = 0x002A;
+
+ public IfdParser parse(InputStream inputStream) throws ExifInvalidFormatException, IOException{
+ if (!seekTiffData(inputStream)) {
+ return null;
+ }
+ TiffInputStream tiffStream = new TiffInputStream(inputStream);
+ parseTiffHeader(tiffStream);
+ long offset = tiffStream.readUnsignedInt();
+ if (offset > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException("Offset value is larger than Integer.MAX_VALUE");
+ }
+ return new IfdParser(tiffStream, (int)offset);
+ }
+
+ private void parseTiffHeader(TiffInputStream tiffStream) throws IOException,
+ ExifInvalidFormatException {
+ short byteOrder = tiffStream.readShort();
+ ByteOrder order;
+ if (LITTLE_ENDIAN_TAG == byteOrder) {
+ tiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+ } else if (BIG_ENDIAN_TAG == byteOrder) {
+ tiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
+ } else {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+
+ if (tiffStream.readShort() != TIFF_HEADER_TAIL) {
+ throw new ExifInvalidFormatException("Invalid TIFF header");
+ }
+ }
+
+ /**
+ * Try to seek the tiff data. If there is no tiff data, return false, else return true and
+ * the inputstream will be at the start of tiff data
+ */
+ private boolean seekTiffData(InputStream inputStream) throws IOException,
+ ExifInvalidFormatException {
+ DataInputStream dataStream = new DataInputStream(inputStream);
+
+ // SOI and APP1
+ if (dataStream.readShort() != SOI) {
+ throw new ExifInvalidFormatException("Invalid JPEG format");
+ }
+
+ if (dataStream.readShort() != APP1) {
+ return false;
+ }
+
+ // APP1 length, it's not used for us
+ dataStream.readShort();
+
+ // Exif header
+ if (dataStream.readInt() != EXIF_HEADER
+ || dataStream.readShort() != EXIF_HEADER_TAIL) {
+ // There is no EXIF data;
+ return false;
+ }
+
+ return true;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/ExifTag.java b/src/com/android/gallery3d/exif/ExifTag.java
new file mode 100644
index 000000000..87fbe6253
--- /dev/null
+++ b/src/com/android/gallery3d/exif/ExifTag.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+public class ExifTag {
+
+ public static final short TYPE_BYTE = 1;
+ public static final short TYPE_ASCII = 2;
+ public static final short TYPE_SHORT = 3;
+ public static final short TYPE_INT = 4;
+ public static final short TYPE_RATIONAL = 5;
+ public static final short TYPE_UNDEFINED = 7;
+ public static final short TYPE_SINT = 9;
+ public static final short TYPE_SRATIONAL = 10;
+
+ private static final int TYPE_TO_SIZE_MAP[] = new int[11];
+ static {
+ TYPE_TO_SIZE_MAP[TYPE_BYTE] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_ASCII] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_SHORT] = 2;
+ TYPE_TO_SIZE_MAP[TYPE_INT] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_RATIONAL] = 8;
+ TYPE_TO_SIZE_MAP[TYPE_UNDEFINED] = 1;
+ TYPE_TO_SIZE_MAP[TYPE_SINT] = 4;
+ TYPE_TO_SIZE_MAP[TYPE_SRATIONAL] = 8;
+ }
+
+ public static int getElementSize(short type) {
+ return TYPE_TO_SIZE_MAP[type];
+ }
+
+ private final short mTagId;
+ private final short mDataType;
+ private final int mDataCount;
+ private final int mOffset;
+
+ ExifTag(short tagId, short type, int dataCount) {
+ mTagId = tagId;
+ mDataType = type;
+ mDataCount = dataCount;
+ mOffset = -1;
+ }
+
+ ExifTag(short tagId, short type, int dataCount, int offset) {
+ mTagId = tagId;
+ mDataType = type;
+ mDataCount = dataCount;
+ mOffset = offset;
+ }
+
+ public int getOffset() {
+ return mOffset;
+ }
+
+ public short getTagId() {
+ return mTagId;
+ }
+
+ public short getDataType() {
+ return mDataType;
+ }
+
+ public int getComponentCount() {
+ return mDataCount;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/IfdParser.java b/src/com/android/gallery3d/exif/IfdParser.java
new file mode 100644
index 000000000..6af10c732
--- /dev/null
+++ b/src/com/android/gallery3d/exif/IfdParser.java
@@ -0,0 +1,181 @@
+/*
+ * 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.IOException;
+import java.nio.charset.Charset;
+import java.util.Comparator;
+import java.util.TreeSet;
+
+public class IfdParser {
+
+ // special sub IDF tags
+ private static final short EXIF_IDF = (short) 0x8769;
+ private static final short GPS_IDF = (short) 0x8825;
+ private static final short INTEROPERABILITY_IDF = (short) 0xA005;
+
+ private static final int TAG_SIZE = 12;
+
+ private final TiffInputStream mTiffStream;
+ private final int mEndOfTagOffset;
+ private final int mNumOfTag;
+ private int mNextOffset;
+ private int mOffsetToNextIfd = 0;
+
+ private TreeSet<ExifTag> mCorrespondingTag = new TreeSet<ExifTag>(
+ new Comparator<ExifTag>() {
+ @Override
+ public int compare(ExifTag lhs, ExifTag rhs) {
+ return lhs.getOffset() - rhs.getOffset();
+ }
+ });
+ private ExifTag mCurrTag;
+
+ public static final int TYPE_NEW_TAG = 0;
+ public static final int TYPE_VALUE_OF_PREV_TAG = 1;
+ public static final int TYPE_NEXT_IFD = 2;
+ public static final int TYPE_END = 3;
+ public static final int TYPE_SUB_IFD = 4;
+
+ IfdParser(TiffInputStream tiffStream, int offset) throws IOException {
+ mTiffStream = tiffStream;
+ mTiffStream.skipTo(offset);
+ mNumOfTag = mTiffStream.readUnsignedShort();
+ mEndOfTagOffset = offset + mNumOfTag * TAG_SIZE + 2;
+ mNextOffset = offset + 2;
+ }
+
+ public int next() throws IOException {
+ int offset = mTiffStream.getReadByteCount();
+
+ if (offset < mEndOfTagOffset) {
+ skipTo(mNextOffset);
+ mNextOffset += TAG_SIZE;
+ return TYPE_NEW_TAG;
+ }
+
+ if (offset == mEndOfTagOffset) {
+ mOffsetToNextIfd = mTiffStream.readInt();
+ }
+
+ if (!mCorrespondingTag.isEmpty()) {
+ mCurrTag = mCorrespondingTag.pollFirst();
+ skipTo(mCurrTag.getOffset());
+ if (isSubIfdTag(mCurrTag.getTagId())) {
+ return TYPE_SUB_IFD;
+ } else {
+ return TYPE_VALUE_OF_PREV_TAG;
+ }
+ } else {
+ if (offset <= mOffsetToNextIfd) {
+ skipTo(mOffsetToNextIfd);
+ return TYPE_NEXT_IFD;
+ } else {
+ return TYPE_END;
+ }
+ }
+ }
+
+ public ExifTag readTag() throws IOException, ExifInvalidFormatException {
+ short tagId = mTiffStream.readShort();
+ short dataFormat = mTiffStream.readShort();
+ long numOfComp = mTiffStream.readUnsignedInt();
+ if (numOfComp > Integer.MAX_VALUE) {
+ throw new ExifInvalidFormatException(
+ "Number of component is larger then Integer.MAX_VALUE");
+ }
+
+ if (ExifTag.getElementSize(dataFormat) * numOfComp > 4
+ || isSubIfdTag(tagId)) {
+ int offset = mTiffStream.readInt();
+ return new ExifTag(tagId, dataFormat, (int) numOfComp, offset);
+ } else {
+ return new ExifTag(tagId, dataFormat, (int) numOfComp);
+ }
+ }
+
+ public ExifTag getCorrespodingExifTag() {
+ return mCurrTag.getOffset() != mTiffStream.getReadByteCount() ? null : mCurrTag;
+ }
+
+ public void waitValueOfTag(ExifTag tag) {
+ mCorrespondingTag.add(tag);
+ }
+
+ public void skipTo(int offset) throws IOException {
+ mTiffStream.skipTo(offset);
+ while (!mCorrespondingTag.isEmpty() && mCorrespondingTag.first().getOffset() < offset) {
+ mCorrespondingTag.pollFirst();
+ }
+ }
+
+ public IfdParser parseIfdBlock() throws IOException {
+ return new IfdParser(mTiffStream, mTiffStream.getReadByteCount());
+ }
+
+ public int read(byte[] buffer, int offset, int length) throws IOException {
+ return mTiffStream.read(buffer, offset, length);
+ }
+
+ public int read(byte[] buffer) throws IOException {
+ return mTiffStream.read(buffer);
+ }
+
+ public String readString(int n) throws IOException {
+ byte[] buf = new byte[n];
+ mTiffStream.readOrThrow(buf);
+ return new String(buf, 0, n - 1, "UTF8");
+ }
+
+ public String readString(int n, Charset charset) throws IOException {
+ byte[] buf = new byte[n];
+ mTiffStream.readOrThrow(buf);
+ return new String(buf, 0, n - 1, charset);
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ public long readUnsignedInt() throws IOException {
+ return readInt() & 0xffffffffL;
+ }
+
+ public Rational readUnsignedRational() throws IOException {
+ long nomi = readUnsignedInt();
+ long denomi = readUnsignedInt();
+ return new Rational(nomi, denomi);
+ }
+
+ public int readInt() throws IOException {
+ return mTiffStream.readInt();
+ }
+
+ public short readShort() throws IOException {
+ return mTiffStream.readShort();
+ }
+
+ public Rational readRational() throws IOException {
+ int nomi = readInt();
+ int denomi = readInt();
+ return new Rational(nomi, denomi);
+ }
+
+ private static boolean isSubIfdTag(short tagId) {
+ return tagId == EXIF_IDF || tagId == GPS_IDF || tagId == INTEROPERABILITY_IDF;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/gallery3d/exif/Rational.java b/src/com/android/gallery3d/exif/Rational.java
new file mode 100644
index 000000000..cef6c9112
--- /dev/null
+++ b/src/com/android/gallery3d/exif/Rational.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+public class Rational {
+
+ private final long mNominator;
+ private final long mDenominator;
+
+ public Rational(long nominator, long denominator) {
+ mNominator = nominator;
+ mDenominator = denominator;
+ }
+
+ public long getNominator() {
+ return mNominator;
+ }
+
+ public long getDenominator() {
+ return mDenominator;
+ }
+}
diff --git a/src/com/android/gallery3d/exif/TiffInputStream.java b/src/com/android/gallery3d/exif/TiffInputStream.java
new file mode 100644
index 000000000..2b0054c46
--- /dev/null
+++ b/src/com/android/gallery3d/exif/TiffInputStream.java
@@ -0,0 +1,134 @@
+/*
+ * 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.EOFException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.Charset;
+
+class TiffInputStream extends FilterInputStream {
+
+ private int mCount = 0;
+
+ // allocate a byte buffer for a long value;
+ private final byte mByteArray[] = new byte[8];
+ private final ByteBuffer mByteBuffer = ByteBuffer.wrap(mByteArray);
+
+ protected TiffInputStream(InputStream in) {
+ super(in);
+ }
+
+ public int getReadByteCount() {
+ return mCount;
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ int r = super.read(b, off, len);
+ mCount += (r >= 0) ? r : 0;
+ return r;
+ }
+
+ @Override
+ public int read() throws IOException {
+ int r = super.read();
+ mCount += (r >= 0) ? 1 : 0;
+ return r;
+ }
+
+ @Override
+ public long skip(long length) throws IOException {
+ long skip = super.skip(length);
+ mCount += skip;
+ return skip;
+ }
+
+ public void skipOrThrow(long length) throws IOException {
+ if (skip(length) != length) throw new EOFException();
+ }
+
+ public void skipTo(long target) throws IOException {
+ long cur = mCount;
+ long diff = target - cur;
+ assert(diff >= 0);
+ skipOrThrow(diff);
+ }
+
+ public void readOrThrow(byte[] b, int off, int len) throws IOException {
+ int r = read(b, off, len);
+ if (r != len) throw new EOFException();
+ }
+
+ public void readOrThrow(byte[] b) throws IOException {
+ readOrThrow(b, 0, b.length);
+ }
+
+ public void setByteOrder(ByteOrder order) {
+ mByteBuffer.order(order);
+ }
+
+ public ByteOrder getByteOrder() {
+ return mByteBuffer.order();
+ }
+
+ public short readShort() throws IOException {
+ readOrThrow(mByteArray, 0 ,2);
+ mByteBuffer.rewind();
+ return mByteBuffer.getShort();
+ }
+
+ public int readUnsignedShort() throws IOException {
+ return readShort() & 0xffff;
+ }
+
+ public int readInt() throws IOException {
+ readOrThrow(mByteArray, 0 , 4);
+ mByteBuffer.rewind();
+ return mByteBuffer.getInt();
+ }
+
+ public long readUnsignedInt() throws IOException {
+ return readInt() & 0xffffffffL;
+ }
+
+ public long readLong() throws IOException {
+ readOrThrow(mByteArray, 0 , 8);
+ mByteBuffer.rewind();
+ return mByteBuffer.getLong();
+ }
+
+ public String readString(int n) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, "UTF8");
+ }
+
+ public String readString(int n, Charset charset) throws IOException {
+ byte buf[] = new byte[n];
+ readOrThrow(buf);
+ return new String(buf, charset);
+ }
+} \ No newline at end of file