Enable reading a thumbnail from RAW image files in MtpDatabase

Also introduce new supported RAW image file formats, PEF and SRW.

RAW image file formats are not defined in PTP 1.2 specification except
for DNG. They are mostly built on top of TIFF or TIFF/EP. (Fuji's RAF
is the exception).

In this CL, image file formats are classified newly as below:

DNG: dng
TIFF: cr2, nrw, arw, rw2, orf, pef, srw
TIFF/EP: nef
Unknown Image Formats(FORMAT_DEFINED): wbmap, webp, raf

I referred to the following documents for defining MTP formats of RAW
images:

* http://www.rags-int-inc.com/PhotoTechStuff/RawStandards/RawSummary.html
* https://en.wikipedia.org/wiki/Raw_image_format

Bug: 26552863, Bug: 26626825
Change-Id: Ia218f6320c4c1ff051a23ca0060ceac46134b0d7
diff --git a/api/current.txt b/api/current.txt
index 657c7c3..cf8763b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -22500,6 +22500,7 @@
     field public static final int FORMAT_AUDIBLE = 47364; // 0xb904
     field public static final int FORMAT_AVI = 12298; // 0x300a
     field public static final int FORMAT_BMP = 14340; // 0x3804
+    field public static final int FORMAT_DEFINED = 14336; // 0x3800
     field public static final int FORMAT_DNG = 14353; // 0x3811
     field public static final int FORMAT_DPOF = 12294; // 0x3006
     field public static final int FORMAT_EXECUTABLE = 12291; // 0x3003
diff --git a/api/system-current.txt b/api/system-current.txt
index 459dc59..80e5dd2 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -24056,6 +24056,7 @@
     field public static final int FORMAT_AUDIBLE = 47364; // 0xb904
     field public static final int FORMAT_AVI = 12298; // 0x300a
     field public static final int FORMAT_BMP = 14340; // 0x3804
+    field public static final int FORMAT_DEFINED = 14336; // 0x3800
     field public static final int FORMAT_DNG = 14353; // 0x3811
     field public static final int FORMAT_DPOF = 12294; // 0x3006
     field public static final int FORMAT_EXECUTABLE = 12291; // 0x3003
diff --git a/api/test-current.txt b/api/test-current.txt
index d78973f..aa4d8ab 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -22508,6 +22508,7 @@
     field public static final int FORMAT_AUDIBLE = 47364; // 0xb904
     field public static final int FORMAT_AVI = 12298; // 0x300a
     field public static final int FORMAT_BMP = 14340; // 0x3804
+    field public static final int FORMAT_DEFINED = 14336; // 0x3800
     field public static final int FORMAT_DNG = 14353; // 0x3811
     field public static final int FORMAT_DPOF = 12294; // 0x3006
     field public static final int FORMAT_EXECUTABLE = 12291; // 0x3003
diff --git a/media/java/android/media/MediaFile.java b/media/java/android/media/MediaFile.java
index 7d76e74..f7becf5 100644
--- a/media/java/android/media/MediaFile.java
+++ b/media/java/android/media/MediaFile.java
@@ -91,8 +91,10 @@
     public static final int FILE_TYPE_RW2     = 305;
     public static final int FILE_TYPE_ORF     = 306;
     public static final int FILE_TYPE_RAF     = 307;
+    public static final int FILE_TYPE_PEF     = 308;
+    public static final int FILE_TYPE_SRW     = 309;
     private static final int FIRST_RAW_IMAGE_FILE_TYPE = FILE_TYPE_DNG;
-    private static final int LAST_RAW_IMAGE_FILE_TYPE = FILE_TYPE_RAF;
+    private static final int LAST_RAW_IMAGE_FILE_TYPE = FILE_TYPE_SRW;
 
     // Playlist file types
     public static final int FILE_TYPE_M3U      = 41;
@@ -228,17 +230,19 @@
         addFileType("GIF", FILE_TYPE_GIF, "image/gif", MtpConstants.FORMAT_GIF);
         addFileType("PNG", FILE_TYPE_PNG, "image/png", MtpConstants.FORMAT_PNG);
         addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp", MtpConstants.FORMAT_BMP);
-        addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp");
-        addFileType("WEBP", FILE_TYPE_WEBP, "image/webp");
+        addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp", MtpConstants.FORMAT_DEFINED);
+        addFileType("WEBP", FILE_TYPE_WEBP, "image/webp", MtpConstants.FORMAT_DEFINED);
 
         addFileType("DNG", FILE_TYPE_DNG, "image/x-adobe-dng", MtpConstants.FORMAT_DNG);
-        addFileType("CR2", FILE_TYPE_CR2, "image/x-canon-cr2");
-        addFileType("NEF", FILE_TYPE_NEF, "image/x-nikon-nef");
-        addFileType("NRW", FILE_TYPE_NRW, "image/x-nikon-nrw");
-        addFileType("ARW", FILE_TYPE_ARW, "image/x-sony-arw");
-        addFileType("RW2", FILE_TYPE_RW2, "image/x-panasonic-rw2");
-        addFileType("ORF", FILE_TYPE_ORF, "image/x-olympus-orf");
-        addFileType("RAF", FILE_TYPE_RAF, "image/x-fuji-raf");
+        addFileType("CR2", FILE_TYPE_CR2, "image/x-canon-cr2", MtpConstants.FORMAT_TIFF);
+        addFileType("NEF", FILE_TYPE_NEF, "image/x-nikon-nef", MtpConstants.FORMAT_TIFF_EP);
+        addFileType("NRW", FILE_TYPE_NRW, "image/x-nikon-nrw", MtpConstants.FORMAT_TIFF);
+        addFileType("ARW", FILE_TYPE_ARW, "image/x-sony-arw", MtpConstants.FORMAT_TIFF);
+        addFileType("RW2", FILE_TYPE_RW2, "image/x-panasonic-rw2", MtpConstants.FORMAT_TIFF);
+        addFileType("ORF", FILE_TYPE_ORF, "image/x-olympus-orf", MtpConstants.FORMAT_TIFF);
+        addFileType("RAF", FILE_TYPE_RAF, "image/x-fuji-raf", MtpConstants.FORMAT_DEFINED);
+        addFileType("PEF", FILE_TYPE_PEF, "image/x-pentax-pef", MtpConstants.FORMAT_TIFF);
+        addFileType("SRW", FILE_TYPE_SRW, "image/x-samsung-srw", MtpConstants.FORMAT_TIFF);
 
         addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl", MtpConstants.FORMAT_M3U_PLAYLIST);
         addFileType("M3U", FILE_TYPE_M3U, "application/x-mpegurl", MtpConstants.FORMAT_M3U_PLAYLIST);
diff --git a/media/java/android/mtp/MtpConstants.java b/media/java/android/mtp/MtpConstants.java
index bdd8643..ef2cf2b 100644
--- a/media/java/android/mtp/MtpConstants.java
+++ b/media/java/android/mtp/MtpConstants.java
@@ -182,6 +182,13 @@
     public static final int FORMAT_MPEG = 0x300B;
     /** Format code for ASF files */
     public static final int FORMAT_ASF = 0x300C;
+    /**
+     * Format code for unknown image files.
+     * <p>
+     * Will be used for the formats which are not specified in PTP specification.
+     * For instance, WEBP and WBMP.
+     */
+    public static final int FORMAT_DEFINED = 0x3800;
     /** Format code for JPEG image files */
     public static final int FORMAT_EXIF_JPEG = 0x3801;
     /** Format code for TIFF EP image files */
@@ -272,12 +279,12 @@
     public static final int FORMAT_MS_POWERPOINT_PRESENTATION = 0xBA86;
 
     /**
-      * Returns true if the object is abstract (that is, it has no representation
-      * in the underlying file system).
-      *
-      * @param format the format of the object
-      * @return true if the object is abstract
-      */
+     * Returns true if the object is abstract (that is, it has no representation
+     * in the underlying file system).
+     *
+     * @param format the format of the object
+     * @return true if the object is abstract
+     */
     public static boolean isAbstractObject(int format) {
         switch (format) {
             case FORMAT_ABSTRACT_MULTIMEDIA_ALBUM:
diff --git a/media/jni/android_media_ExifInterface.cpp b/media/jni/android_media_ExifInterface.cpp
index 463c17e..42deab4 100644
--- a/media/jni/android_media_ExifInterface.cpp
+++ b/media/jni/android_media_ExifInterface.cpp
@@ -16,92 +16,25 @@
 
 //#define LOG_NDEBUG 0
 #define LOG_TAG "ExifInterface_JNI"
-#include <utils/Log.h>
-#include <utils/String8.h>
-#include <utils/KeyedVector.h>
 
-#include <android_runtime/AndroidRuntime.h>
-
-#include <jni.h>
-#include <JNIHelp.h>
-
-#include <nativehelper/ScopedLocalRef.h>
+#include "android_media_Utils.h"
 
 #include "src/piex_types.h"
 #include "src/piex.h"
 
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/KeyedVector.h>
+
 // ----------------------------------------------------------------------------
 
 using namespace android;
 
-class FileStream : public piex::StreamInterface {
-private:
-    FILE *mFile;
-    size_t mPosition;
-    size_t mSize;
-
-public:
-    FileStream(const String8 filename)
-        : mPosition(0),
-          mSize(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() {
-        if (mFile != NULL) {
-            fclose(mFile);
-            mFile = NULL;
-        }
-    }
-
-    // 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 {
-        if (mFile == NULL) {
-            return piex::Error::kFail;
-        }
-
-        // Seek first.
-        if (mPosition != offset) {
-            fseek(mFile, offset, SEEK_SET);
-        }
-
-        // Read bytes.
-        size_t size = fread((void*)data, sizeof(std::uint8_t), length, mFile);
-        mPosition += size;
-
-        // Handle errors.
-        if (ferror(mFile)) {
-            return piex::Error::kFail;
-        }
-        if (size == 0 && feof(mFile)) {
-            return piex::Error::kFail;
-        }
-        return piex::Error::kOk;
-    }
-
-    bool exists() {
-        return mFile != NULL;
-    }
-
-    size_t size() {
-        return mSize;
-    }
-};
-
 #define FIND_CLASS(var, className) \
     var = env->FindClass(className); \
     LOG_FATAL_IF(! var, "Unable to find class " className);
@@ -159,31 +92,10 @@
     env->ReleaseStringUTFChars(jfilename, filenameChars);
 
     piex::PreviewImageData image_data;
-    memset(&image_data, 0, sizeof(image_data));
     std::unique_ptr<FileStream> stream(new FileStream(filename));
 
-    if (!stream.get()->exists()) {
-        // File is not exists.
-        ALOGI("File is not exists: %s", filename.string());
-        return NULL;
-    }
-
-    if (!piex::IsRaw(stream.get())) {
-        // Format not supported.
-        ALOGI("Format not supported: %s", filename.string());
-        return NULL;
-    }
-
-    piex::Error err = piex::GetPreviewImageData(stream.get(), &image_data);
-    if (err != piex::Error::kOk) {
-        // The input data seems to be broken.
-        ALOGI("Raw image not detected: %s (error code: %d)", filename.string(), (int32_t)err);
-        return NULL;
-    }
-
-    if (image_data.thumbnail_offset + image_data.thumbnail_length > stream.get()->size()) {
-        // Corrupted file.
-        ALOGI("Corrupted file: %s", filename.string());
+    if (!GetExifFromRawImage(stream.get(), filename, image_data)) {
+        ALOGI("Raw image not detected: %s", filename.string());
         return NULL;
     }
 
diff --git a/media/jni/android_media_Utils.cpp b/media/jni/android_media_Utils.cpp
index e101709..a5d0303 100644
--- a/media/jni/android_media_Utils.cpp
+++ b/media/jni/android_media_Utils.cpp
@@ -28,6 +28,91 @@
 
 namespace android {
 
+FileStream::FileStream(const String8 filename)
+    : mPosition(0),
+      mSize(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() {
+    if (mFile != NULL) {
+        fclose(mFile);
+        mFile = NULL;
+    }
+}
+
+piex::Error FileStream::GetData(
+        const size_t offset, const size_t length, std::uint8_t* data) {
+    if (mFile == NULL) {
+        return piex::Error::kFail;
+    }
+
+    // Seek first.
+    if (mPosition != offset) {
+        fseek(mFile, offset, SEEK_SET);
+    }
+
+    // Read bytes.
+    size_t size = fread((void*)data, sizeof(std::uint8_t), length, mFile);
+    mPosition += size;
+
+    // Handle errors.
+    if (ferror(mFile) || (size == 0 && feof(mFile))) {
+        ALOGV("GetData read failed: (offset: %zu, length: %zu)", offset, length);
+        return piex::Error::kFail;
+    }
+    return piex::Error::kOk;
+}
+
+bool FileStream::exists() const {
+    return mFile != NULL;
+}
+
+size_t FileStream::size() const {
+    return mSize;
+}
+
+bool GetExifFromRawImage(
+        FileStream::FileStream* stream, const String8& filename,
+        piex::PreviewImageData& image_data) {
+    memset(&image_data, 0, sizeof(image_data));
+
+    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());
+        return false;
+    }
+
+    piex::Error err = piex::GetPreviewImageData(stream, &image_data);
+
+    if (err != piex::Error::kOk) {
+        // The input data seems to be broken.
+        ALOGV("Raw image not detected: %s (piex error code: %d)", filename.string(), (int32_t)err);
+        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;
+}
+
 bool ConvertKeyValueArraysToKeyedVector(
         JNIEnv *env, jobjectArray keys, jobjectArray values,
         KeyedVector<String8, String8>* keyedVector) {
diff --git a/media/jni/android_media_Utils.h b/media/jni/android_media_Utils.h
index 635bceb..762c904 100644
--- a/media/jni/android_media_Utils.h
+++ b/media/jni/android_media_Utils.h
@@ -17,21 +17,48 @@
 #ifndef _ANDROID_MEDIA_UTILS_H_
 #define _ANDROID_MEDIA_UTILS_H_
 
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
+#include "src/piex_types.h"
+#include "src/piex.h"
 
+#include <android_runtime/AndroidRuntime.h>
+#include <jni.h>
+#include <JNIHelp.h>
 #include <utils/KeyedVector.h>
 #include <utils/String8.h>
 
 namespace android {
 
-/**
- * Returns true if the conversion is successful; otherwise, false.
- */
+class FileStream : public piex::StreamInterface {
+private:
+    FILE *mFile;
+    size_t mPosition;
+    size_t mSize;
+
+public:
+    FileStream(const String8 filename);
+    ~FileStream();
+
+    // 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;
+    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);
+
+// Returns true if the conversion is successful; otherwise, false.
 bool ConvertKeyValueArraysToKeyedVector(
-    JNIEnv *env, jobjectArray keys, jobjectArray values,
-    KeyedVector<String8, String8>* vector);
+        JNIEnv *env, jobjectArray keys, jobjectArray values,
+        KeyedVector<String8, String8>* vector);
 
 struct AMessage;
 status_t ConvertMessageToMap(
diff --git a/media/jni/android_mtp_MtpDatabase.cpp b/media/jni/android_mtp_MtpDatabase.cpp
index ec2f98a..556f2c7 100644
--- a/media/jni/android_mtp_MtpDatabase.cpp
+++ b/media/jni/android_mtp_MtpDatabase.cpp
@@ -17,25 +17,17 @@
 #define LOG_TAG "MtpDatabaseJNI"
 #include "utils/Log.h"
 
-#include <assert.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <stdio.h>
-#include <unistd.h>
-
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
-#include "android_runtime/Log.h"
-
+#include "android_media_Utils.h"
+#include "mtp.h"
 #include "MtpDatabase.h"
 #include "MtpDataPacket.h"
 #include "MtpObjectInfo.h"
 #include "MtpProperty.h"
 #include "MtpStringBuffer.h"
 #include "MtpUtils.h"
-#include "mtp.h"
+
+#include "src/piex_types.h"
+#include "src/piex.h"
 
 extern "C" {
 #include "libexif/exif-content.h"
@@ -44,6 +36,19 @@
 #include "libexif/exif-utils.h"
 }
 
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/Log.h>
+#include <jni.h>
+#include <JNIHelp.h>
+#include <nativehelper/ScopedLocalRef.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdio.h>
+#include <unistd.h>
+
 using namespace android;
 
 // ----------------------------------------------------------------------------
@@ -823,24 +828,48 @@
     env->ReleaseCharArrayElements(mStringBuffer, str, 0);
 
     // read EXIF data for thumbnail information
-    if (info.mFormat == MTP_FORMAT_EXIF_JPEG || info.mFormat == MTP_FORMAT_JFIF) {
+    switch (info.mFormat) {
+        case MTP_FORMAT_EXIF_JPEG:
+        case MTP_FORMAT_JFIF: {
+            ExifData *exifdata = exif_data_new_from_file(path);
+            if (exifdata) {
+                if ((false)) {
+                    exif_data_foreach_content(exifdata, foreachcontent, NULL);
+                }
 
-        ExifData *exifdata = exif_data_new_from_file(path);
-        if (exifdata) {
-            if ((false)) {
-                exif_data_foreach_content(exifdata, foreachcontent, NULL);
+                ExifEntry *w = exif_content_get_entry(
+                        exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_X_DIMENSION);
+                ExifEntry *h = exif_content_get_entry(
+                        exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_Y_DIMENSION);
+                info.mThumbCompressedSize = exifdata->data ? exifdata->size : 0;
+                info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
+                info.mImagePixWidth = w ? getLongFromExifEntry(w) : 0;
+                info.mImagePixHeight = h ? getLongFromExifEntry(h) : 0;
+                exif_data_unref(exifdata);
+            }
+            break;
+        }
+
+        // Except DNG, all supported RAW image formats are not defined in PTP 1.2 specification.
+        // Most of RAW image formats are based on TIFF or TIFF/EP. To render Fuji's RAF format,
+        // it checks MTP_FORMAT_DEFINED case since it's designed as a custom format.
+        case MTP_FORMAT_DNG:
+        case MTP_FORMAT_TIFF:
+        case MTP_FORMAT_TIFF_EP:
+        case MTP_FORMAT_DEFINED: {
+            std::unique_ptr<FileStream> stream(new FileStream(path));
+            piex::PreviewImageData image_data;
+            if (!GetExifFromRawImage(stream.get(), path, image_data)) {
+                // Couldn't parse EXIF data from a image file via piex.
+                break;
             }
 
-            // XXX get this from exif, or parse jpeg header instead?
-            ExifEntry *w = exif_content_get_entry(
-                    exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_X_DIMENSION);
-            ExifEntry *h = exif_content_get_entry(
-                    exifdata->ifd[EXIF_IFD_EXIF], EXIF_TAG_PIXEL_Y_DIMENSION);
-            info.mThumbCompressedSize = exifdata->data ? exifdata->size : 0;
+            info.mThumbCompressedSize = image_data.thumbnail_length;
             info.mThumbFormat = MTP_FORMAT_EXIF_JPEG;
-            info.mImagePixWidth = w ? getLongFromExifEntry(w) : 0;
-            info.mImagePixHeight = h ? getLongFromExifEntry(h) : 0;
-            exif_data_unref(exifdata);
+            info.mImagePixWidth = image_data.full_width;
+            info.mImagePixHeight = image_data.full_height;
+
+            break;
         }
     }
 
@@ -855,19 +884,55 @@
     void* result = NULL;
     outThumbSize = 0;
 
-    if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK
-            && (format == MTP_FORMAT_EXIF_JPEG || format == MTP_FORMAT_JFIF)) {
-
-        ExifData *exifdata = exif_data_new_from_file(path);
-        if (exifdata) {
-            if (exifdata->data) {
-                result = malloc(exifdata->size);
-                if (result) {
-                    memcpy(result, exifdata->data, exifdata->size);
-                    outThumbSize = exifdata->size;
+    if (getObjectFilePath(handle, path, length, format) == MTP_RESPONSE_OK) {
+        switch (format) {
+            case MTP_FORMAT_EXIF_JPEG:
+            case MTP_FORMAT_JFIF: {
+                ExifData *exifdata = exif_data_new_from_file(path);
+                if (exifdata) {
+                    if (exifdata->data) {
+                        result = malloc(exifdata->size);
+                        if (result) {
+                            memcpy(result, exifdata->data, exifdata->size);
+                            outThumbSize = exifdata->size;
+                        }
+                    }
+                    exif_data_unref(exifdata);
                 }
+                break;
             }
-            exif_data_unref(exifdata);
+
+            // See the above comment on getObjectInfo() method.
+            case MTP_FORMAT_DNG:
+            case MTP_FORMAT_TIFF:
+            case MTP_FORMAT_TIFF_EP:
+            case MTP_FORMAT_DEFINED: {
+                std::unique_ptr<FileStream> stream(new FileStream(path));
+                piex::PreviewImageData image_data;
+                if (!GetExifFromRawImage(stream.get(), path, image_data)) {
+                    // Couldn't parse EXIF data from a image file via piex.
+                    break;
+                }
+
+                if (image_data.thumbnail_length == 0) {
+                    // No thumbnail.
+                    break;
+                }
+
+                result = malloc(image_data.thumbnail_length);
+                if (result) {
+                    piex::Error err = stream.get()->GetData(
+                            image_data.thumbnail_offset,
+                            image_data.thumbnail_length,
+                            (std::uint8_t *)result);
+                    if (err == piex::Error::kOk) {
+                        outThumbSize = image_data.thumbnail_length;
+                    } else {
+                        free(result);
+                    }
+                }
+                break;
+            }
         }
     }