diff options
author | 2023-08-18 01:48:10 +0000 | |
---|---|---|
committer | 2023-08-29 23:05:32 +0000 | |
commit | 6b3cd9ac6ba7568c29570d492c1fedc980fba07f (patch) | |
tree | 7ee60bd1a5488e0616752e8a065cc814063fde63 | |
parent | ca78def79c655a89d73b974c56f5231493bdf0d6 (diff) |
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
-rw-r--r-- | libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h | 17 | ||||
-rw-r--r-- | libs/ultrahdr/include/ultrahdr/jpegr.h | 16 | ||||
-rw-r--r-- | libs/ultrahdr/jpegdecoderhelper.cpp | 90 | ||||
-rw-r--r-- | libs/ultrahdr/jpegr.cpp | 80 |
4 files changed, 169 insertions, 34 deletions
diff --git a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h index 1f0c38ff7f..30149c11f0 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h +++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h @@ -76,15 +76,27 @@ public: */ 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 @@ private: // 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 850cb32e75..114c81d818 100644 --- a/libs/ultrahdr/include/ultrahdr/jpegr.h +++ b/libs/ultrahdr/include/ultrahdr/jpegr.h @@ -366,22 +366,24 @@ private: * 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 7ccf1f3697..2e7940cc2c 100644 --- a/libs/ultrahdr/jpegdecoderhelper.cpp +++ b/libs/ultrahdr/jpegdecoderhelper.cpp @@ -32,12 +32,17 @@ const uint32_t kAPP0Marker = JPEG_APP0; // JFIF 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 @@ size_t JpegDecoderHelper::getDecompressedImageHeight() { 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 JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) 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 @@ bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int leng } 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 f27bd2d774..74760d9b32 100644 --- a/libs/ultrahdr/jpegr.cpp +++ b/libs/ultrahdr/jpegr.cpp @@ -72,6 +72,32 @@ int GetCPUCoreCount() { 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 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, // 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 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, // 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 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, // 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 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, // 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 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, 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 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, } // 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 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, 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 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, 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 @@ status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, } // 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) |