Updating the ExifParsing code
> Using system implementation of ExifInterface to read orientation
> For inputstream, creating a temporary file with just the header
since the system API only supports file input
Change-Id: I19c94ff28e9d9bac14cd9b717de0ff165ba95595
This commit is contained in:
@@ -21,8 +21,6 @@ import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.gallery3d.exif.ExifInterface;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -42,38 +40,26 @@ public class BitmapUtils {
|
||||
}
|
||||
|
||||
public static int getRotationFromExif(Context context, Uri uri) {
|
||||
return BitmapUtils.getRotationFromExifHelper(null, 0, context, uri);
|
||||
return BitmapUtils.getRotationFromExifHelper(null, 0, uri, context);
|
||||
}
|
||||
|
||||
public static int getRotationFromExif(Resources res, int resId) {
|
||||
return BitmapUtils.getRotationFromExifHelper(res, resId, null, null);
|
||||
public static int getRotationFromExif(Resources res, int resId, Context context) {
|
||||
return BitmapUtils.getRotationFromExifHelper(res, resId, null, context);
|
||||
}
|
||||
|
||||
private static int getRotationFromExifHelper(Resources res, int resId, Context context, Uri uri) {
|
||||
ExifInterface ei = new ExifInterface();
|
||||
private static int getRotationFromExifHelper(Resources res, int resId,
|
||||
Uri uri, Context context) {
|
||||
InputStream is = null;
|
||||
BufferedInputStream bis = null;
|
||||
try {
|
||||
if (uri != null) {
|
||||
is = context.getContentResolver().openInputStream(uri);
|
||||
bis = new BufferedInputStream(is);
|
||||
ei.readExif(bis);
|
||||
} else {
|
||||
is = res.openRawResource(resId);
|
||||
bis = new BufferedInputStream(is);
|
||||
ei.readExif(bis);
|
||||
}
|
||||
Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
|
||||
if (ori != null) {
|
||||
return ExifInterface.getRotationForOrientationValue(ori.shortValue());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Getting exif data failed", e);
|
||||
} catch (NullPointerException e) {
|
||||
// Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
|
||||
return ExifOrientation.readRotation(new BufferedInputStream(is), context);
|
||||
} catch (IOException | NullPointerException e) {
|
||||
Log.w(TAG, "Getting exif data failed", e);
|
||||
} finally {
|
||||
Utils.closeSilently(bis);
|
||||
Utils.closeSilently(is);
|
||||
}
|
||||
return 0;
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Copyright (C) 2015 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.common;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.ExifInterface;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class ExifOrientation {
|
||||
private static final String TAG = "ExifOrientation";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final short SOI = (short) 0xFFD8; // start of input
|
||||
private static final short APP0 = (short) 0xFFE0;
|
||||
private static final short APPF = (short) 0xFFEF;
|
||||
private static final short APP1 = (short) 0xFFE1;
|
||||
private static final short SOS = (short) 0xFFDA; // start of stream
|
||||
private static final short EOI = (short) 0xFFD9; // end of input
|
||||
|
||||
// The header is available in first 64 bytes, so reading upto 128 bytes
|
||||
// should be more than enough.
|
||||
private static final int MAX_BYTES_TO_READ = 128 * 1024;
|
||||
|
||||
/**
|
||||
* Parses the rotation of the JPEG image from the input stream.
|
||||
*/
|
||||
public static final int readRotation(InputStream in, Context context) {
|
||||
// Since the platform implementation only takes file input, create a temporary file
|
||||
// with just the image header.
|
||||
File tempFile = null;
|
||||
DataOutputStream tempOut = null;
|
||||
|
||||
try {
|
||||
DataInputStream din = new DataInputStream(in);
|
||||
int pos = 0;
|
||||
if (din.readShort() == SOI) {
|
||||
pos += 2;
|
||||
|
||||
short marker = din.readShort();
|
||||
pos += 2;
|
||||
|
||||
while ((marker >= APP0 && marker <= APPF) && pos < MAX_BYTES_TO_READ) {
|
||||
int length = din.readUnsignedShort();
|
||||
if (length < 2) {
|
||||
throw new IOException("Invalid header size");
|
||||
}
|
||||
|
||||
// We only want APP1 headers
|
||||
if (length > 2) {
|
||||
if (marker == APP1) {
|
||||
// Copy the header
|
||||
if (tempFile == null) {
|
||||
tempFile = File.createTempFile(TAG, ".jpg", context.getCacheDir());
|
||||
tempOut = new DataOutputStream(new FileOutputStream(tempFile));
|
||||
tempOut.writeShort(SOI);
|
||||
}
|
||||
|
||||
tempOut.writeShort(marker);
|
||||
tempOut.writeShort(length);
|
||||
|
||||
byte[] header = new byte[length - 2];
|
||||
din.read(header);
|
||||
tempOut.write(header);
|
||||
} else {
|
||||
din.skip(length - 2);
|
||||
}
|
||||
}
|
||||
pos += length;
|
||||
|
||||
marker = din.readShort();
|
||||
pos += 2;
|
||||
}
|
||||
|
||||
if (tempOut != null) {
|
||||
// Write empty image data.
|
||||
tempOut.writeShort(SOS);
|
||||
// Write the frame size as 2. Since this includes the size bytes as well
|
||||
// (short = 2 bytes), it implies there is 0 byte of image data.
|
||||
tempOut.writeShort(2);
|
||||
|
||||
// End of input
|
||||
tempOut.writeShort(EOI);
|
||||
tempOut.close();
|
||||
|
||||
return readRotation(tempFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Error parsing input stream", e);
|
||||
}
|
||||
} finally {
|
||||
Utils.closeSilently(in);
|
||||
Utils.closeSilently(tempOut);
|
||||
if (tempFile != null) {
|
||||
tempFile.delete();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the rotation of the JPEG image.
|
||||
*/
|
||||
public static final int readRotation(String filePath) {
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(filePath);
|
||||
switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 0)) {
|
||||
case ExifInterface.ORIENTATION_ROTATE_90:
|
||||
return 90;
|
||||
case ExifInterface.ORIENTATION_ROTATE_270:
|
||||
return 270;
|
||||
case ExifInterface.ORIENTATION_ROTATE_180:
|
||||
return 180;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Error reading file", e);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* 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 CountedDataInputStream 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 CountedDataInputStream(InputStream in) {
|
||||
super(in);
|
||||
}
|
||||
|
||||
public int getReadByteCount() {
|
||||
return mCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b) throws IOException {
|
||||
int r = in.read(b);
|
||||
mCount += (r >= 0) ? r : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] b, int off, int len) throws IOException {
|
||||
int r = in.read(b, off, len);
|
||||
mCount += (r >= 0) ? r : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
int r = in.read();
|
||||
mCount += (r >= 0) ? 1 : 0;
|
||||
return r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long length) throws IOException {
|
||||
long skip = in.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);
|
||||
}
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.Log;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class stores the EXIF header in IFDs according to the JPEG
|
||||
* specification. It is the result produced by {@link ExifReader}.
|
||||
*
|
||||
* @see ExifReader
|
||||
* @see IfdData
|
||||
*/
|
||||
class ExifData {
|
||||
private static final String TAG = "ExifData";
|
||||
private static final byte[] USER_COMMENT_ASCII = {
|
||||
0x41, 0x53, 0x43, 0x49, 0x49, 0x00, 0x00, 0x00
|
||||
};
|
||||
private static final byte[] USER_COMMENT_JIS = {
|
||||
0x4A, 0x49, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
private static final byte[] USER_COMMENT_UNICODE = {
|
||||
0x55, 0x4E, 0x49, 0x43, 0x4F, 0x44, 0x45, 0x00
|
||||
};
|
||||
|
||||
private final IfdData[] mIfdDatas = new IfdData[IfdId.TYPE_IFD_COUNT];
|
||||
private byte[] mThumbnail;
|
||||
private ArrayList<byte[]> mStripBytes = new ArrayList<byte[]>();
|
||||
private final ByteOrder mByteOrder;
|
||||
|
||||
ExifData(ByteOrder order) {
|
||||
mByteOrder = order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compressed thumbnail. Returns null if there is no compressed
|
||||
* thumbnail.
|
||||
*
|
||||
* @see #hasCompressedThumbnail()
|
||||
*/
|
||||
protected byte[] getCompressedThumbnail() {
|
||||
return mThumbnail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compressed thumbnail.
|
||||
*/
|
||||
protected void setCompressedThumbnail(byte[] thumbnail) {
|
||||
mThumbnail = thumbnail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true it this header contains a compressed thumbnail.
|
||||
*/
|
||||
protected boolean hasCompressedThumbnail() {
|
||||
return mThumbnail != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an uncompressed strip.
|
||||
*/
|
||||
protected void setStripBytes(int index, byte[] strip) {
|
||||
if (index < mStripBytes.size()) {
|
||||
mStripBytes.set(index, strip);
|
||||
} else {
|
||||
for (int i = mStripBytes.size(); i < index; i++) {
|
||||
mStripBytes.add(null);
|
||||
}
|
||||
mStripBytes.add(strip);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strip count.
|
||||
*/
|
||||
protected int getStripCount() {
|
||||
return mStripBytes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the strip at the specified index.
|
||||
*
|
||||
* @exceptions #IndexOutOfBoundException
|
||||
*/
|
||||
protected byte[] getStrip(int index) {
|
||||
return mStripBytes.get(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this header contains uncompressed strip.
|
||||
*/
|
||||
protected boolean hasUncompressedStrip() {
|
||||
return mStripBytes.size() != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the byte order.
|
||||
*/
|
||||
protected ByteOrder getByteOrder() {
|
||||
return mByteOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IfdData} object corresponding to a given IFD if it
|
||||
* exists or null.
|
||||
*/
|
||||
protected IfdData getIfdData(int ifdId) {
|
||||
if (ExifTag.isValidIfd(ifdId)) {
|
||||
return mIfdDatas[ifdId];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds IFD data. If IFD data of the same type already exists, it will be
|
||||
* replaced by the new data.
|
||||
*/
|
||||
protected void addIfdData(IfdData data) {
|
||||
mIfdDatas[data.getId()] = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link IfdData} object corresponding to a given IFD or
|
||||
* generates one if none exist.
|
||||
*/
|
||||
protected IfdData getOrCreateIfdData(int ifdId) {
|
||||
IfdData ifdData = mIfdDatas[ifdId];
|
||||
if (ifdData == null) {
|
||||
ifdData = new IfdData(ifdId);
|
||||
mIfdDatas[ifdId] = ifdData;
|
||||
}
|
||||
return ifdData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the tag with a given TID in the given IFD if the tag exists.
|
||||
* Otherwise returns null.
|
||||
*/
|
||||
protected ExifTag getTag(short tag, int ifd) {
|
||||
IfdData ifdData = mIfdDatas[ifd];
|
||||
return (ifdData == null) ? null : ifdData.getTag(tag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ExifTag to its default IFD and returns an existing ExifTag
|
||||
* with the same TID or null if none exist.
|
||||
*/
|
||||
protected ExifTag addTag(ExifTag tag) {
|
||||
if (tag != null) {
|
||||
int ifd = tag.getIfd();
|
||||
return addTag(tag, ifd);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given ExifTag to the given IFD and returns an existing ExifTag
|
||||
* with the same TID or null if none exist.
|
||||
*/
|
||||
protected ExifTag addTag(ExifTag tag, int ifdId) {
|
||||
if (tag != null && ExifTag.isValidIfd(ifdId)) {
|
||||
IfdData ifdData = getOrCreateIfdData(ifdId);
|
||||
return ifdData.setTag(tag);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected void clearThumbnailAndStrips() {
|
||||
mThumbnail = null;
|
||||
mStripBytes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the thumbnail and its related tags. IFD1 will be removed.
|
||||
*/
|
||||
protected void removeThumbnailData() {
|
||||
clearThumbnailAndStrips();
|
||||
mIfdDatas[IfdId.TYPE_IFD_1] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tag with a given TID and IFD.
|
||||
*/
|
||||
protected void removeTag(short tagId, int ifdId) {
|
||||
IfdData ifdData = mIfdDatas[ifdId];
|
||||
if (ifdData == null) {
|
||||
return;
|
||||
}
|
||||
ifdData.removeTag(tagId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the user comment tag into string as specified in the EXIF
|
||||
* standard. Returns null if decoding failed.
|
||||
*/
|
||||
protected String getUserComment() {
|
||||
IfdData ifdData = mIfdDatas[IfdId.TYPE_IFD_0];
|
||||
if (ifdData == null) {
|
||||
return null;
|
||||
}
|
||||
ExifTag tag = ifdData.getTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_USER_COMMENT));
|
||||
if (tag == null) {
|
||||
return null;
|
||||
}
|
||||
if (tag.getComponentCount() < 8) {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte[] buf = new byte[tag.getComponentCount()];
|
||||
tag.getBytes(buf);
|
||||
|
||||
byte[] code = new byte[8];
|
||||
System.arraycopy(buf, 0, code, 0, 8);
|
||||
|
||||
try {
|
||||
if (Arrays.equals(code, USER_COMMENT_ASCII)) {
|
||||
return new String(buf, 8, buf.length - 8, "US-ASCII");
|
||||
} else if (Arrays.equals(code, USER_COMMENT_JIS)) {
|
||||
return new String(buf, 8, buf.length - 8, "EUC-JP");
|
||||
} else if (Arrays.equals(code, USER_COMMENT_UNICODE)) {
|
||||
return new String(buf, 8, buf.length - 8, "UTF-16");
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.w(TAG, "Failed to decode the user comment");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link ExifTag}s in the ExifData or null if there
|
||||
* are none.
|
||||
*/
|
||||
protected List<ExifTag> getAllTags() {
|
||||
ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
|
||||
for (IfdData d : mIfdDatas) {
|
||||
if (d != null) {
|
||||
ExifTag[] tags = d.getAllTags();
|
||||
if (tags != null) {
|
||||
for (ExifTag t : tags) {
|
||||
ret.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link ExifTag}s in a given IFD or null if there
|
||||
* are none.
|
||||
*/
|
||||
protected List<ExifTag> getAllTagsForIfd(int ifd) {
|
||||
IfdData d = mIfdDatas[ifd];
|
||||
if (d == null) {
|
||||
return null;
|
||||
}
|
||||
ExifTag[] tags = d.getAllTags();
|
||||
if (tags == null) {
|
||||
return null;
|
||||
}
|
||||
ArrayList<ExifTag> ret = new ArrayList<ExifTag>(tags.length);
|
||||
for (ExifTag t : tags) {
|
||||
ret.add(t);
|
||||
}
|
||||
if (ret.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all {@link ExifTag}s with a given TID or null if there
|
||||
* are none.
|
||||
*/
|
||||
protected List<ExifTag> getAllTagsForTagId(short tag) {
|
||||
ArrayList<ExifTag> ret = new ArrayList<ExifTag>();
|
||||
for (IfdData d : mIfdDatas) {
|
||||
if (d != null) {
|
||||
ExifTag t = d.getTag(tag);
|
||||
if (t != null) {
|
||||
ret.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ret.size() == 0) {
|
||||
return null;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof ExifData) {
|
||||
ExifData data = (ExifData) obj;
|
||||
if (data.mByteOrder != mByteOrder ||
|
||||
data.mStripBytes.size() != mStripBytes.size() ||
|
||||
!Arrays.equals(data.mThumbnail, mThumbnail)) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < mStripBytes.size(); i++) {
|
||||
if (!Arrays.equals(data.mStripBytes.get(i), mStripBytes.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
|
||||
IfdData ifd1 = data.getIfdData(i);
|
||||
IfdData ifd2 = getIfdData(i);
|
||||
if (ifd1 != ifd2 && ifd1 != null && !ifd1.equals(ifd2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class ExifModifier {
|
||||
public static final String TAG = "ExifModifier";
|
||||
public static final boolean DEBUG = false;
|
||||
private final ByteBuffer mByteBuffer;
|
||||
private final ExifData mTagToModified;
|
||||
private final List<TagOffset> mTagOffsets = new ArrayList<TagOffset>();
|
||||
private final ExifInterface mInterface;
|
||||
private int mOffsetBase;
|
||||
|
||||
private static class TagOffset {
|
||||
final int mOffset;
|
||||
final ExifTag mTag;
|
||||
|
||||
TagOffset(ExifTag tag, int offset) {
|
||||
mTag = tag;
|
||||
mOffset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
protected ExifModifier(ByteBuffer byteBuffer, ExifInterface iRef) throws IOException,
|
||||
ExifInvalidFormatException {
|
||||
mByteBuffer = byteBuffer;
|
||||
mOffsetBase = byteBuffer.position();
|
||||
mInterface = iRef;
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = new ByteBufferInputStream(byteBuffer);
|
||||
// Do not require any IFD;
|
||||
ExifParser parser = ExifParser.parse(is, mInterface);
|
||||
mTagToModified = new ExifData(parser.getByteOrder());
|
||||
mOffsetBase += parser.getTiffStartPosition();
|
||||
mByteBuffer.position(0);
|
||||
} finally {
|
||||
ExifInterface.closeSilently(is);
|
||||
}
|
||||
}
|
||||
|
||||
protected ByteOrder getByteOrder() {
|
||||
return mTagToModified.getByteOrder();
|
||||
}
|
||||
|
||||
protected 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, mInterface);
|
||||
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 {
|
||||
ExifInterface.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) {
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "modifying tag to: \n" + tag.toString());
|
||||
Log.v(TAG, "at offset: " + 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.getNumerator());
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,518 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.Log;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* This class provides a way to replace the Exif header of a JPEG image.
|
||||
* <p>
|
||||
* Below is an example of writing EXIF data into a file
|
||||
*
|
||||
* <pre>
|
||||
* public static void writeExif(byte[] jpeg, ExifData exif, String path) {
|
||||
* OutputStream os = null;
|
||||
* try {
|
||||
* os = new FileOutputStream(path);
|
||||
* ExifOutputStream eos = new ExifOutputStream(os);
|
||||
* // Set the exif header
|
||||
* eos.setExifData(exif);
|
||||
* // Write the original jpeg out, the header will be add into the file.
|
||||
* eos.write(jpeg);
|
||||
* } catch (FileNotFoundException e) {
|
||||
* e.printStackTrace();
|
||||
* } catch (IOException e) {
|
||||
* e.printStackTrace();
|
||||
* } finally {
|
||||
* if (os != null) {
|
||||
* try {
|
||||
* os.close();
|
||||
* } catch (IOException e) {
|
||||
* e.printStackTrace();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
class ExifOutputStream extends FilterOutputStream {
|
||||
private static final String TAG = "ExifOutputStream";
|
||||
private static final boolean DEBUG = false;
|
||||
private static final int STREAMBUFFER_SIZE = 0x00010000; // 64Kb
|
||||
|
||||
private static final int STATE_SOI = 0;
|
||||
private static final int STATE_FRAME_HEADER = 1;
|
||||
private static final int STATE_JPEG_DATA = 2;
|
||||
|
||||
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 static final int MAX_EXIF_SIZE = 65535;
|
||||
|
||||
private ExifData mExifData;
|
||||
private int mState = STATE_SOI;
|
||||
private int mByteToSkip;
|
||||
private int mByteToCopy;
|
||||
private byte[] mSingleByteArray = new byte[1];
|
||||
private ByteBuffer mBuffer = ByteBuffer.allocate(4);
|
||||
private final ExifInterface mInterface;
|
||||
|
||||
protected ExifOutputStream(OutputStream ou, ExifInterface iRef) {
|
||||
super(new BufferedOutputStream(ou, STREAMBUFFER_SIZE));
|
||||
mInterface = iRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ExifData to be written into the JPEG file. Should be called
|
||||
* before writing image data.
|
||||
*/
|
||||
protected void setExifData(ExifData exifData) {
|
||||
mExifData = exifData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Exif header to be written into the JPEF file.
|
||||
*/
|
||||
protected 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the image out. The input data should be a valid JPEG format. After
|
||||
* writing, it's Exif header will be replaced by the given header.
|
||||
*/
|
||||
@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;
|
||||
}
|
||||
if (length == 0) {
|
||||
return;
|
||||
}
|
||||
switch (mState) {
|
||||
case STATE_SOI:
|
||||
int byteRead = requestByteToBuffer(2, buffer, offset, length);
|
||||
offset += byteRead;
|
||||
length -= byteRead;
|
||||
if (mBuffer.position() < 2) {
|
||||
return;
|
||||
}
|
||||
mBuffer.rewind();
|
||||
if (mBuffer.getShort() != JpegHeader.SOI) {
|
||||
throw new IOException("Not a valid jpeg image, cannot write exif");
|
||||
}
|
||||
out.write(mBuffer.array(), 0, 2);
|
||||
mState = STATE_FRAME_HEADER;
|
||||
mBuffer.rewind();
|
||||
writeExifData();
|
||||
break;
|
||||
case STATE_FRAME_HEADER:
|
||||
// We ignore the APP1 segment and copy all other segments
|
||||
// until SOF tag.
|
||||
byteRead = requestByteToBuffer(4, buffer, offset, length);
|
||||
offset += byteRead;
|
||||
length -= byteRead;
|
||||
// Check if this image data doesn't contain SOF.
|
||||
if (mBuffer.position() == 2) {
|
||||
short tag = mBuffer.getShort();
|
||||
if (tag == JpegHeader.EOI) {
|
||||
out.write(mBuffer.array(), 0, 2);
|
||||
mBuffer.rewind();
|
||||
}
|
||||
}
|
||||
if (mBuffer.position() < 4) {
|
||||
return;
|
||||
}
|
||||
mBuffer.rewind();
|
||||
short marker = mBuffer.getShort();
|
||||
if (marker == JpegHeader.APP1) {
|
||||
mByteToSkip = (mBuffer.getShort() & 0x0000ffff) - 2;
|
||||
mState = STATE_JPEG_DATA;
|
||||
} else if (!JpegHeader.isSofMarker(marker)) {
|
||||
out.write(mBuffer.array(), 0, 4);
|
||||
mByteToCopy = (mBuffer.getShort() & 0x0000ffff) - 2;
|
||||
} else {
|
||||
out.write(mBuffer.array(), 0, 4);
|
||||
mState = STATE_JPEG_DATA;
|
||||
}
|
||||
mBuffer.rewind();
|
||||
}
|
||||
}
|
||||
if (length > 0) {
|
||||
out.write(buffer, offset, length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the one bytes out. The input data should be a valid JPEG format.
|
||||
* After writing, it's Exif header will be replaced by the given header.
|
||||
*/
|
||||
@Override
|
||||
public void write(int oneByte) throws IOException {
|
||||
mSingleByteArray[0] = (byte) (0xff & oneByte);
|
||||
write(mSingleByteArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to calling write(buffer, 0, buffer.length).
|
||||
*/
|
||||
@Override
|
||||
public void write(byte[] buffer) throws IOException {
|
||||
write(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
private void writeExifData() throws IOException {
|
||||
if (mExifData == null) {
|
||||
return;
|
||||
}
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "Writing exif data...");
|
||||
}
|
||||
ArrayList<ExifTag> nullTags = stripNullValueTags(mExifData);
|
||||
createRequiredIfdAndTag();
|
||||
int exifSize = calculateAllOffset();
|
||||
if (exifSize + 8 > MAX_EXIF_SIZE) {
|
||||
throw new IOException("Exif header is too large (>64Kb)");
|
||||
}
|
||||
OrderedDataOutputStream dataOutputStream = new OrderedDataOutputStream(out);
|
||||
dataOutputStream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
dataOutputStream.writeShort(JpegHeader.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);
|
||||
writeAllTags(dataOutputStream);
|
||||
writeThumbnail(dataOutputStream);
|
||||
for (ExifTag t : nullTags) {
|
||||
mExifData.addTag(t);
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<ExifTag> stripNullValueTags(ExifData data) {
|
||||
ArrayList<ExifTag> nullTags = new ArrayList<ExifTag>();
|
||||
for(ExifTag t : data.getAllTags()) {
|
||||
if (t.getValue() == null && !ExifInterface.isOffsetTag(t.getTagId())) {
|
||||
data.removeTag(t.getTagId(), t.getIfd());
|
||||
nullTags.add(t);
|
||||
}
|
||||
}
|
||||
return nullTags;
|
||||
}
|
||||
|
||||
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 writeAllTags(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();
|
||||
dataOutputStream.writeShort((short) tags.length);
|
||||
for (ExifTag tag : tags) {
|
||||
dataOutputStream.writeShort(tag.getTagId());
|
||||
dataOutputStream.writeShort(tag.getDataType());
|
||||
dataOutputStream.writeInt(tag.getComponentCount());
|
||||
if (DEBUG) {
|
||||
Log.v(TAG, "\n" + tag.toString());
|
||||
}
|
||||
if (tag.getDataSize() > 4) {
|
||||
dataOutputStream.writeInt(tag.getOffset());
|
||||
} else {
|
||||
ExifOutputStream.writeTagValue(tag, dataOutputStream);
|
||||
for (int i = 0, n = 4 - tag.getDataSize(); i < n; i++) {
|
||||
dataOutputStream.write(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
dataOutputStream.writeInt(ifd.getOffsetToNextIfd());
|
||||
for (ExifTag tag : tags) {
|
||||
if (tag.getDataSize() > 4) {
|
||||
ExifOutputStream.writeTagValue(tag, dataOutputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateOffsetOfIfd(IfdData ifd, int offset) {
|
||||
offset += 2 + ifd.getTagCount() * TAG_SIZE + 4;
|
||||
ExifTag[] tags = ifd.getAllTags();
|
||||
for (ExifTag tag : tags) {
|
||||
if (tag.getDataSize() > 4) {
|
||||
tag.setOffset(offset);
|
||||
offset += tag.getDataSize();
|
||||
}
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
private void createRequiredIfdAndTag() throws IOException {
|
||||
// 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 = mInterface.buildUninitializedTag(ExifInterface.TAG_EXIF_IFD);
|
||||
if (exifOffsetTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_EXIF_IFD);
|
||||
}
|
||||
ifd0.setTag(exifOffsetTag);
|
||||
|
||||
// Exif IFD is required for all files.
|
||||
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 = mInterface.buildUninitializedTag(ExifInterface.TAG_GPS_IFD);
|
||||
if (gpsOffsetTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_GPS_IFD);
|
||||
}
|
||||
ifd0.setTag(gpsOffsetTag);
|
||||
}
|
||||
|
||||
// Interoperability IFD
|
||||
IfdData interIfd = mExifData.getIfdData(IfdId.TYPE_IFD_INTEROPERABILITY);
|
||||
if (interIfd != null) {
|
||||
ExifTag interOffsetTag = mInterface
|
||||
.buildUninitializedTag(ExifInterface.TAG_INTEROPERABILITY_IFD);
|
||||
if (interOffsetTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_INTEROPERABILITY_IFD);
|
||||
}
|
||||
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 = mInterface
|
||||
.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
|
||||
if (offsetTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
|
||||
}
|
||||
|
||||
ifd1.setTag(offsetTag);
|
||||
ExifTag lengthTag = mInterface
|
||||
.buildUninitializedTag(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
|
||||
if (lengthTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
|
||||
}
|
||||
|
||||
lengthTag.setValue(mExifData.getCompressedThumbnail().length);
|
||||
ifd1.setTag(lengthTag);
|
||||
|
||||
// Get rid of tags for uncompressed if they exist.
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
|
||||
} else if (mExifData.hasUncompressedStrip()) {
|
||||
if (ifd1 == null) {
|
||||
ifd1 = new IfdData(IfdId.TYPE_IFD_1);
|
||||
mExifData.addIfdData(ifd1);
|
||||
}
|
||||
int stripCount = mExifData.getStripCount();
|
||||
ExifTag offsetTag = mInterface.buildUninitializedTag(ExifInterface.TAG_STRIP_OFFSETS);
|
||||
if (offsetTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_STRIP_OFFSETS);
|
||||
}
|
||||
ExifTag lengthTag = mInterface
|
||||
.buildUninitializedTag(ExifInterface.TAG_STRIP_BYTE_COUNTS);
|
||||
if (lengthTag == null) {
|
||||
throw new IOException("No definition for crucial exif tag: "
|
||||
+ ExifInterface.TAG_STRIP_BYTE_COUNTS);
|
||||
}
|
||||
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);
|
||||
// Get rid of tags for compressed if they exist.
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
|
||||
ifd1.removeTag(ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
|
||||
} else if (ifd1 != null) {
|
||||
// Get rid of offset and length tags if there is no thumbnail.
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS));
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS));
|
||||
ifd1.removeTag(ExifInterface.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT));
|
||||
ifd1.removeTag(ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateAllOffset() {
|
||||
int offset = TIFF_HEADER_SIZE;
|
||||
IfdData ifd0 = mExifData.getIfdData(IfdId.TYPE_IFD_0);
|
||||
offset = calculateOffsetOfIfd(ifd0, offset);
|
||||
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.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(ExifInterface.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD))
|
||||
.setValue(offset);
|
||||
offset = calculateOffsetOfIfd(interIfd, offset);
|
||||
}
|
||||
|
||||
IfdData gpsIfd = mExifData.getIfdData(IfdId.TYPE_IFD_GPS);
|
||||
if (gpsIfd != null) {
|
||||
ifd0.getTag(ExifInterface.getTrueTagKey(ExifInterface.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(ExifInterface.getTrueTagKey(ExifInterface.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(ExifInterface.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS)).setValue(
|
||||
offsets);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
static void writeTagValue(ExifTag tag, OrderedDataOutputStream dataOutputStream)
|
||||
throws IOException {
|
||||
switch (tag.getDataType()) {
|
||||
case ExifTag.TYPE_ASCII:
|
||||
byte buf[] = tag.getStringByte();
|
||||
if (buf.length == tag.getComponentCount()) {
|
||||
buf[buf.length - 1] = 0;
|
||||
dataOutputStream.write(buf);
|
||||
} else {
|
||||
dataOutputStream.write(buf);
|
||||
dataOutputStream.write(0);
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_LONG:
|
||||
case ExifTag.TYPE_UNSIGNED_LONG:
|
||||
for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
|
||||
dataOutputStream.writeInt((int) tag.getValueAt(i));
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_RATIONAL:
|
||||
case ExifTag.TYPE_UNSIGNED_RATIONAL:
|
||||
for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
|
||||
dataOutputStream.writeRational(tag.getRational(i));
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_UNDEFINED:
|
||||
case ExifTag.TYPE_UNSIGNED_BYTE:
|
||||
buf = new byte[tag.getComponentCount()];
|
||||
tag.getBytes(buf);
|
||||
dataOutputStream.write(buf);
|
||||
break;
|
||||
case ExifTag.TYPE_UNSIGNED_SHORT:
|
||||
for (int i = 0, n = tag.getComponentCount(); i < n; i++) {
|
||||
dataOutputStream.writeShort((short) tag.getValueAt(i));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,916 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* This class provides a low-level EXIF parsing API. Given a JPEG format
|
||||
* InputStream, the caller can request which IFD's to read via
|
||||
* {@link #parse(InputStream, int)} with given options.
|
||||
* <p>
|
||||
* Below is an example of getting EXIF data from IFD 0 and EXIF IFD using the
|
||||
* parser.
|
||||
*
|
||||
* <pre>
|
||||
* void parse() {
|
||||
* ExifParser parser = ExifParser.parse(mImageInputStream,
|
||||
* ExifParser.OPTION_IFD_0 | ExifParser.OPTIONS_IFD_EXIF);
|
||||
* int event = parser.next();
|
||||
* while (event != ExifParser.EVENT_END) {
|
||||
* switch (event) {
|
||||
* case ExifParser.EVENT_START_OF_IFD:
|
||||
* break;
|
||||
* case ExifParser.EVENT_NEW_TAG:
|
||||
* ExifTag tag = parser.getTag();
|
||||
* if (!tag.hasValue()) {
|
||||
* parser.registerForTagValue(tag);
|
||||
* } else {
|
||||
* processTag(tag);
|
||||
* }
|
||||
* break;
|
||||
* case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
|
||||
* tag = parser.getTag();
|
||||
* if (tag.getDataType() != ExifTag.TYPE_UNDEFINED) {
|
||||
* processTag(tag);
|
||||
* }
|
||||
* break;
|
||||
* }
|
||||
* event = parser.next();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* void processTag(ExifTag tag) {
|
||||
* // process the tag as you like.
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
class ExifParser {
|
||||
private static final boolean LOGV = false;
|
||||
private static final String TAG = "ExifParser";
|
||||
/**
|
||||
* When the parser reaches a new IFD area. Call {@link #getCurrentIfd()} to
|
||||
* know which IFD we are in.
|
||||
*/
|
||||
public static final int EVENT_START_OF_IFD = 0;
|
||||
/**
|
||||
* When the parser reaches a new tag. Call {@link #getTag()}to get the
|
||||
* corresponding tag.
|
||||
*/
|
||||
public static final int EVENT_NEW_TAG = 1;
|
||||
/**
|
||||
* When the parser reaches the value area of tag that is registered by
|
||||
* {@link #registerForTagValue(ExifTag)} previously. Call {@link #getTag()}
|
||||
* to get the corresponding tag.
|
||||
*/
|
||||
public static final int EVENT_VALUE_OF_REGISTERED_TAG = 2;
|
||||
|
||||
/**
|
||||
* When the parser reaches the compressed image area.
|
||||
*/
|
||||
public static final int EVENT_COMPRESSED_IMAGE = 3;
|
||||
/**
|
||||
* When the parser reaches the uncompressed image strip. Call
|
||||
* {@link #getStripIndex()} to get the index of the strip.
|
||||
*
|
||||
* @see #getStripIndex()
|
||||
* @see #getStripCount()
|
||||
*/
|
||||
public static final int EVENT_UNCOMPRESSED_STRIP = 4;
|
||||
/**
|
||||
* When there is nothing more to parse.
|
||||
*/
|
||||
public static final int EVENT_END = 5;
|
||||
|
||||
/**
|
||||
* Option bit to request to parse IFD0.
|
||||
*/
|
||||
public static final int OPTION_IFD_0 = 1 << 0;
|
||||
/**
|
||||
* Option bit to request to parse IFD1.
|
||||
*/
|
||||
public static final int OPTION_IFD_1 = 1 << 1;
|
||||
/**
|
||||
* Option bit to request to parse Exif-IFD.
|
||||
*/
|
||||
public static final int OPTION_IFD_EXIF = 1 << 2;
|
||||
/**
|
||||
* Option bit to request to parse GPS-IFD.
|
||||
*/
|
||||
public static final int OPTION_IFD_GPS = 1 << 3;
|
||||
/**
|
||||
* Option bit to request to parse Interoperability-IFD.
|
||||
*/
|
||||
public static final int OPTION_IFD_INTEROPERABILITY = 1 << 4;
|
||||
/**
|
||||
* Option bit to request to parse thumbnail.
|
||||
*/
|
||||
public static final int OPTION_THUMBNAIL = 1 << 5;
|
||||
|
||||
protected static final int EXIF_HEADER = 0x45786966; // EXIF header "Exif"
|
||||
protected static final short EXIF_HEADER_TAIL = (short) 0x0000; // EXIF header in APP1
|
||||
|
||||
// TIFF header
|
||||
protected static final short LITTLE_ENDIAN_TAG = (short) 0x4949; // "II"
|
||||
protected static final short BIG_ENDIAN_TAG = (short) 0x4d4d; // "MM"
|
||||
protected static final short TIFF_HEADER_TAIL = 0x002A;
|
||||
|
||||
protected static final int TAG_SIZE = 12;
|
||||
protected static final int OFFSET_SIZE = 2;
|
||||
|
||||
private static final Charset US_ASCII = Charset.forName("US-ASCII");
|
||||
|
||||
protected static final int DEFAULT_IFD0_OFFSET = 8;
|
||||
|
||||
private final CountedDataInputStream mTiffStream;
|
||||
private final int mOptions;
|
||||
private int mIfdStartOffset = 0;
|
||||
private int mNumOfTagInIfd = 0;
|
||||
private int mIfdType;
|
||||
private ExifTag mTag;
|
||||
private ImageEvent mImageEvent;
|
||||
private int mStripCount;
|
||||
private ExifTag mStripSizeTag;
|
||||
private ExifTag mJpegSizeTag;
|
||||
private boolean mNeedToParseOffsetsInCurrentIfd;
|
||||
private boolean mContainExifData = false;
|
||||
private int mApp1End;
|
||||
private int mOffsetToApp1EndFromSOF = 0;
|
||||
private byte[] mDataAboveIfd0;
|
||||
private int mIfd0Position;
|
||||
private int mTiffStartPosition;
|
||||
private final ExifInterface mInterface;
|
||||
|
||||
private static final short TAG_EXIF_IFD = ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_EXIF_IFD);
|
||||
private static final short TAG_GPS_IFD = ExifInterface.getTrueTagKey(ExifInterface.TAG_GPS_IFD);
|
||||
private static final short TAG_INTEROPERABILITY_IFD = ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_INTEROPERABILITY_IFD);
|
||||
private static final short TAG_JPEG_INTERCHANGE_FORMAT = ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT);
|
||||
private static final short TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH);
|
||||
private static final short TAG_STRIP_OFFSETS = ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_STRIP_OFFSETS);
|
||||
private static final short TAG_STRIP_BYTE_COUNTS = ExifInterface
|
||||
.getTrueTagKey(ExifInterface.TAG_STRIP_BYTE_COUNTS);
|
||||
|
||||
private final TreeMap<Integer, Object> mCorrespondingEvent = new TreeMap<Integer, Object>();
|
||||
|
||||
private boolean isIfdRequested(int ifdType) {
|
||||
switch (ifdType) {
|
||||
case IfdId.TYPE_IFD_0:
|
||||
return (mOptions & OPTION_IFD_0) != 0;
|
||||
case IfdId.TYPE_IFD_1:
|
||||
return (mOptions & OPTION_IFD_1) != 0;
|
||||
case IfdId.TYPE_IFD_EXIF:
|
||||
return (mOptions & OPTION_IFD_EXIF) != 0;
|
||||
case IfdId.TYPE_IFD_GPS:
|
||||
return (mOptions & OPTION_IFD_GPS) != 0;
|
||||
case IfdId.TYPE_IFD_INTEROPERABILITY:
|
||||
return (mOptions & OPTION_IFD_INTEROPERABILITY) != 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isThumbnailRequested() {
|
||||
return (mOptions & OPTION_THUMBNAIL) != 0;
|
||||
}
|
||||
|
||||
private ExifParser(InputStream inputStream, int options, ExifInterface iRef)
|
||||
throws IOException, ExifInvalidFormatException {
|
||||
if (inputStream == null) {
|
||||
throw new IOException("Null argument inputStream to ExifParser");
|
||||
}
|
||||
if (LOGV) {
|
||||
Log.v(TAG, "Reading exif...");
|
||||
}
|
||||
mInterface = iRef;
|
||||
mContainExifData = seekTiffData(inputStream);
|
||||
mTiffStream = new CountedDataInputStream(inputStream);
|
||||
mOptions = options;
|
||||
if (!mContainExifData) {
|
||||
return;
|
||||
}
|
||||
|
||||
parseTiffHeader();
|
||||
long offset = mTiffStream.readUnsignedInt();
|
||||
if (offset > Integer.MAX_VALUE) {
|
||||
throw new ExifInvalidFormatException("Invalid offset " + offset);
|
||||
}
|
||||
mIfd0Position = (int) offset;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the the given InputStream with the given options
|
||||
*
|
||||
* @exception IOException
|
||||
* @exception ExifInvalidFormatException
|
||||
*/
|
||||
protected static ExifParser parse(InputStream inputStream, int options, ExifInterface iRef)
|
||||
throws IOException, ExifInvalidFormatException {
|
||||
return new ExifParser(inputStream, options, iRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the the given InputStream with default options; that is, every IFD
|
||||
* and thumbnaill will be parsed.
|
||||
*
|
||||
* @exception IOException
|
||||
* @exception ExifInvalidFormatException
|
||||
* @see #parse(InputStream, int)
|
||||
*/
|
||||
protected static ExifParser parse(InputStream inputStream, ExifInterface iRef)
|
||||
throws IOException, ExifInvalidFormatException {
|
||||
return new ExifParser(inputStream, OPTION_IFD_0 | OPTION_IFD_1
|
||||
| OPTION_IFD_EXIF | OPTION_IFD_GPS | OPTION_IFD_INTEROPERABILITY
|
||||
| OPTION_THUMBNAIL, iRef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the parser forward and returns the next parsing event
|
||||
*
|
||||
* @exception IOException
|
||||
* @exception ExifInvalidFormatException
|
||||
* @see #EVENT_START_OF_IFD
|
||||
* @see #EVENT_NEW_TAG
|
||||
* @see #EVENT_VALUE_OF_REGISTERED_TAG
|
||||
* @see #EVENT_COMPRESSED_IMAGE
|
||||
* @see #EVENT_UNCOMPRESSED_STRIP
|
||||
* @see #EVENT_END
|
||||
*/
|
||||
protected int next() throws IOException, ExifInvalidFormatException {
|
||||
if (!mContainExifData) {
|
||||
return EVENT_END;
|
||||
}
|
||||
int offset = mTiffStream.getReadByteCount();
|
||||
int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
|
||||
if (offset < endOfTags) {
|
||||
mTag = readTag();
|
||||
if (mTag == null) {
|
||||
return next();
|
||||
}
|
||||
if (mNeedToParseOffsetsInCurrentIfd) {
|
||||
checkOffsetOrImageTag(mTag);
|
||||
}
|
||||
return EVENT_NEW_TAG;
|
||||
} else if (offset == endOfTags) {
|
||||
// There is a link to ifd1 at the end of ifd0
|
||||
if (mIfdType == IfdId.TYPE_IFD_0) {
|
||||
long ifdOffset = readUnsignedLong();
|
||||
if (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested()) {
|
||||
if (ifdOffset != 0) {
|
||||
registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int offsetSize = 4;
|
||||
// Some camera models use invalid length of the offset
|
||||
if (mCorrespondingEvent.size() > 0) {
|
||||
offsetSize = mCorrespondingEvent.firstEntry().getKey() -
|
||||
mTiffStream.getReadByteCount();
|
||||
}
|
||||
if (offsetSize < 4) {
|
||||
Log.w(TAG, "Invalid size of link to next IFD: " + offsetSize);
|
||||
} else {
|
||||
long ifdOffset = readUnsignedLong();
|
||||
if (ifdOffset != 0) {
|
||||
Log.w(TAG, "Invalid link to next IFD: " + ifdOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (mCorrespondingEvent.size() != 0) {
|
||||
Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
|
||||
Object event = entry.getValue();
|
||||
try {
|
||||
skipTo(entry.getKey());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to skip to data at: " + entry.getKey() +
|
||||
" for " + event.getClass().getName() + ", the file may be broken.");
|
||||
continue;
|
||||
}
|
||||
if (event instanceof IfdEvent) {
|
||||
mIfdType = ((IfdEvent) event).ifd;
|
||||
mNumOfTagInIfd = mTiffStream.readUnsignedShort();
|
||||
mIfdStartOffset = entry.getKey();
|
||||
|
||||
if (mNumOfTagInIfd * TAG_SIZE + mIfdStartOffset + OFFSET_SIZE > mApp1End) {
|
||||
Log.w(TAG, "Invalid size of IFD " + mIfdType);
|
||||
return EVENT_END;
|
||||
}
|
||||
|
||||
mNeedToParseOffsetsInCurrentIfd = needToParseOffsetsInCurrentIfd();
|
||||
if (((IfdEvent) event).isRequested) {
|
||||
return EVENT_START_OF_IFD;
|
||||
} else {
|
||||
skipRemainingTagsInCurrentIfd();
|
||||
}
|
||||
} else if (event instanceof ImageEvent) {
|
||||
mImageEvent = (ImageEvent) event;
|
||||
return mImageEvent.type;
|
||||
} else {
|
||||
ExifTagEvent tagEvent = (ExifTagEvent) event;
|
||||
mTag = tagEvent.tag;
|
||||
if (mTag.getDataType() != ExifTag.TYPE_UNDEFINED) {
|
||||
readFullTagValue(mTag);
|
||||
checkOffsetOrImageTag(mTag);
|
||||
}
|
||||
if (tagEvent.isRequested) {
|
||||
return EVENT_VALUE_OF_REGISTERED_TAG;
|
||||
}
|
||||
}
|
||||
}
|
||||
return EVENT_END;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the tags area of current IFD, if the parser is not in the tag area,
|
||||
* nothing will happen.
|
||||
*
|
||||
* @throws IOException
|
||||
* @throws ExifInvalidFormatException
|
||||
*/
|
||||
protected void skipRemainingTagsInCurrentIfd() throws IOException, ExifInvalidFormatException {
|
||||
int endOfTags = mIfdStartOffset + OFFSET_SIZE + TAG_SIZE * mNumOfTagInIfd;
|
||||
int offset = mTiffStream.getReadByteCount();
|
||||
if (offset > endOfTags) {
|
||||
return;
|
||||
}
|
||||
if (mNeedToParseOffsetsInCurrentIfd) {
|
||||
while (offset < endOfTags) {
|
||||
mTag = readTag();
|
||||
offset += TAG_SIZE;
|
||||
if (mTag == null) {
|
||||
continue;
|
||||
}
|
||||
checkOffsetOrImageTag(mTag);
|
||||
}
|
||||
} else {
|
||||
skipTo(endOfTags);
|
||||
}
|
||||
long ifdOffset = readUnsignedLong();
|
||||
// For ifd0, there is a link to ifd1 in the end of all tags
|
||||
if (mIfdType == IfdId.TYPE_IFD_0
|
||||
&& (isIfdRequested(IfdId.TYPE_IFD_1) || isThumbnailRequested())) {
|
||||
if (ifdOffset > 0) {
|
||||
registerIfd(IfdId.TYPE_IFD_1, ifdOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needToParseOffsetsInCurrentIfd() {
|
||||
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_1);
|
||||
case IfdId.TYPE_IFD_1:
|
||||
return isThumbnailRequested();
|
||||
case IfdId.TYPE_IFD_EXIF:
|
||||
// The offset to interoperability IFD is located in Exif IFD
|
||||
return isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link #next()} return {@link #EVENT_NEW_TAG} or
|
||||
* {@link #EVENT_VALUE_OF_REGISTERED_TAG}, call this function to get the
|
||||
* corresponding tag.
|
||||
* <p>
|
||||
* For {@link #EVENT_NEW_TAG}, the tag may not contain the value if the size
|
||||
* of the value is greater than 4 bytes. One should call
|
||||
* {@link ExifTag#hasValue()} to check if the tag contains value. If there
|
||||
* is no value,call {@link #registerForTagValue(ExifTag)} to have the parser
|
||||
* emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
|
||||
* pointed by the offset.
|
||||
* <p>
|
||||
* When {@link #EVENT_VALUE_OF_REGISTERED_TAG} is emitted, the value of the
|
||||
* tag will have already been read except for tags of undefined type. For
|
||||
* tags of undefined type, call one of the read methods to get the value.
|
||||
*
|
||||
* @see #registerForTagValue(ExifTag)
|
||||
* @see #read(byte[])
|
||||
* @see #read(byte[], int, int)
|
||||
* @see #readLong()
|
||||
* @see #readRational()
|
||||
* @see #readString(int)
|
||||
* @see #readString(int, Charset)
|
||||
*/
|
||||
protected ExifTag getTag() {
|
||||
return mTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets number of tags in the current IFD area.
|
||||
*/
|
||||
protected int getTagCountInCurrentIfd() {
|
||||
return mNumOfTagInIfd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of current IFD.
|
||||
*
|
||||
* @see IfdId#TYPE_IFD_0
|
||||
* @see IfdId#TYPE_IFD_1
|
||||
* @see IfdId#TYPE_IFD_GPS
|
||||
* @see IfdId#TYPE_IFD_INTEROPERABILITY
|
||||
* @see IfdId#TYPE_IFD_EXIF
|
||||
*/
|
||||
protected int getCurrentIfd() {
|
||||
return mIfdType;
|
||||
}
|
||||
|
||||
/**
|
||||
* When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
|
||||
* get the index of this strip.
|
||||
*
|
||||
* @see #getStripCount()
|
||||
*/
|
||||
protected int getStripIndex() {
|
||||
return mImageEvent.stripIndex;
|
||||
}
|
||||
|
||||
/**
|
||||
* When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
|
||||
* get the number of strip data.
|
||||
*
|
||||
* @see #getStripIndex()
|
||||
*/
|
||||
protected int getStripCount() {
|
||||
return mStripCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* When receiving {@link #EVENT_UNCOMPRESSED_STRIP}, call this function to
|
||||
* get the strip size.
|
||||
*/
|
||||
protected int getStripSize() {
|
||||
if (mStripSizeTag == null)
|
||||
return 0;
|
||||
return (int) mStripSizeTag.getValueAt(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* When receiving {@link #EVENT_COMPRESSED_IMAGE}, call this function to get
|
||||
* the image data size.
|
||||
*/
|
||||
protected int getCompressedImageSize() {
|
||||
if (mJpegSizeTag == null) {
|
||||
return 0;
|
||||
}
|
||||
return (int) mJpegSizeTag.getValueAt(0);
|
||||
}
|
||||
|
||||
private void skipTo(int offset) throws IOException {
|
||||
mTiffStream.skipTo(offset);
|
||||
while (!mCorrespondingEvent.isEmpty() && mCorrespondingEvent.firstKey() < offset) {
|
||||
mCorrespondingEvent.pollFirstEntry();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When getting {@link #EVENT_NEW_TAG} in the tag area of IFD, the tag may
|
||||
* not contain the value if the size of the value is greater than 4 bytes.
|
||||
* When the value is not available here, call this method so that the parser
|
||||
* will emit {@link #EVENT_VALUE_OF_REGISTERED_TAG} when it reaches the area
|
||||
* where the value is located.
|
||||
*
|
||||
* @see #EVENT_VALUE_OF_REGISTERED_TAG
|
||||
*/
|
||||
protected void registerForTagValue(ExifTag tag) {
|
||||
if (tag.getOffset() >= mTiffStream.getReadByteCount()) {
|
||||
mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, true));
|
||||
}
|
||||
}
|
||||
|
||||
private void registerIfd(int ifdType, long offset) {
|
||||
// Cast unsigned int to int since the offset is always smaller
|
||||
// than the size of APP1 (65536)
|
||||
mCorrespondingEvent.put((int) offset, new IfdEvent(ifdType, isIfdRequested(ifdType)));
|
||||
}
|
||||
|
||||
private void registerCompressedImage(long offset) {
|
||||
mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_COMPRESSED_IMAGE));
|
||||
}
|
||||
|
||||
private void registerUncompressedStrip(int stripIndex, long offset) {
|
||||
mCorrespondingEvent.put((int) offset, new ImageEvent(EVENT_UNCOMPRESSED_STRIP
|
||||
, stripIndex));
|
||||
}
|
||||
|
||||
private 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");
|
||||
}
|
||||
// Some invalid image file contains invalid data type. Ignore those tags
|
||||
if (!ExifTag.isValidType(dataFormat)) {
|
||||
Log.w(TAG, String.format("Tag %04x: Invalid data type %d", tagId, dataFormat));
|
||||
mTiffStream.skip(4);
|
||||
return null;
|
||||
}
|
||||
// TODO: handle numOfComp overflow
|
||||
ExifTag tag = new ExifTag(tagId, dataFormat, (int) numOfComp, mIfdType,
|
||||
((int) numOfComp) != ExifTag.SIZE_UNDEFINED);
|
||||
int dataSize = tag.getDataSize();
|
||||
if (dataSize > 4) {
|
||||
long offset = mTiffStream.readUnsignedInt();
|
||||
if (offset > Integer.MAX_VALUE) {
|
||||
throw new ExifInvalidFormatException(
|
||||
"offset is larger then Integer.MAX_VALUE");
|
||||
}
|
||||
// Some invalid images put some undefined data before IFD0.
|
||||
// Read the data here.
|
||||
if ((offset < mIfd0Position) && (dataFormat == ExifTag.TYPE_UNDEFINED)) {
|
||||
byte[] buf = new byte[(int) numOfComp];
|
||||
System.arraycopy(mDataAboveIfd0, (int) offset - DEFAULT_IFD0_OFFSET,
|
||||
buf, 0, (int) numOfComp);
|
||||
tag.setValue(buf);
|
||||
} else {
|
||||
tag.setOffset((int) offset);
|
||||
}
|
||||
} else {
|
||||
boolean defCount = tag.hasDefinedCount();
|
||||
// Set defined count to 0 so we can add \0 to non-terminated strings
|
||||
tag.setHasDefinedCount(false);
|
||||
// Read value
|
||||
readFullTagValue(tag);
|
||||
tag.setHasDefinedCount(defCount);
|
||||
mTiffStream.skip(4 - dataSize);
|
||||
// Set the offset to the position of value.
|
||||
tag.setOffset(mTiffStream.getReadByteCount() - 4);
|
||||
}
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the tag, if the tag is one of the offset tag that points to the IFD
|
||||
* or image the caller is interested in, register the IFD or image.
|
||||
*/
|
||||
private void checkOffsetOrImageTag(ExifTag tag) {
|
||||
// Some invalid formattd image contains tag with 0 size.
|
||||
if (tag.getComponentCount() == 0) {
|
||||
return;
|
||||
}
|
||||
short tid = tag.getTagId();
|
||||
int ifd = tag.getIfd();
|
||||
if (tid == TAG_EXIF_IFD && checkAllowed(ifd, ExifInterface.TAG_EXIF_IFD)) {
|
||||
if (isIfdRequested(IfdId.TYPE_IFD_EXIF)
|
||||
|| isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
|
||||
registerIfd(IfdId.TYPE_IFD_EXIF, tag.getValueAt(0));
|
||||
}
|
||||
} else if (tid == TAG_GPS_IFD && checkAllowed(ifd, ExifInterface.TAG_GPS_IFD)) {
|
||||
if (isIfdRequested(IfdId.TYPE_IFD_GPS)) {
|
||||
registerIfd(IfdId.TYPE_IFD_GPS, tag.getValueAt(0));
|
||||
}
|
||||
} else if (tid == TAG_INTEROPERABILITY_IFD
|
||||
&& checkAllowed(ifd, ExifInterface.TAG_INTEROPERABILITY_IFD)) {
|
||||
if (isIfdRequested(IfdId.TYPE_IFD_INTEROPERABILITY)) {
|
||||
registerIfd(IfdId.TYPE_IFD_INTEROPERABILITY, tag.getValueAt(0));
|
||||
}
|
||||
} else if (tid == TAG_JPEG_INTERCHANGE_FORMAT
|
||||
&& checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT)) {
|
||||
if (isThumbnailRequested()) {
|
||||
registerCompressedImage(tag.getValueAt(0));
|
||||
}
|
||||
} else if (tid == TAG_JPEG_INTERCHANGE_FORMAT_LENGTH
|
||||
&& checkAllowed(ifd, ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH)) {
|
||||
if (isThumbnailRequested()) {
|
||||
mJpegSizeTag = tag;
|
||||
}
|
||||
} else if (tid == TAG_STRIP_OFFSETS && checkAllowed(ifd, ExifInterface.TAG_STRIP_OFFSETS)) {
|
||||
if (isThumbnailRequested()) {
|
||||
if (tag.hasValue()) {
|
||||
for (int i = 0; i < tag.getComponentCount(); i++) {
|
||||
if (tag.getDataType() == ExifTag.TYPE_UNSIGNED_SHORT) {
|
||||
registerUncompressedStrip(i, tag.getValueAt(i));
|
||||
} else {
|
||||
registerUncompressedStrip(i, tag.getValueAt(i));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mCorrespondingEvent.put(tag.getOffset(), new ExifTagEvent(tag, false));
|
||||
}
|
||||
}
|
||||
} else if (tid == TAG_STRIP_BYTE_COUNTS
|
||||
&& checkAllowed(ifd, ExifInterface.TAG_STRIP_BYTE_COUNTS)
|
||||
&&isThumbnailRequested() && tag.hasValue()) {
|
||||
mStripSizeTag = tag;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkAllowed(int ifd, int tagId) {
|
||||
int info = mInterface.getTagInfo().get(tagId);
|
||||
if (info == ExifInterface.DEFINITION_NULL) {
|
||||
return false;
|
||||
}
|
||||
return ExifInterface.isIfdAllowed(info, ifd);
|
||||
}
|
||||
|
||||
protected void readFullTagValue(ExifTag tag) throws IOException {
|
||||
// Some invalid images contains tags with wrong size, check it here
|
||||
short type = tag.getDataType();
|
||||
if (type == ExifTag.TYPE_ASCII || type == ExifTag.TYPE_UNDEFINED ||
|
||||
type == ExifTag.TYPE_UNSIGNED_BYTE) {
|
||||
int size = tag.getComponentCount();
|
||||
if (mCorrespondingEvent.size() > 0) {
|
||||
if (mCorrespondingEvent.firstEntry().getKey() < mTiffStream.getReadByteCount()
|
||||
+ size) {
|
||||
Object event = mCorrespondingEvent.firstEntry().getValue();
|
||||
if (event instanceof ImageEvent) {
|
||||
// Tag value overlaps thumbnail, ignore thumbnail.
|
||||
Log.w(TAG, "Thumbnail overlaps value for tag: \n" + tag.toString());
|
||||
Entry<Integer, Object> entry = mCorrespondingEvent.pollFirstEntry();
|
||||
Log.w(TAG, "Invalid thumbnail offset: " + entry.getKey());
|
||||
} else {
|
||||
// Tag value overlaps another tag, shorten count
|
||||
if (event instanceof IfdEvent) {
|
||||
Log.w(TAG, "Ifd " + ((IfdEvent) event).ifd
|
||||
+ " overlaps value for tag: \n" + tag.toString());
|
||||
} else if (event instanceof ExifTagEvent) {
|
||||
Log.w(TAG, "Tag value for tag: \n"
|
||||
+ ((ExifTagEvent) event).tag.toString()
|
||||
+ " overlaps value for tag: \n" + tag.toString());
|
||||
}
|
||||
size = mCorrespondingEvent.firstEntry().getKey()
|
||||
- mTiffStream.getReadByteCount();
|
||||
Log.w(TAG, "Invalid size of tag: \n" + tag.toString()
|
||||
+ " setting count to: " + size);
|
||||
tag.forceSetComponentCount(size);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (tag.getDataType()) {
|
||||
case ExifTag.TYPE_UNSIGNED_BYTE:
|
||||
case ExifTag.TYPE_UNDEFINED: {
|
||||
byte buf[] = new byte[tag.getComponentCount()];
|
||||
read(buf);
|
||||
tag.setValue(buf);
|
||||
}
|
||||
break;
|
||||
case ExifTag.TYPE_ASCII:
|
||||
tag.setValue(readString(tag.getComponentCount()));
|
||||
break;
|
||||
case ExifTag.TYPE_UNSIGNED_LONG: {
|
||||
long value[] = new long[tag.getComponentCount()];
|
||||
for (int i = 0, n = value.length; i < n; i++) {
|
||||
value[i] = readUnsignedLong();
|
||||
}
|
||||
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;
|
||||
}
|
||||
if (LOGV) {
|
||||
Log.v(TAG, "\n" + tag.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseTiffHeader() throws IOException,
|
||||
ExifInvalidFormatException {
|
||||
short byteOrder = mTiffStream.readShort();
|
||||
if (LITTLE_ENDIAN_TAG == byteOrder) {
|
||||
mTiffStream.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||
} else if (BIG_ENDIAN_TAG == byteOrder) {
|
||||
mTiffStream.setByteOrder(ByteOrder.BIG_ENDIAN);
|
||||
} else {
|
||||
throw new ExifInvalidFormatException("Invalid TIFF header");
|
||||
}
|
||||
|
||||
if (mTiffStream.readShort() != TIFF_HEADER_TAIL) {
|
||||
throw new ExifInvalidFormatException("Invalid TIFF header");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean seekTiffData(InputStream inputStream) throws IOException,
|
||||
ExifInvalidFormatException {
|
||||
CountedDataInputStream dataStream = new CountedDataInputStream(inputStream);
|
||||
if (dataStream.readShort() != JpegHeader.SOI) {
|
||||
throw new ExifInvalidFormatException("Invalid JPEG format");
|
||||
}
|
||||
|
||||
short marker = dataStream.readShort();
|
||||
while (marker != JpegHeader.EOI
|
||||
&& !JpegHeader.isSofMarker(marker)) {
|
||||
int length = dataStream.readUnsignedShort();
|
||||
// Some invalid formatted image contains multiple APP1,
|
||||
// try to find the one with Exif data.
|
||||
if (marker == JpegHeader.APP1) {
|
||||
int header = 0;
|
||||
short headerTail = 0;
|
||||
if (length >= 8) {
|
||||
header = dataStream.readInt();
|
||||
headerTail = dataStream.readShort();
|
||||
length -= 6;
|
||||
if (header == EXIF_HEADER && headerTail == EXIF_HEADER_TAIL) {
|
||||
mTiffStartPosition = dataStream.getReadByteCount();
|
||||
mApp1End = length;
|
||||
mOffsetToApp1EndFromSOF = mTiffStartPosition + mApp1End;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length < 2 || (length - 2) != dataStream.skip(length - 2)) {
|
||||
Log.w(TAG, "Invalid JPEG format.");
|
||||
return false;
|
||||
}
|
||||
marker = dataStream.readShort();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected int getOffsetToExifEndFromSOF() {
|
||||
return mOffsetToApp1EndFromSOF;
|
||||
}
|
||||
|
||||
protected int getTiffStartPosition() {
|
||||
return mTiffStartPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads bytes from the InputStream.
|
||||
*/
|
||||
protected int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
return mTiffStream.read(buffer, offset, length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to read(buffer, 0, buffer.length).
|
||||
*/
|
||||
protected int read(byte[] buffer) throws IOException {
|
||||
return mTiffStream.read(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a String from the InputStream with US-ASCII charset. The parser
|
||||
* will read n bytes and convert it to ascii string. This is used for
|
||||
* reading values of type {@link ExifTag#TYPE_ASCII}.
|
||||
*/
|
||||
protected String readString(int n) throws IOException {
|
||||
return readString(n, US_ASCII);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a String from the InputStream with the given charset. The parser
|
||||
* will read n bytes and convert it to string. This is used for reading
|
||||
* values of type {@link ExifTag#TYPE_ASCII}.
|
||||
*/
|
||||
protected String readString(int n, Charset charset) throws IOException {
|
||||
if (n > 0) {
|
||||
return mTiffStream.readString(n, charset);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads value of type {@link ExifTag#TYPE_UNSIGNED_SHORT} from the
|
||||
* InputStream.
|
||||
*/
|
||||
protected int readUnsignedShort() throws IOException {
|
||||
return mTiffStream.readShort() & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads value of type {@link ExifTag#TYPE_UNSIGNED_LONG} from the
|
||||
* InputStream.
|
||||
*/
|
||||
protected long readUnsignedLong() throws IOException {
|
||||
return readLong() & 0xffffffffL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads value of type {@link ExifTag#TYPE_UNSIGNED_RATIONAL} from the
|
||||
* InputStream.
|
||||
*/
|
||||
protected Rational readUnsignedRational() throws IOException {
|
||||
long nomi = readUnsignedLong();
|
||||
long denomi = readUnsignedLong();
|
||||
return new Rational(nomi, denomi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads value of type {@link ExifTag#TYPE_LONG} from the InputStream.
|
||||
*/
|
||||
protected int readLong() throws IOException {
|
||||
return mTiffStream.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads value of type {@link ExifTag#TYPE_RATIONAL} from the InputStream.
|
||||
*/
|
||||
protected Rational readRational() throws IOException {
|
||||
int nomi = readLong();
|
||||
int denomi = readLong();
|
||||
return new Rational(nomi, denomi);
|
||||
}
|
||||
|
||||
private static class ImageEvent {
|
||||
int stripIndex;
|
||||
int type;
|
||||
|
||||
ImageEvent(int type) {
|
||||
this.stripIndex = 0;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
ImageEvent(int type, int stripIndex) {
|
||||
this.type = type;
|
||||
this.stripIndex = stripIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private static class IfdEvent {
|
||||
int ifd;
|
||||
boolean isRequested;
|
||||
|
||||
IfdEvent(int ifd, boolean isInterestedIfd) {
|
||||
this.ifd = ifd;
|
||||
this.isRequested = isInterestedIfd;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ExifTagEvent {
|
||||
ExifTag tag;
|
||||
boolean isRequested;
|
||||
|
||||
ExifTagEvent(ExifTag tag, boolean isRequireByUser) {
|
||||
this.tag = tag;
|
||||
this.isRequested = isRequireByUser;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the byte order of the current InputStream.
|
||||
*/
|
||||
protected ByteOrder getByteOrder() {
|
||||
return mTiffStream.getByteOrder();
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* 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 android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* This class reads the EXIF header of a JPEG file and stores it in
|
||||
* {@link ExifData}.
|
||||
*/
|
||||
class ExifReader {
|
||||
private static final String TAG = "ExifReader";
|
||||
|
||||
private final ExifInterface mInterface;
|
||||
|
||||
ExifReader(ExifInterface iRef) {
|
||||
mInterface = iRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the inputStream and and returns the EXIF data in an
|
||||
* {@link ExifData}.
|
||||
*
|
||||
* @throws ExifInvalidFormatException
|
||||
* @throws IOException
|
||||
*/
|
||||
protected ExifData read(InputStream inputStream) throws ExifInvalidFormatException,
|
||||
IOException {
|
||||
ExifParser parser = ExifParser.parse(inputStream, mInterface);
|
||||
ExifData exifData = new ExifData(parser.getByteOrder());
|
||||
ExifTag tag = null;
|
||||
|
||||
int event = parser.next();
|
||||
while (event != ExifParser.EVENT_END) {
|
||||
switch (event) {
|
||||
case ExifParser.EVENT_START_OF_IFD:
|
||||
exifData.addIfdData(new IfdData(parser.getCurrentIfd()));
|
||||
break;
|
||||
case ExifParser.EVENT_NEW_TAG:
|
||||
tag = parser.getTag();
|
||||
if (!tag.hasValue()) {
|
||||
parser.registerForTagValue(tag);
|
||||
} else {
|
||||
exifData.getIfdData(tag.getIfd()).setTag(tag);
|
||||
}
|
||||
break;
|
||||
case ExifParser.EVENT_VALUE_OF_REGISTERED_TAG:
|
||||
tag = parser.getTag();
|
||||
if (tag.getDataType() == ExifTag.TYPE_UNDEFINED) {
|
||||
parser.readFullTagValue(tag);
|
||||
}
|
||||
exifData.getIfdData(tag.getIfd()).setTag(tag);
|
||||
break;
|
||||
case ExifParser.EVENT_COMPRESSED_IMAGE:
|
||||
byte buf[] = new byte[parser.getCompressedImageSize()];
|
||||
if (buf.length == parser.read(buf)) {
|
||||
exifData.setCompressedThumbnail(buf);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to read the compressed thumbnail");
|
||||
}
|
||||
break;
|
||||
case ExifParser.EVENT_UNCOMPRESSED_STRIP:
|
||||
buf = new byte[parser.getStripSize()];
|
||||
if (buf.length == parser.read(buf)) {
|
||||
exifData.setStripBytes(parser.getStripIndex(), buf);
|
||||
} else {
|
||||
Log.w(TAG, "Failed to read the strip bytes");
|
||||
}
|
||||
break;
|
||||
}
|
||||
event = parser.next();
|
||||
}
|
||||
return exifData;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* 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.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class stores all the tags in an IFD.
|
||||
*
|
||||
* @see ExifData
|
||||
* @see ExifTag
|
||||
*/
|
||||
class IfdData {
|
||||
|
||||
private final int mIfdId;
|
||||
private final Map<Short, ExifTag> mExifTags = new HashMap<Short, ExifTag>();
|
||||
private int mOffsetToNextIfd = 0;
|
||||
private static final int[] sIfds = {
|
||||
IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1, IfdId.TYPE_IFD_EXIF,
|
||||
IfdId.TYPE_IFD_INTEROPERABILITY, IfdId.TYPE_IFD_GPS
|
||||
};
|
||||
/**
|
||||
* Creates an IfdData with given IFD ID.
|
||||
*
|
||||
* @see IfdId#TYPE_IFD_0
|
||||
* @see IfdId#TYPE_IFD_1
|
||||
* @see IfdId#TYPE_IFD_EXIF
|
||||
* @see IfdId#TYPE_IFD_GPS
|
||||
* @see IfdId#TYPE_IFD_INTEROPERABILITY
|
||||
*/
|
||||
IfdData(int ifdId) {
|
||||
mIfdId = ifdId;
|
||||
}
|
||||
|
||||
static protected int[] getIfds() {
|
||||
return sIfds;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a array the contains all {@link ExifTag} in this IFD.
|
||||
*/
|
||||
protected ExifTag[] getAllTags() {
|
||||
return mExifTags.values().toArray(new ExifTag[mExifTags.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of this IFD.
|
||||
*
|
||||
* @see IfdId#TYPE_IFD_0
|
||||
* @see IfdId#TYPE_IFD_1
|
||||
* @see IfdId#TYPE_IFD_EXIF
|
||||
* @see IfdId#TYPE_IFD_GPS
|
||||
* @see IfdId#TYPE_IFD_INTEROPERABILITY
|
||||
*/
|
||||
protected int getId() {
|
||||
return mIfdId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link ExifTag} with given tag id. Return null if there is no
|
||||
* such tag.
|
||||
*/
|
||||
protected ExifTag getTag(short tagId) {
|
||||
return mExifTags.get(tagId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds or replaces a {@link ExifTag}.
|
||||
*/
|
||||
protected ExifTag setTag(ExifTag tag) {
|
||||
tag.setIfd(mIfdId);
|
||||
return mExifTags.put(tag.getTagId(), tag);
|
||||
}
|
||||
|
||||
protected boolean checkCollision(short tagId) {
|
||||
return mExifTags.get(tagId) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the tag of the given ID
|
||||
*/
|
||||
protected void removeTag(short tagId) {
|
||||
mExifTags.remove(tagId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the tags count in the IFD.
|
||||
*/
|
||||
protected int getTagCount() {
|
||||
return mExifTags.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset of next IFD.
|
||||
*/
|
||||
protected void setOffsetToNextIfd(int offset) {
|
||||
mOffsetToNextIfd = offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the offset of next IFD.
|
||||
*/
|
||||
protected int getOffsetToNextIfd() {
|
||||
return mOffsetToNextIfd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if all tags in this two IFDs are equal. Note that tags of
|
||||
* IFDs offset or thumbnail offset will be ignored.
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (obj instanceof IfdData) {
|
||||
IfdData data = (IfdData) obj;
|
||||
if (data.getId() == mIfdId && data.getTagCount() == getTagCount()) {
|
||||
ExifTag[] tags = data.getAllTags();
|
||||
for (ExifTag tag : tags) {
|
||||
if (ExifInterface.isOffsetTag(tag.getTagId())) {
|
||||
continue;
|
||||
}
|
||||
ExifTag tag2 = mExifTags.get(tag.getTagId());
|
||||
if (!tag.equals(tag2)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The constants of the IFD ID defined in EXIF spec.
|
||||
*/
|
||||
public interface IfdId {
|
||||
public static final int TYPE_IFD_0 = 0;
|
||||
public static final int TYPE_IFD_1 = 1;
|
||||
public static final int TYPE_IFD_EXIF = 2;
|
||||
public static final int TYPE_IFD_INTEROPERABILITY = 3;
|
||||
public static final int TYPE_IFD_GPS = 4;
|
||||
/* This is used in ExifData to allocate enough IfdData */
|
||||
static final int TYPE_IFD_COUNT = 5;
|
||||
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
class JpegHeader {
|
||||
public static final short SOI = (short) 0xFFD8;
|
||||
public static final short APP1 = (short) 0xFFE1;
|
||||
public static final short APP0 = (short) 0xFFE0;
|
||||
public static final short EOI = (short) 0xFFD9;
|
||||
|
||||
/**
|
||||
* SOF (start of frame). All value between SOF0 and SOF15 is SOF marker except for DHT, JPG,
|
||||
* and DAC marker.
|
||||
*/
|
||||
public static final short SOF0 = (short) 0xFFC0;
|
||||
public static final short SOF15 = (short) 0xFFCF;
|
||||
public static final short DHT = (short) 0xFFC4;
|
||||
public static final short JPG = (short) 0xFFC8;
|
||||
public static final short DAC = (short) 0xFFCC;
|
||||
|
||||
public static final boolean isSofMarker(short marker) {
|
||||
return marker >= SOF0 && marker <= SOF15 && marker != DHT && marker != JPG
|
||||
&& marker != DAC;
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
class OrderedDataOutputStream extends FilterOutputStream {
|
||||
private final ByteBuffer mByteBuffer = ByteBuffer.allocate(4);
|
||||
|
||||
public OrderedDataOutputStream(OutputStream out) {
|
||||
super(out);
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream setByteOrder(ByteOrder order) {
|
||||
mByteBuffer.order(order);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream writeShort(short value) throws IOException {
|
||||
mByteBuffer.rewind();
|
||||
mByteBuffer.putShort(value);
|
||||
out.write(mByteBuffer.array(), 0, 2);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream writeInt(int value) throws IOException {
|
||||
mByteBuffer.rewind();
|
||||
mByteBuffer.putInt(value);
|
||||
out.write(mByteBuffer.array());
|
||||
return this;
|
||||
}
|
||||
|
||||
public OrderedDataOutputStream writeRational(Rational rational) throws IOException {
|
||||
writeInt((int) rational.getNumerator());
|
||||
writeInt((int) rational.getDenominator());
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The rational data type of EXIF tag. Contains a pair of longs representing the
|
||||
* numerator and denominator of a Rational number.
|
||||
*/
|
||||
public class Rational {
|
||||
|
||||
private final long mNumerator;
|
||||
private final long mDenominator;
|
||||
|
||||
/**
|
||||
* Create a Rational with a given numerator and denominator.
|
||||
*
|
||||
* @param nominator
|
||||
* @param denominator
|
||||
*/
|
||||
public Rational(long nominator, long denominator) {
|
||||
mNumerator = nominator;
|
||||
mDenominator = denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a copy of a Rational.
|
||||
*/
|
||||
public Rational(Rational r) {
|
||||
mNumerator = r.mNumerator;
|
||||
mDenominator = r.mDenominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the numerator of the rational.
|
||||
*/
|
||||
public long getNumerator() {
|
||||
return mNumerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the denominator of the rational
|
||||
*/
|
||||
public long getDenominator() {
|
||||
return mDenominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the rational value as type double. Will cause a divide-by-zero error
|
||||
* if the denominator is 0.
|
||||
*/
|
||||
public double toDouble() {
|
||||
return mNumerator / (double) mDenominator;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj instanceof Rational) {
|
||||
Rational data = (Rational) obj;
|
||||
return mNumerator == data.mNumerator && mDenominator == data.mDenominator;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mNumerator + "/" + mDenominator;
|
||||
}
|
||||
}
|
||||
@@ -318,7 +318,7 @@ public class WallpaperCropActivity extends BaseActivity implements Handler.Callb
|
||||
Resources res, int resId, final boolean finishActivityWhenDone) {
|
||||
// crop this image and scale it down to the default wallpaper size for
|
||||
// this device
|
||||
int rotation = BitmapUtils.getRotationFromExif(res, resId);
|
||||
int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
|
||||
Point inSize = mCropView.getSourceDimensions();
|
||||
Point outSize = WallpaperUtils.getDefaultWallpaperSize(getResources(),
|
||||
getWindowManager());
|
||||
|
||||
@@ -200,8 +200,8 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
@Override
|
||||
public void onClick(final WallpaperPickerActivity a) {
|
||||
a.setWallpaperButtonEnabled(false);
|
||||
final BitmapRegionTileSource.UriBitmapSource bitmapSource =
|
||||
new BitmapRegionTileSource.UriBitmapSource(a.getContext(), Uri.fromFile(mFile));
|
||||
final BitmapRegionTileSource.FilePathBitmapSource bitmapSource =
|
||||
new BitmapRegionTileSource.FilePathBitmapSource(mFile.getAbsolutePath());
|
||||
a.setCropViewTileSource(bitmapSource, false, true, null, new Runnable() {
|
||||
|
||||
@Override
|
||||
@@ -239,7 +239,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
public void onClick(final WallpaperPickerActivity a) {
|
||||
a.setWallpaperButtonEnabled(false);
|
||||
final BitmapRegionTileSource.ResourceBitmapSource bitmapSource =
|
||||
new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId);
|
||||
new BitmapRegionTileSource.ResourceBitmapSource(mResources, mResId, a);
|
||||
a.setCropViewTileSource(bitmapSource, false, false, new CropViewScaleProvider() {
|
||||
|
||||
@Override
|
||||
@@ -1030,7 +1030,7 @@ public class WallpaperPickerActivity extends WallpaperCropActivity {
|
||||
} else {
|
||||
Resources res = getResources();
|
||||
Point defaultThumbSize = getDefaultThumbnailSize(res);
|
||||
int rotation = BitmapUtils.getRotationFromExif(res, resId);
|
||||
int rotation = BitmapUtils.getRotationFromExif(res, resId, this);
|
||||
thumb = createThumbnail(
|
||||
defaultThumbSize, getContext(), null, null, sysRes, resId, rotation, false);
|
||||
if (thumb != null) {
|
||||
|
||||
@@ -25,14 +25,15 @@ import android.graphics.BitmapRegionDecoder;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.opengl.GLUtils;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.gallery3d.common.BitmapUtils;
|
||||
import com.android.gallery3d.common.ExifOrientation;
|
||||
import com.android.gallery3d.common.Utils;
|
||||
import com.android.gallery3d.exif.ExifInterface;
|
||||
import com.android.gallery3d.glrenderer.BasicTexture;
|
||||
import com.android.gallery3d.glrenderer.BitmapTexture;
|
||||
import com.android.photos.views.TiledImageRenderer;
|
||||
@@ -160,13 +161,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
private State mState = State.NOT_LOADED;
|
||||
|
||||
public boolean loadInBackground(InBitmapProvider bitmapProvider) {
|
||||
ExifInterface ei = new ExifInterface();
|
||||
if (readExif(ei)) {
|
||||
Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
|
||||
if (ori != null) {
|
||||
mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue());
|
||||
}
|
||||
}
|
||||
mRotation = getExifRotation();
|
||||
mDecoder = loadBitmapRegionDecoder();
|
||||
if (mDecoder == null) {
|
||||
mState = State.ERROR_LOADING;
|
||||
@@ -232,7 +227,7 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
public abstract boolean readExif(ExifInterface ei);
|
||||
public abstract int getExifRotation();
|
||||
public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder();
|
||||
public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options);
|
||||
|
||||
@@ -259,18 +254,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
|
||||
return BitmapFactory.decodeFile(mPath, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readExif(ExifInterface ei) {
|
||||
try {
|
||||
ei.readExif(mPath);
|
||||
return true;
|
||||
} catch (NullPointerException e) {
|
||||
Log.w("BitmapRegionTileSource", "reading exif failed", e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.w("BitmapRegionTileSource", "getting decoder failed", e);
|
||||
return false;
|
||||
}
|
||||
public int getExifRotation() {
|
||||
return ExifOrientation.readRotation(mPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,35 +302,22 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readExif(ExifInterface ei) {
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = regenerateInputStream();
|
||||
ei.readExif(is);
|
||||
Utils.closeSilently(is);
|
||||
return true;
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.d("BitmapRegionTileSource", "Failed to load URI " + mUri, e);
|
||||
return false;
|
||||
} catch (NullPointerException e) {
|
||||
Log.d("BitmapRegionTileSource", "Failed to read EXIF for URI " + mUri, e);
|
||||
return false;
|
||||
} finally {
|
||||
Utils.closeSilently(is);
|
||||
}
|
||||
public int getExifRotation() {
|
||||
return BitmapUtils.getRotationFromExif(mContext, mUri);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourceBitmapSource extends BitmapSource {
|
||||
private Resources mRes;
|
||||
private int mResId;
|
||||
public ResourceBitmapSource(Resources res, int resId) {
|
||||
private Context mContext;
|
||||
|
||||
public ResourceBitmapSource(Resources res, int resId, Context context) {
|
||||
mRes = res;
|
||||
mResId = resId;
|
||||
mContext = context;
|
||||
}
|
||||
private InputStream regenerateInputStream() {
|
||||
InputStream is = mRes.openRawResource(mResId);
|
||||
@@ -366,17 +340,10 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource {
|
||||
public Bitmap loadPreviewBitmap(BitmapFactory.Options options) {
|
||||
return BitmapFactory.decodeResource(mRes, mResId, options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readExif(ExifInterface ei) {
|
||||
try {
|
||||
InputStream is = regenerateInputStream();
|
||||
ei.readExif(is);
|
||||
Utils.closeSilently(is);
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Log.e("BitmapRegionTileSource", "Error reading resource", e);
|
||||
return false;
|
||||
}
|
||||
public int getExifRotation() {
|
||||
return BitmapUtils.getRotationFromExif(mRes, mResId, mContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user