UltraHDR: reorder MPF and EXIF
For encode API-2, 3 and 4 where input has a JPEG, we always want
the EXIF package appears in front of MPF.
This change also fixed "double null terminator" string problem in
the decoder helper.
Bug: 292561235
Test: jpegr_test.cpp
Change-Id: If6890cc499e179ba01c4e7775796111edb7a5d2f
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
index 1f0c38f..30149c1 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -76,15 +76,27 @@
*/
size_t getXMPSize();
/*
+ * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+ */
+ bool extractEXIF(const void* image, int length);
+ /*
* Returns the EXIF data from the image.
+ * This method must be called after extractEXIF() or decompressImage().
*/
void* getEXIFPtr();
/*
* Returns the decompressed EXIF buffer size. This method must be called only after
- * calling decompressImage() or getCompressedImageParameters().
+ * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
*/
size_t getEXIFSize();
/*
+ * Returns the position offset of EXIF package
+ * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
+ * or -1 if no EXIF exists.
+ * This method must be called after extractEXIF() or decompressImage().
+ */
+ int getEXIFPos() { return mExifPos; }
+ /*
* Returns the ICC data from the image.
*/
void* getICCPtr();
@@ -122,6 +134,9 @@
// Resolution of the decompressed image.
size_t mWidth;
size_t mHeight;
+
+ // Position of EXIF package, default value is -1 which means no EXIF package appears.
+ size_t mExifPos;
};
} /* namespace android::ultrahdr */
diff --git a/libs/ultrahdr/include/ultrahdr/jpegr.h b/libs/ultrahdr/include/ultrahdr/jpegr.h
index 850cb32..114c81d 100644
--- a/libs/ultrahdr/include/ultrahdr/jpegr.h
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -366,22 +366,24 @@
* the compressed gain map and optionally the exif package as inputs, and generate the XMP
* metadata, and finally append everything in the order of:
* SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
- * Note that EXIF package is only available for encoding API-0 and API-1. For encoding API-2 and
- * API-3 this parameter is null, but the primary image in JPEG/R may still have EXIF as long as
- * the input JPEG has EXIF.
*
-
+ * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
+ * conditions is fulfilled:
+ * (1) EXIF package is available from outside input. I.e. pExif != nullptr.
+ * (2) Input JPEG has EXIF.
+ * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
+ *
* @param primary_jpg_image_ptr destination of primary image
* @param gainmap_jpg_image_ptr destination of compressed gain map image
- * @param (nullable) exif EXIF package
- * @param (nullable) icc ICC package
+ * @param (nullable) pExif EXIF package
+ * @param (nullable) pIcc ICC package
* @param icc_size length in bytes of ICC package
* @param metadata JPEG/R metadata to encode in XMP of the jpeg
* @param dest compressed JPEGR image
* @return NO_ERROR if calculation succeeds, error code if error occurs.
*/
status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
- jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
+ jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
/*
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
index 7ccf1f3..2e7940c 100644
--- a/libs/ultrahdr/jpegdecoderhelper.cpp
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -32,12 +32,17 @@
const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
-const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/";
-const std::string kExifIdCode = "Exif";
constexpr uint32_t kICCMarkerHeaderSize = 14;
constexpr uint8_t kICCSig[] = {
'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
};
+constexpr uint8_t kXmpNameSpace[] = {
+ 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
+ '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
+};
+constexpr uint8_t kExifIdCode[] = {
+ 'E', 'x', 'i', 'f', '\0', '\0',
+};
struct jpegr_source_mgr : jpeg_source_mgr {
jpegr_source_mgr(const uint8_t* ptr, int len);
@@ -146,6 +151,58 @@
return mHeight;
}
+// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
+// in the image file.
+// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+// two bytes of package length which is stored in marker->original_length, and the real data
+// which is stored in marker->data.
+bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
+ jpeg_decompress_struct cinfo;
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ jpegrerror_mgr myerr;
+
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+
+ cinfo.src = &mgr;
+ jpeg_read_header(&cinfo, TRUE);
+
+ size_t pos = 2; // position after SOI
+ for (jpeg_marker_struct* marker = cinfo.marker_list;
+ marker;
+ marker = marker->next) {
+
+ pos += 4;
+ pos += marker->original_length;
+
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+
+ if (len > sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+ mEXIFBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+ mExifPos = pos - marker->original_length;
+ break;
+ }
+ }
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
bool status = true;
jpeg_decompress_struct cinfo;
@@ -178,25 +235,31 @@
bool exifAppears = false;
bool xmpAppears = false;
bool iccAppears = false;
+ size_t pos = 2; // position after SOI
for (jpeg_marker_struct* marker = cinfo.marker_list;
- marker && !(exifAppears && xmpAppears && iccAppears); marker = marker->next) {
+ marker && !(exifAppears && xmpAppears && iccAppears);
+ marker = marker->next) {
+ pos += 4;
+ pos += marker->original_length;
if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
continue;
}
const unsigned int len = marker->data_length;
- if (!xmpAppears && len > kXmpNameSpace.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data), kXmpNameSpace.c_str(),
- kXmpNameSpace.size())) {
- mXMPBuffer.resize(len + 1, 0);
+ if (!xmpAppears &&
+ len > sizeof(kXmpNameSpace) &&
+ !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
+ mXMPBuffer.resize(len+1, 0);
memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
xmpAppears = true;
- } else if (!exifAppears && len > kExifIdCode.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
- kExifIdCode.size())) {
+ } else if (!exifAppears &&
+ len > sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
mEXIFBuffer.resize(len, 0);
memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
exifAppears = true;
- } else if (!iccAppears && len > sizeof(kICCSig) &&
+ mExifPos = pos - marker->original_length;
+ } else if (!iccAppears &&
+ len > sizeof(kICCSig) &&
!memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
mICCBuffer.resize(len, 0);
memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
@@ -325,9 +388,8 @@
}
const unsigned int len = marker->data_length;
- if (len >= kExifIdCode.size() &&
- !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(),
- kExifIdCode.size())) {
+ if (len >= sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
exifData->resize(len, 0);
memcpy(static_cast<void*>(exifData->data()), marker->data, len);
exifAppears = true;
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
index f27bd2d..74760d9 100644
--- a/libs/ultrahdr/jpegr.cpp
+++ b/libs/ultrahdr/jpegr.cpp
@@ -72,6 +72,32 @@
return cpuCoreCount;
}
+/*
+ * Helper function copies the JPEG image from without EXIF.
+ *
+ * @param pDest destination of the data to be written.
+ * @param pSource source of data being written.
+ * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
+ * (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
+ * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
+ */
+static void copyJpegWithoutExif(jr_compressed_ptr pDest,
+ jr_compressed_ptr pSource,
+ size_t exif_pos,
+ size_t exif_size) {
+ memcpy(pDest, pSource, sizeof(jpegr_compressed_struct));
+
+ const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
+ pDest->length = pSource->length - exif_size - exif_offset;
+ pDest->data = new uint8_t[pDest->length];
+ std::unique_ptr<uint8_t[]> dest_data;
+ dest_data.reset(reinterpret_cast<uint8_t*>(pDest->data));
+ memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
+ memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
+ (uint8_t*)pSource->data + exif_pos + exif_size,
+ pSource->length - exif_pos - exif_size);
+}
+
status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
jr_uncompressed_ptr yuv420_image_ptr,
ultrahdr_transfer_function hdr_tf,
@@ -1152,7 +1178,8 @@
// JPEG/R structure:
// SOI (ff d8)
//
-// (Optional, only if EXIF package is from outside)
+// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
+// in the JPEG input (Encode API-2, API-3, API-4))
// APP1 (ff e1)
// 2 bytes of length (2 + length of exif package)
// EXIF package (this includes the first two bytes representing the package length)
@@ -1166,7 +1193,7 @@
// 2 bytes of length
// MPF
//
-// (Required) primary image (without the first two bytes (SOI), may have other packages)
+// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
//
// SOI (ff d8)
//
@@ -1183,8 +1210,8 @@
// Adobe XMP spec part 3 for XMP marker
// ICC v4.3 spec for ICC
status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
- jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
- size_t icc_size, ultrahdr_metadata_ptr metadata,
+ jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
+ void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
jr_compressed_ptr dest) {
if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
dest == nullptr) {
@@ -1229,6 +1256,35 @@
// same as primary
const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
+ // Check if EXIF package presents in the JPEG input.
+ // If so, extract and remove the EXIF package.
+ JpegDecoderHelper decoder;
+ if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ jpegr_exif_struct exif_from_jpg;
+ exif_from_jpg.data = nullptr;
+ exif_from_jpg.length = 0;
+ jpegr_compressed_struct new_jpg_image;
+ new_jpg_image.data = nullptr;
+ new_jpg_image.length = 0;
+ if (decoder.getEXIFPos() != 0) {
+ if (pExif != nullptr) {
+ ALOGE("received EXIF from outside while the primary image already contains EXIF");
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ copyJpegWithoutExif(&new_jpg_image,
+ primary_jpg_image_ptr,
+ decoder.getEXIFPos(),
+ decoder.getEXIFSize());
+ exif_from_jpg.data = decoder.getEXIFPtr();
+ exif_from_jpg.length = decoder.getEXIFSize();
+ pExif = &exif_from_jpg;
+ }
+
+ jr_compressed_ptr final_primary_jpg_image_ptr =
+ new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
+
int pos = 0;
// Begin primary image
// Write SOI
@@ -1236,15 +1292,15 @@
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
// Write EXIF
- if (exif != nullptr) {
- const int length = 2 + exif->length;
+ if (pExif != nullptr) {
+ const int length = 2 + pExif->length;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
+ JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
}
// Prepare and write XMP
@@ -1261,7 +1317,7 @@
}
// Write ICC
- if (icc != nullptr && icc_size > 0) {
+ if (pIcc != nullptr && icc_size > 0) {
const int length = icc_size + 2;
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
@@ -1269,7 +1325,7 @@
JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
- JPEGR_CHECK(Write(dest, icc, icc_size, pos));
+ JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
}
// Prepare and write MPF
@@ -1277,7 +1333,7 @@
const int length = 2 + calculateMpfSize();
const uint8_t lengthH = ((length >> 8) & 0xff);
const uint8_t lengthL = (length & 0xff);
- int primary_image_size = pos + length + primary_jpg_image_ptr->length;
+ int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
// between APP2 + package size + signature
// ff e2 00 58 4d 50 46 00
// 2 + 2 + 4 = 8 (bytes)
@@ -1293,8 +1349,8 @@
}
// Write primary image
- JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
- primary_jpg_image_ptr->length - 2, pos));
+ JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
+ final_primary_jpg_image_ptr->length - 2, pos));
// Finish primary image
// Begin secondary image (gain map)