diff options
author | Earl Ou <shunhsingou@google.com> | 2012-07-29 20:59:17 -0700 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-07-29 20:59:17 -0700 |
commit | 9636c66cb89e7243de92eb46ae90d6223b3d79f5 (patch) | |
tree | 0812bdd344000fe42dfb7af51196e14bab702356 /src | |
parent | dbc472eb08c19e78811823f8b83e59c3aeacc6d0 (diff) | |
parent | ea8759d2cf72a292a97264955bc895984d1ec257 (diff) | |
download | android_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.java | 23 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifParser.java | 98 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/ExifTag.java | 80 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/IfdParser.java | 181 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/Rational.java | 36 | ||||
-rw-r--r-- | src/com/android/gallery3d/exif/TiffInputStream.java | 134 |
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 |