Merge "ExifInterface: add RAW input stream support" into nyc-dev

This commit is contained in:
Jaesung Chung
2016-03-09 08:23:04 +00:00
committed by Android (Google) Code Review
9 changed files with 461 additions and 180 deletions

View File

@@ -16,6 +16,8 @@
package android.media;
import android.annotation.NonNull;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.system.ErrnoException;
@@ -24,6 +26,7 @@ import android.system.OsConstants;
import android.util.Log;
import android.util.Pair;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -145,6 +148,7 @@ public class ExifInterface {
private static final String TAG_HAS_THUMBNAIL = "hasThumbnail";
private static final String TAG_THUMBNAIL_OFFSET = "thumbnailOffset";
private static final String TAG_THUMBNAIL_LENGTH = "thumbnailLength";
private static final String TAG_THUMBNAIL_DATA = "thumbnailData";
// Constants used for the Orientation Exif tag.
public static final int ORIENTATION_UNDEFINED = 0;
@@ -163,6 +167,9 @@ public class ExifInterface {
public static final int WHITEBALANCE_AUTO = 0;
public static final int WHITEBALANCE_MANUAL = 1;
private static final byte[] JPEG_SIGNATURE = new byte[] {(byte) 0xff, (byte) 0xd8, (byte) 0xff};
private static final int JPEG_SIGNATURE_SIZE = 3;
private static SimpleDateFormat sFormatter;
// See Exchangeable image file format for digital still cameras: Exif version 2.2.
@@ -408,8 +415,8 @@ public class ExifInterface {
// Mappings from tag number to tag name and each item represents one IFD tag group.
private static final HashMap[] sExifTagMapsForReading = new HashMap[EXIF_TAGS.length];
// Mapping from tag name to tag number and the corresponding tag group.
private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting
= new HashMap<>();
private static final HashMap<String, Pair<Integer, Integer>> sExifTagMapForWriting =
new HashMap<>();
// See JPEG File Interchange Format Version 1.02.
// The following values are defined for handling JPEG streams. In this implementation, we are
@@ -443,7 +450,7 @@ public class ExifInterface {
static {
System.loadLibrary("media_jni");
initRawNative();
nativeInitRaw();
sFormatter = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss");
sFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
@@ -468,10 +475,10 @@ public class ExifInterface {
}
private final String mFilename;
private final FileDescriptor mFileDescriptor;
private final InputStream mInputStream;
private boolean mIsRaw;
private final FileDescriptor mSeekableFileDescriptor;
private final AssetManager.AssetInputStream mAssetInputStream;
private final HashMap<String, String> mAttributes = new HashMap<>();
private boolean mIsRaw;
private boolean mHasThumbnail;
// The following values used for indicating a thumbnail position.
private int mThumbnailOffset;
@@ -488,23 +495,33 @@ public class ExifInterface {
if (filename == null) {
throw new IllegalArgumentException("filename cannot be null");
}
FileInputStream in = new FileInputStream(filename);
mAssetInputStream = null;
mFilename = filename;
mFileDescriptor = null;
mInputStream = new FileInputStream(filename);
loadAttributes();
if (isSeekableFD(in.getFD())) {
mSeekableFileDescriptor = in.getFD();
} else {
mSeekableFileDescriptor = null;
}
loadAttributes(in);
}
/**
* Reads Exif tags from the specified image file descriptor.
* Reads Exif tags from the specified image file descriptor. Attribute mutation is supported
* for seekable file descriptors only.
*/
public ExifInterface(FileDescriptor fileDescriptor) throws IOException {
if (fileDescriptor == null) {
throw new IllegalArgumentException("parcelFileDescriptor cannot be null");
}
mAssetInputStream = null;
mFilename = null;
mFileDescriptor = fileDescriptor;
mInputStream = new FileInputStream(fileDescriptor);
loadAttributes();
if (isSeekableFD(fileDescriptor)) {
mSeekableFileDescriptor = fileDescriptor;
} else {
mSeekableFileDescriptor = null;
}
loadAttributes(new FileInputStream(fileDescriptor));
}
/**
@@ -516,9 +533,18 @@ public class ExifInterface {
throw new IllegalArgumentException("inputStream cannot be null");
}
mFilename = null;
mFileDescriptor = null;
mInputStream = inputStream;
loadAttributes();
if (inputStream instanceof AssetManager.AssetInputStream) {
mAssetInputStream = (AssetManager.AssetInputStream) inputStream;
mSeekableFileDescriptor = null;
} else if (inputStream instanceof FileInputStream
&& isSeekableFD(((FileInputStream) inputStream).getFD())) {
mAssetInputStream = null;
mSeekableFileDescriptor = ((FileInputStream) inputStream).getFD();
} else {
mAssetInputStream = null;
mSeekableFileDescriptor = null;
}
loadAttributes(inputStream);
}
/**
@@ -587,77 +613,93 @@ public class ExifInterface {
}
/**
* Initialize mAttributes with the attributes from the file mFilename.
*
* mAttributes is a HashMap which stores the Exif attributes of the file.
* The key is the standard tag name and the value is the tag's value: e.g.
* Model -&gt; Nikon. Numeric values are stored as strings.
*
* This function also initialize mHasThumbnail to indicate whether the
* file has a thumbnail inside.
* This function decides which parser to read the image data according to the given input stream
* type and the content of the input stream. In each case, it reads the first three bytes to
* determine whether the image data format is JPEG or not.
*/
private void loadAttributes() throws IOException {
FileInputStream in = null;
try {
if (mFilename != null) {
in = new FileInputStream(mFilename);
private void loadAttributes(@NonNull InputStream in) throws IOException {
// Process RAW input stream
if (mAssetInputStream != null) {
long asset = mAssetInputStream.getNativeAsset();
if (handleRawResult(nativeGetRawAttributesFromAsset(asset))) {
return;
}
if (mFileDescriptor != null) {
in = new FileInputStream(mFileDescriptor);
} else if (mSeekableFileDescriptor != null) {
if (handleRawResult(nativeGetRawAttributesFromFileDescriptor(
mSeekableFileDescriptor))) {
return;
}
if (in != null) {
// First test whether a given file is a one of RAW format or not.
HashMap map = getRawAttributesNative(Os.dup(in.getFD()));
mIsRaw = map != null;
if (mIsRaw) {
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
String attrName = (String) entry.getKey();
String attrValue = (String) entry.getValue();
switch (attrName) {
case TAG_HAS_THUMBNAIL:
mHasThumbnail = attrValue.equalsIgnoreCase("true");
break;
case TAG_THUMBNAIL_OFFSET:
mThumbnailOffset = Integer.parseInt(attrValue);
break;
case TAG_THUMBNAIL_LENGTH:
mThumbnailLength = Integer.parseInt(attrValue);
break;
default:
mAttributes.put(attrName, attrValue);
break;
}
}
if (DEBUG) {
printAttributes();
}
return;
}
} else {
in = new BufferedInputStream(in, JPEG_SIGNATURE_SIZE);
if (!isJpegInputStream((BufferedInputStream) in) && handleRawResult(
nativeGetRawAttributesFromInputStream(in))) {
return;
}
} catch (ErrnoException e) {
e.rethrowAsIOException();
} finally {
IoUtils.closeQuietly(in);
}
try {
if (mFileDescriptor != null) {
Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
}
// Process JPEG input stream
getJpegAttributes(in);
getJpegAttributes(mInputStream);
} catch (ErrnoException e) {
e.rethrowAsIOException();
} finally {
IoUtils.closeQuietly(mInputStream);
if (DEBUG) {
printAttributes();
}
}
private static boolean isJpegInputStream(BufferedInputStream in) throws IOException {
in.mark(JPEG_SIGNATURE_SIZE);
byte[] signatureBytes = new byte[JPEG_SIGNATURE_SIZE];
if (in.read(signatureBytes) != JPEG_SIGNATURE_SIZE) {
throw new EOFException();
}
boolean isJpeg = Arrays.equals(JPEG_SIGNATURE, signatureBytes);
in.reset();
return isJpeg;
}
private boolean handleRawResult(HashMap map) {
if (map == null) {
return false;
}
// Mark for disabling the save feature.
mIsRaw = true;
for (Object obj : map.entrySet()) {
Map.Entry entry = (Map.Entry) obj;
String attrName = (String) entry.getKey();
switch (attrName) {
case TAG_HAS_THUMBNAIL:
mHasThumbnail = ((String) entry.getValue()).equalsIgnoreCase("true");
break;
case TAG_THUMBNAIL_OFFSET:
mThumbnailOffset = Integer.parseInt((String) entry.getValue());
break;
case TAG_THUMBNAIL_LENGTH:
mThumbnailLength = Integer.parseInt((String) entry.getValue());
break;
case TAG_THUMBNAIL_DATA:
mThumbnailBytes = (byte[]) entry.getValue();
break;
default:
mAttributes.put(attrName, (String) entry.getValue());
break;
}
}
if (DEBUG) {
printAttributes();
}
return true;
}
private static boolean isSeekableFD(FileDescriptor fd) throws IOException {
try {
Os.lseek(fd, 0, OsConstants.SEEK_CUR);
return true;
} catch (ErrnoException e) {
return false;
}
}
// Prints out attributes for debugging.
@@ -679,9 +721,9 @@ public class ExifInterface {
throw new UnsupportedOperationException(
"ExifInterface does not support saving attributes on RAW formats.");
}
if (mFileDescriptor == null && mFilename == null) {
if (mSeekableFileDescriptor == null && mFilename == null) {
throw new UnsupportedOperationException(
"ExifInterface does not support saving attributes for input streams.");
"ExifInterface does not support saving attributes for the current input.");
}
// Keep the thumbnail in memory
@@ -698,11 +740,10 @@ public class ExifInterface {
if (!originalFile.renameTo(tempFile)) {
throw new IOException("Could'nt rename to " + tempFile.getAbsolutePath());
}
}
if (mFileDescriptor != null) {
} else if (mSeekableFileDescriptor != null) {
tempFile = File.createTempFile("temp", "jpg");
Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
in = new FileInputStream(mFileDescriptor);
Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
in = new FileInputStream(mSeekableFileDescriptor);
out = new FileOutputStream(tempFile);
Streams.copy(in, out);
}
@@ -720,10 +761,9 @@ public class ExifInterface {
in = new FileInputStream(tempFile);
if (mFilename != null) {
out = new FileOutputStream(mFilename);
}
if (mFileDescriptor != null) {
Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
out = new FileOutputStream(mFileDescriptor);
} else if (mSeekableFileDescriptor != null) {
Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
out = new FileOutputStream(mSeekableFileDescriptor);
}
saveJpegAttributes(in, out);
} catch (ErrnoException e) {
@@ -760,13 +800,15 @@ public class ExifInterface {
// Read the thumbnail.
FileInputStream in = null;
try {
if (mFileDescriptor != null) {
Os.lseek(mFileDescriptor, 0, OsConstants.SEEK_SET);
in = new FileInputStream(mFileDescriptor);
}
if (mFilename != null) {
try {
if (mAssetInputStream != null) {
return nativeGetThumbnailFromAsset(
mAssetInputStream.getNativeAsset(), mThumbnailOffset, mThumbnailLength);
} else if (mFilename != null) {
in = new FileInputStream(mFilename);
} else if (mSeekableFileDescriptor != null) {
Os.lseek(mSeekableFileDescriptor, 0, OsConstants.SEEK_SET);
in = new FileInputStream(mSeekableFileDescriptor);
}
if (in == null) {
// Should not be reached this.
@@ -1180,8 +1222,9 @@ public class ExifInterface {
mThumbnailOffset = exifOffsetFromBeginning + jpegInterchangeFormat;
mThumbnailLength = jpegInterchangeFormatLength;
// Do not store a thumbnail in memory if the given input can be re-read.
if (mFileDescriptor == null && mFilename == null) {
if (mFilename == null && mAssetInputStream == null
&& mSeekableFileDescriptor == null) {
// Save the thumbnail in memory if the input doesn't support reading again.
byte[] thumbnailBytes = new byte[jpegInterchangeFormatLength];
dataInputStream.seek(jpegInterchangeFormat);
dataInputStream.readFully(thumbnailBytes);
@@ -1988,6 +2031,10 @@ public class ExifInterface {
}
// JNI methods for RAW formats.
private static native void initRawNative();
private static native HashMap getRawAttributesNative(FileDescriptor fileDescriptor);
private static native void nativeInitRaw();
private static native byte[] nativeGetThumbnailFromAsset(
long asset, int thumbnailOffset, int thumbnailLength);
private static native HashMap nativeGetRawAttributesFromAsset(long asset);
private static native HashMap nativeGetRawAttributesFromFileDescriptor(FileDescriptor fd);
private static native HashMap nativeGetRawAttributesFromInputStream(InputStream in);
}

View File

@@ -19,12 +19,15 @@
#include "android_media_Utils.h"
#include "android/graphics/CreateJavaOutputStreamAdaptor.h"
#include "src/piex_types.h"
#include "src/piex.h"
#include <jni.h>
#include <JNIHelp.h>
#include <androidfw/Asset.h>
#include <android_runtime/AndroidRuntime.h>
#include <android/graphics/Utils.h>
#include <nativehelper/ScopedLocalRef.h>
#include <utils/Log.h>
@@ -35,6 +38,9 @@
using namespace android;
static const char kJpegSignatureChars[] = {(char)0xff, (char)0xd8, (char)0xff};
static const int kJpegSignatureSize = 3;
#define FIND_CLASS(var, className) \
var = env->FindClass(className); \
LOG_FATAL_IF(! var, "Unable to find class " className);
@@ -82,18 +88,48 @@ static void ExifInterface_initRaw(JNIEnv *env) {
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
}
static jobject ExifInterface_getRawMetadata(
JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
if (fd < 0) {
ALOGI("Invalid file descriptor");
static bool is_asset_stream(const SkStream& stream) {
return stream.hasLength() && stream.hasPosition();
}
static jobject ExifInterface_getThumbnailFromAsset(
JNIEnv* env, jclass /* clazz */, jlong jasset, jint jthumbnailOffset,
jint jthumbnailLength) {
Asset* asset = reinterpret_cast<Asset*>(jasset);
std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
std::unique_ptr<jbyte[]> thumbnailData(new jbyte[(int)jthumbnailLength]);
if (thumbnailData.get() == NULL) {
ALOGI("No memory to get thumbnail");
return NULL;
}
piex::PreviewImageData image_data;
std::unique_ptr<FileStream> stream(new FileStream(fd));
// Do not know the current offset. So rewind it.
stream->rewind();
if (!GetExifFromRawImage(stream.get(), String8("[file descriptor]"), image_data)) {
// Read thumbnail.
stream->skip((int)jthumbnailOffset);
stream->read((void*)thumbnailData.get(), (int)jthumbnailLength);
// Copy to the byte array.
jbyteArray byteArray = env->NewByteArray(jthumbnailLength);
env->SetByteArrayRegion(byteArray, 0, jthumbnailLength, thumbnailData.get());
return byteArray;
}
static jobject getRawAttributes(JNIEnv* env, SkStream* stream, bool returnThumbnail) {
std::unique_ptr<SkStream> streamDeleter(stream);
std::unique_ptr<::piex::StreamInterface> piexStream;
if (is_asset_stream(*stream)) {
piexStream.reset(new AssetStream(streamDeleter.release()));
} else {
piexStream.reset(new BufferedStream(streamDeleter.release()));
}
piex::PreviewImageData image_data;
if (!GetExifFromRawImage(piexStream.get(), String8("[piex stream]"), image_data)) {
ALOGI("Raw image not detected");
return NULL;
}
@@ -253,7 +289,117 @@ static jobject ExifInterface_getRawMetadata(
}
}
return KeyedVectorToHashMap(env, map);
jobject hashMap = KeyedVectorToHashMap(env, map);
if (returnThumbnail) {
std::unique_ptr<jbyte[]> thumbnailData(new jbyte[image_data.thumbnail.length]);
if (thumbnailData.get() == NULL) {
ALOGE("No memory to parse a thumbnail");
return NULL;
}
jbyteArray jthumbnailByteArray = env->NewByteArray(image_data.thumbnail.length);
if (jthumbnailByteArray == NULL) {
ALOGE("No memory to parse a thumbnail");
return NULL;
}
piexStream.get()->GetData(image_data.thumbnail.offset, image_data.thumbnail.length,
(uint8_t*)thumbnailData.get());
env->SetByteArrayRegion(
jthumbnailByteArray, 0, image_data.thumbnail.length, thumbnailData.get());
jstring jkey = env->NewStringUTF(String8("thumbnailData"));
env->CallObjectMethod(hashMap, gFields.hashMap.put, jkey, jthumbnailByteArray);
env->DeleteLocalRef(jkey);
env->DeleteLocalRef(jthumbnailByteArray);
}
return hashMap;
}
static jobject ExifInterface_getRawAttributesFromAsset(
JNIEnv* env, jclass /* clazz */, jlong jasset) {
std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
if (jpegSignature.get() == NULL) {
ALOGE("No enough memory to parse");
return NULL;
}
Asset* asset = reinterpret_cast<Asset*>(jasset);
std::unique_ptr<AssetStreamAdaptor> stream(new AssetStreamAdaptor(asset));
if (stream.get()->read(jpegSignature.get(), kJpegSignatureSize) != kJpegSignatureSize) {
// Rewind the stream.
stream.get()->rewind();
ALOGI("Corrupted image.");
return NULL;
}
// Rewind the stream.
stream.get()->rewind();
if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
ALOGI("Should be a JPEG stream.");
return NULL;
}
// Try to parse from the given stream.
jobject result = getRawAttributes(env, stream.get(), false);
// Rewind the stream for the chance to read JPEG.
if (result == NULL) {
stream.get()->rewind();
}
return result;
}
static jobject ExifInterface_getRawAttributesFromFileDescriptor(
JNIEnv* env, jclass /* clazz */, jobject jfileDescriptor) {
std::unique_ptr<char[]> jpegSignature(new char[kJpegSignatureSize]);
if (jpegSignature.get() == NULL) {
ALOGE("No enough memory to parse");
return NULL;
}
int fd = jniGetFDFromFileDescriptor(env, jfileDescriptor);
if (fd < 0) {
ALOGI("Invalid file descriptor");
return NULL;
}
// Restore the file descriptor's offset on exiting this function.
AutoFDSeek autoRestore(fd);
int dupFd = dup(fd);
FILE* file = fdopen(dupFd, "r");
if (file == NULL) {
ALOGI("Failed to open the file descriptor");
return NULL;
}
if (fgets(jpegSignature.get(), kJpegSignatureSize, file) == NULL) {
ALOGI("Corrupted image.");
return NULL;
}
if (memcmp(jpegSignature.get(), kJpegSignatureChars, kJpegSignatureSize) == 0) {
ALOGI("Should be a JPEG stream.");
return NULL;
}
// Rewind the file descriptor.
fseek(file, 0L, SEEK_SET);
std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file,
SkFILEStream::kCallerPasses_Ownership));
return getRawAttributes(env, fileStream.get(), false);
}
static jobject ExifInterface_getRawAttributesFromInputStream(
JNIEnv* env, jclass /* clazz */, jobject jinputStream) {
jbyteArray byteArray = env->NewByteArray(8*1024);
ScopedLocalRef<jbyteArray> scoper(env, byteArray);
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, jinputStream, scoper.get()));
return getRawAttributes(env, stream.get(), true);
}
} // extern "C"
@@ -261,9 +407,14 @@ static jobject ExifInterface_getRawMetadata(
// ----------------------------------------------------------------------------
static JNINativeMethod gMethods[] = {
{ "initRawNative", "()V", (void *)ExifInterface_initRaw },
{ "getRawAttributesNative", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
(void*)ExifInterface_getRawMetadata },
{ "nativeInitRaw", "()V", (void *)ExifInterface_initRaw },
{ "nativeGetThumbnailFromAsset", "(JII)[B", (void *)ExifInterface_getThumbnailFromAsset },
{ "nativeGetRawAttributesFromAsset", "(J)Ljava/util/HashMap;",
(void*)ExifInterface_getRawAttributesFromAsset },
{ "nativeGetRawAttributesFromFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/util/HashMap;",
(void*)ExifInterface_getRawAttributesFromFileDescriptor },
{ "nativeGetRawAttributesFromInputStream", "(Ljava/io/InputStream;)Ljava/util/HashMap;",
(void*)ExifInterface_getRawAttributesFromInputStream },
};
int register_android_media_ExifInterface(JNIEnv *env) {

View File

@@ -30,30 +30,78 @@
namespace android {
AssetStream::AssetStream(SkStream* stream)
: mStream(stream) {
}
AssetStream::~AssetStream() {
}
piex::Error AssetStream::GetData(
const size_t offset, const size_t length, std::uint8_t* data) {
// Seek first.
if (mPosition != offset) {
if (!mStream->seek(offset)) {
return piex::Error::kFail;
}
}
// Read bytes.
size_t size = mStream->read((void*)data, length);
mPosition += size;
return size == length ? piex::Error::kOk : piex::Error::kFail;
}
BufferedStream::BufferedStream(SkStream* stream)
: mStream(stream) {
}
BufferedStream::~BufferedStream() {
}
piex::Error BufferedStream::GetData(
const size_t offset, const size_t length, std::uint8_t* data) {
// Seek first.
if (offset + length > mStreamBuffer.bytesWritten()) {
size_t sizeToRead = offset + length - mStreamBuffer.bytesWritten();
if (sizeToRead <= kMinSizeToRead) {
sizeToRead = kMinSizeToRead;
}
void* tempBuffer = malloc(sizeToRead);
if (tempBuffer != NULL) {
size_t bytesRead = mStream->read(tempBuffer, sizeToRead);
if (bytesRead != sizeToRead) {
free(tempBuffer);
return piex::Error::kFail;
}
mStreamBuffer.write(tempBuffer, bytesRead);
free(tempBuffer);
}
}
// Read bytes.
if (mStreamBuffer.read((void*)data, offset, length)) {
return piex::Error::kOk;
} else {
return piex::Error::kFail;
}
}
FileStream::FileStream(const int fd)
: mPosition(0),
mSize(0) {
: mPosition(0) {
mFile = fdopen(fd, "r");
if (mFile == NULL) {
return;
}
// Get the size.
fseek(mFile, 0l, SEEK_END);
mSize = ftell(mFile);
fseek(mFile, 0l, SEEK_SET);
}
FileStream::FileStream(const String8 filename)
: mPosition(0),
mSize(0) {
: mPosition(0) {
mFile = fopen(filename.string(), "r");
if (mFile == NULL) {
return;
}
// Get the size.
fseek(mFile, 0l, SEEK_END);
mSize = ftell(mFile);
fseek(mFile, 0l, SEEK_SET);
}
FileStream::~FileStream() {
@@ -79,7 +127,7 @@ piex::Error FileStream::GetData(
mPosition += size;
// Handle errors.
if (ferror(mFile) || (size == 0 && feof(mFile))) {
if (ferror(mFile)) {
ALOGV("GetData read failed: (offset: %zu, length: %zu)", offset, length);
return piex::Error::kFail;
}
@@ -90,21 +138,12 @@ bool FileStream::exists() const {
return mFile != NULL;
}
size_t FileStream::size() const {
return mSize;
}
bool GetExifFromRawImage(
FileStream* stream, const String8& filename, piex::PreviewImageData& image_data) {
piex::StreamInterface* stream, const String8& filename,
piex::PreviewImageData& image_data) {
// Reset the PreviewImageData to its default.
image_data = piex::PreviewImageData();
if (!stream->exists()) {
// File is not exists.
ALOGV("File is not exists: %s", filename.string());
return false;
}
if (!piex::IsRaw(stream)) {
// Format not supported.
ALOGV("Format not supported: %s", filename.string());
@@ -119,12 +158,6 @@ bool GetExifFromRawImage(
return false;
}
if (image_data.thumbnail_offset + image_data.thumbnail_length > stream->size()) {
// Corrupted image.
ALOGV("Corrupted file: %s", filename.string());
return false;
}
return true;
}

View File

@@ -21,20 +21,62 @@
#include "src/piex.h"
#include <android_runtime/AndroidRuntime.h>
#include <camera3.h>
#include <gui/CpuConsumer.h>
#include <jni.h>
#include <JNIHelp.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
#include <gui/CpuConsumer.h>
#include <camera3.h>
#include <SkStream.h>
namespace android {
class AssetStream : public piex::StreamInterface {
private:
SkStream *mStream;
size_t mPosition;
public:
AssetStream(SkStream* stream);
~AssetStream();
// Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
// provided by the caller, guaranteed to be at least "length" bytes long.
// On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
// 'offset' bytes from the start of the stream.
// Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
// change the contents of 'data'.
piex::Error GetData(
const size_t offset, const size_t length, std::uint8_t* data) override;
};
class BufferedStream : public piex::StreamInterface {
private:
SkStream *mStream;
// Growable memory stream
SkDynamicMemoryWStream mStreamBuffer;
// Minimum size to read on filling the buffer.
const size_t kMinSizeToRead = 8192;
public:
BufferedStream(SkStream* stream);
~BufferedStream();
// Reads 'length' amount of bytes from 'offset' to 'data'. The 'data' buffer
// provided by the caller, guaranteed to be at least "length" bytes long.
// On 'kOk' the 'data' pointer contains 'length' valid bytes beginning at
// 'offset' bytes from the start of the stream.
// Returns 'kFail' if 'offset' + 'length' exceeds the stream and does not
// change the contents of 'data'.
piex::Error GetData(
const size_t offset, const size_t length, std::uint8_t* data) override;
};
class FileStream : public piex::StreamInterface {
private:
FILE *mFile;
size_t mPosition;
size_t mSize;
public:
FileStream(const int fd);
@@ -50,13 +92,12 @@ public:
piex::Error GetData(
const size_t offset, const size_t length, std::uint8_t* data) override;
bool exists() const;
size_t size() const;
};
// Reads EXIF metadata from a given raw image via piex.
// And returns true if the operation is successful; otherwise, false.
bool GetExifFromRawImage(
FileStream* stream, const String8& filename, piex::PreviewImageData& image_data);
piex::StreamInterface* stream, const String8& filename, piex::PreviewImageData& image_data);
// Returns true if the conversion is successful; otherwise, false.
bool ConvertKeyValueArraysToKeyedVector(

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@@ -76,9 +76,9 @@
<item>0</item>
</array>
<array name="lg_g4_iso_800_dng">
<item>false</item>
<item>0</item>
<item>0</item>
<item>true</item>
<item>256</item>
<item>144</item>
<item>true</item>
<item>53.834507</item>
<item>10.69585</item>

View File

@@ -23,20 +23,20 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.test.AndroidTestCase;
import android.util.Log;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import libcore.io.IoUtils;
import libcore.io.Streams;
@@ -46,11 +46,10 @@ public class ExifInterfaceTest extends AndroidTestCase {
private static final boolean VERBOSE = false; // lots of logging
private static final double DIFFERENCE_TOLERANCE = .005;
private static final int BUFFER_SIZE = 32768;
// List of files.
private static final String EXIF_BYTE_ORDER_II_JPEG = "ExifByteOrderII.jpg";
private static final String EXIF_BYTE_ORDER_MM_JPEG = "ExifByteOrderMM.jpg";
private static final String EXIF_BYTE_ORDER_II_JPEG = "image_exif_byte_order_ii.jpg";
private static final String EXIF_BYTE_ORDER_MM_JPEG = "image_exif_byte_order_mm.jpg";
private static final String LG_G4_ISO_800_DNG = "lg_g4_iso_800.dng";
private static final int[] IMAGE_RESOURCES = new int[] {
R.raw.image_exif_byte_order_ii, R.raw.image_exif_byte_order_mm, R.raw.lg_g4_iso_800 };
@@ -165,8 +164,6 @@ public class ExifInterfaceTest extends AndroidTestCase {
@Override
protected void setUp() throws Exception {
byte[] buffer = new byte[BUFFER_SIZE];
for (int i = 0; i < IMAGE_RESOURCES.length; ++i) {
String outputPath = new File(Environment.getExternalStorageDirectory(),
IMAGE_FILENAMES[i]).getAbsolutePath();
@@ -314,26 +311,30 @@ public class ExifInterfaceTest extends AndroidTestCase {
expectedValue.whiteBalance);
}
private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
private void testExifInterfaceCommon(File imageFile, ExpectedValue expectedValue)
throws IOException {
ExpectedValue expectedValue = new ExpectedValue(
getContext().getResources().obtainTypedArray(typedArrayResourceId));
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
// Created via path.
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
// Created from an asset file.
InputStream in = mContext.getAssets().open(imageFile.getName());
exifInterface = new ExifInterface(in);
if (VERBOSE) {
printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
// Created via InputStream.
FileInputStream in = null;
in = null;
try {
in = new FileInputStream(imageFile.getAbsolutePath());
in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
exifInterface = new ExifInterface(in);
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
} finally {
@@ -345,18 +346,30 @@ public class ExifInterfaceTest extends AndroidTestCase {
FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDONLY, 0600);
exifInterface = new ExifInterface(fd);
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
printExifTagsAndValues(imageFile.getName(), exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
} catch (ErrnoException e) {
e.rethrowAsIOException();
}
}
private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
throws IOException {
ExpectedValue expectedValue = new ExpectedValue(
getContext().getResources().obtainTypedArray(typedArrayResourceId));
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
// Test for reading from various inputs.
testExifInterfaceCommon(imageFile, expectedValue);
// Test for saving attributes.
ExifInterface exifInterface;
try {
FileDescriptor fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
exifInterface = new ExifInterface(fd);
exifInterface.saveAttributes();
fd = Os.open(imageFile.getAbsolutePath(), OsConstants.O_RDWR, 0600);
exifInterface = new ExifInterface(fd);
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
@@ -383,25 +396,11 @@ public class ExifInterfaceTest extends AndroidTestCase {
getContext().getResources().obtainTypedArray(typedArrayResourceId));
File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
// Created via path.
ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
// Test for reading from various inputs.
testExifInterfaceCommon(imageFile, expectedValue);
// Created via FileDescriptor.
FileInputStream in = null;
try {
in = new FileInputStream(imageFile);
exifInterface = new ExifInterface(in.getFD());
if (VERBOSE) {
printExifTagsAndValues(fileName, exifInterface);
}
compareWithExpectedValue(exifInterface, expectedValue);
} finally {
IoUtils.closeQuietly(in);
}
// Since ExifInterface does not support for saving attributes for RAW files, do not test
// about writing back in here.
}
public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
@@ -415,4 +414,14 @@ public class ExifInterfaceTest extends AndroidTestCase {
public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
}
public void testCorruptedImage() {
byte[] bytes = new byte[1024];
try {
new ExifInterface(new ByteArrayInputStream(bytes));
fail("Should not reach here!");
} catch (IOException e) {
// Success
}
}
}