diff options
author | 2022-12-16 20:55:24 +0000 | |
---|---|---|
committer | 2022-12-19 18:12:23 +0000 | |
commit | d18bc30fc49c0344fe119dc406a4ee80f830d20d (patch) | |
tree | 74946febba2ae514fb6854a753018973c9c63bed | |
parent | 15e721937ae6eaf7da0f394d31ecb15bbde03614 (diff) |
Update EXIF
Modify the EXIF package if it exists, or create a single-entry EXIF
package (API-1).
Extract / modify the first EXIF package (if it exists) or create a
single-entry EXIF package from the primary image (API-2, API-3).
Change the order of the EXIF and XMP package, new order:
SOI
EXIF
XMP
[other packages if they appears in the primary image]
primary image without EXIF
secondary image
Test: recoverymap_test and check the encoded file with a hex editor
Bug: b/252835416
Change-Id: I3d8c4a3fe3cff63f8b57277511aa801b9dd1b75d
-rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h | 37 | ||||
-rw-r--r-- | libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h | 2 | ||||
-rw-r--r-- | libs/jpegrecoverymap/jpegdecoder.cpp | 134 | ||||
-rw-r--r-- | libs/jpegrecoverymap/recoverymap.cpp | 290 |
4 files changed, 407 insertions, 56 deletions
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h index 5c9c8b6ec6..39c79c9695 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h @@ -47,7 +47,7 @@ public: */ void* getDecompressedImagePtr(); /* - * Returns the decompressed raw image buffer size. This method must be called only after + * Returns the decompressed raw image buffer size. This mgit ethod must be called only after * calling decompressImage(). */ size_t getDecompressedImageSize(); @@ -67,14 +67,35 @@ public: void* getXMPPtr(); /* * Returns the decompressed XMP buffer size. This method must be called only after - * calling decompressImage(). + * calling decompressImage() or getCompressedImageParameters(). */ size_t getXMPSize(); - + /* + * Returns the EXIF data from the image. + */ + void* getEXIFPtr(); + /* + * Returns the decompressed EXIF buffer size. This method must be called only after + * calling decompressImage() 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. + */ + int getEXIFPos() { return mExifPos; } + /* + * Decompresses metadata of the image. + */ bool getCompressedImageParameters(const void* image, int length, - size_t* pWidth, size_t* pHeight, - std::vector<uint8_t>* &iccData, - std::vector<uint8_t>* &exifData); + size_t* pWidth, size_t* pHeight, + std::vector<uint8_t>* &iccData, + std::vector<uint8_t>* &exifData); + /* + * Extracts EXIF package and updates the EXIF position / length without decoding the image. + */ + bool extractEXIF(const void* image, int length); private: bool decode(const void* image, int length); @@ -89,10 +110,14 @@ private: std::vector<JOCTET> mResultBuffer; // The buffer that holds XMP Data. std::vector<JOCTET> mXMPBuffer; + // The buffer that holds EXIF Data. + std::vector<JOCTET> mEXIFBuffer; // 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 */ diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h index 50ccdff6f7..905bf16c1a 100644 --- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h @@ -325,12 +325,14 @@ private: * * @param compressed_jpeg_image compressed 8-bit JPEG image * @param compress_recovery_map compressed recover map + * @param exif EXIF 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 appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, + jr_exif_ptr exif, jr_metadata_ptr metadata, jr_compressed_ptr dest); diff --git a/libs/jpegrecoverymap/jpegdecoder.cpp b/libs/jpegrecoverymap/jpegdecoder.cpp index 0185e55e9e..c2a8f45dbd 100644 --- a/libs/jpegrecoverymap/jpegdecoder.cpp +++ b/libs/jpegrecoverymap/jpegdecoder.cpp @@ -26,8 +26,12 @@ using namespace std; namespace android::recoverymap { -const uint32_t kExifMarker = JPEG_APP0 + 1; -const uint32_t kICCMarker = JPEG_APP0 + 2; +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"; struct jpegr_source_mgr : jpeg_source_mgr { jpegr_source_mgr(const uint8_t* ptr, int len); @@ -83,6 +87,7 @@ static void jpegrerror_exit(j_common_ptr cinfo) { } JpegDecoder::JpegDecoder() { + mExifPos = 0; } JpegDecoder::~JpegDecoder() { @@ -119,6 +124,13 @@ size_t JpegDecoder::getXMPSize() { return mXMPBuffer.size(); } +void* JpegDecoder::getEXIFPtr() { + return mEXIFBuffer.data(); +} + +size_t JpegDecoder::getEXIFSize() { + return mEXIFBuffer.size(); +} size_t JpegDecoder::getDecompressedImageWidth() { return mWidth; @@ -132,7 +144,6 @@ bool JpegDecoder::decode(const void* image, int length) { jpeg_decompress_struct cinfo; jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); jpegrerror_mgr myerr; - string nameSpace = "http://ns.adobe.com/xap/1.0/"; cinfo.err = jpeg_std_error(&myerr.pub); myerr.pub.error_exit = jpegrerror_exit; @@ -143,25 +154,58 @@ bool JpegDecoder::decode(const void* image, int length) { } jpeg_create_decompress(&cinfo); - jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); cinfo.src = &mgr; jpeg_read_header(&cinfo, TRUE); - // Save XMP Data - for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) { - if (marker->marker == kExifMarker) { - const unsigned int len = marker->data_length; - if (len > nameSpace.size() && - !strncmp(reinterpret_cast<const char*>(marker->data), - nameSpace.c_str(), nameSpace.size())) { - mXMPBuffer.resize(len+1, 0); - memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len); - break; - } + // Save XMP data and EXIF data. + // Here we only handle the first XMP / EXIF package. + // The parameter pos is used for capturing start offset of EXIF, which is hacky, but working... + // 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. The pos is adding up all previous package lengths ( + // 4 bytes marker and length, marker->original_length) before EXIF appears. Note that here we + // we are using marker->original_length instead of marker->data_length because in case the real + // package length is larger than the limitation, jpeg-turbo will only copy the data within the + // limitation (represented by data_length) and this may vary from original_length / real offset. + // A better solution is making jpeg_marker_struct holding the offset, but currently it doesn't. + bool exifAppears = false; + bool xmpAppears = false; + size_t pos = 2; // position after SOI + for (jpeg_marker_struct* marker = cinfo.marker_list; + marker && !(exifAppears && xmpAppears); + marker = marker->next) { + + pos += 4; + pos += marker->original_length; + + if (marker->marker != kAPP1Marker) { + 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); + 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())) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); + exifAppears = true; + mExifPos = pos - marker->original_length; + } + } mWidth = cinfo.image_width; mHeight = cinfo.image_height; @@ -189,6 +233,60 @@ bool JpegDecoder::decode(const void* image, int length) { return true; } +// TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have +// similar functionality. Yet Dichen is not familiar with who's calling +// getCompressedImageParameters(), looks like it's used by some pending CLs. +bool JpegDecoder::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); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); + + cinfo.src = &mgr; + jpeg_read_header(&cinfo, TRUE); + + bool exifAppears = false; + size_t pos = 2; // position after SOI + for (jpeg_marker_struct* marker = cinfo.marker_list; + marker && !exifAppears; + marker = marker->next) { + + pos += 4; + pos += marker->original_length; + + if (marker->marker != kAPP1Marker) { + continue; + } + + const unsigned int len = marker->data_length; + if (!exifAppears && + len > kExifIdCode.size() && + !strncmp(reinterpret_cast<const char*>(marker->data), + kExifIdCode.c_str(), + kExifIdCode.size())) { + mEXIFBuffer.resize(len, 0); + memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); + exifAppears = true; + mExifPos = pos - marker->original_length; + } + } + + jpeg_destroy_decompress(&cinfo); + return true; +} + bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, bool isSingleChannel) { if (isSingleChannel) { @@ -212,8 +310,8 @@ bool JpegDecoder::getCompressedImageParameters(const void* image, int length, } jpeg_create_decompress(&cinfo); - jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF); - jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); + jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); cinfo.src = &mgr; if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp index fafc319271..53fa8ce9e3 100644 --- a/libs/jpegrecoverymap/recoverymap.cpp +++ b/libs/jpegrecoverymap/recoverymap.cpp @@ -82,12 +82,122 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, return NO_ERROR; } +status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) { + memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); + position += length; + return NO_ERROR; +} + +// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry +// where the length is represented by this value. +const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28; +// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is +// represented by this value. +const size_t EXIF_J_R_ENTRY_LENGTH = 12; + +/* + * Helper function + * Add J R entry to existing exif, or create a new one with J R entry if it's null. + * EXIF syntax / change: + * ori: + * FF E1 - APP1 + * 01 FC - size of APP1 (to be calculated) + * ----------------------------------------------------- + * 45 78 69 66 00 00 - Exif\0\0 "Exif header" + * 49 49 2A 00 - TIFF Header + * 08 00 00 00 - offset to the IFD (image file directory) + * 06 00 - 6 entries + * 00 01 - Width Tag + * 03 00 - 'Short' type + * 01 00 00 00 - one entry + * 00 05 00 00 - image with 0x500 + *-------------------------------------------------------------------------- + * new: + * FF E1 - APP1 + * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12) + *----------------------------------------------------- + * 45 78 69 66 00 00 - Exif\0\0 "Exif header" + * 49 49 2A 00 - TIFF Header + * 08 00 00 00 - offset to the IFD (image file directory) + * 07 00 - +1 entry + * 4A 52 Custom ('J''R') Tag + * 07 00 - Unknown type + * 01 00 00 00 - one element + * 00 00 00 00 - empty data + * 00 01 - Width Tag + * 03 00 - 'Short' type + * 01 00 00 00 - one entry + * 00 05 00 00 - image with 0x500 + */ +status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) { + if (exif == nullptr || exif->data == nullptr) { + uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = { + 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, + 0x49, 0x49, 0x2A, 0x00, + 0x08, 0x00, 0x00, 0x00, + 0x01, 0x00, + 0x4A, 0x52, + 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + int pos = 0; + Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos); + return NO_ERROR; + } + + int num_entry = 0; + uint8_t num_entry_low = 0; + uint8_t num_entry_high = 0; + bool use_big_endian = false; + if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) { + num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14]; + num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15]; + } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) { + use_big_endian = true; + num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14]; + num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15]; + } else { + return ERROR_JPEGR_METADATA_ERROR; + } + num_entry = (num_entry_high << 8) | num_entry_low; + num_entry += 1; + num_entry_low = num_entry & 0xff; + num_entry_high = (num_entry << 8) & 0xff; + + int pos = 0; + Write(dest, (uint8_t*)exif->data, 14, pos); + + if (use_big_endian) { + Write(dest, &num_entry_high, 1, pos); + Write(dest, &num_entry_low, 1, pos); + uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { + 0x4A, 0x52, + 0x07, 0x00, + 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); + } else { + Write(dest, &num_entry_low, 1, pos); + Write(dest, &num_entry_high, 1, pos); + uint8_t data[EXIF_J_R_ENTRY_LENGTH] = { + 0x4A, 0x52, + 0x00, 0x07, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00}; + Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos); + } + + Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos); + + return NO_ERROR; +} + /* Encode API-0 */ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, - jr_exif_ptr /* exif */) { + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } @@ -129,7 +239,18 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest)); + jpegr_exif_struct new_exif; + if (exif->data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH; + } + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr<uint8_t[]> new_exif_data; + new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data)); + JPEGR_CHECK(updateExif(exif, &new_exif)); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -140,7 +261,7 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpegr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, - jr_exif_ptr /* exif */) { + jr_exif_ptr exif) { if (uncompressed_p010_image == nullptr || uncompressed_yuv_420_image == nullptr || dest == nullptr) { @@ -186,7 +307,19 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, jpeg.data = jpeg_encoder.getCompressedImagePtr(); jpeg.length = jpeg_encoder.getCompressedImageSize(); - JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest)); + jpegr_exif_struct new_exif; + if (exif == nullptr || exif->data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH; + } + + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr<uint8_t[]> new_exif_data; + new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data)); + JPEGR_CHECK(updateExif(exif, &new_exif)); + + JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -228,7 +361,41 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest)); + // Extract EXIF from JPEG without decoding. + JpegDecoder jpeg_decoder; + if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) { + return ERROR_JPEGR_DECODE_ERROR; + } + + jpegr_exif_struct exif; + exif.data = nullptr; + exif.length = 0; + // Delete EXIF package if it appears, and update exif. + if (jpeg_decoder.getEXIFPos() != 0) { + int new_length = compressed_jpeg_image->length - jpeg_decoder.getEXIFSize() - 4; + memcpy((uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() - 4, + (uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() + + jpeg_decoder.getEXIFSize(), + compressed_jpeg_image->length - jpeg_decoder.getEXIFPos() - jpeg_decoder.getEXIFSize()); + compressed_jpeg_image->length = new_length; + exif.data = jpeg_decoder.getEXIFPtr(); + exif.length = jpeg_decoder.getEXIFSize(); + } + + jpegr_exif_struct new_exif; + if (exif.data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH; + } + + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr<uint8_t[]> new_exif_data; + new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data)); + JPEGR_CHECK(updateExif(&exif, &new_exif)); + + JPEGR_CHECK(appendRecoveryMap( + compressed_jpeg_image, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -254,6 +421,32 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut; + jpegr_exif_struct exif; + exif.data = nullptr; + exif.length = 0; + // Delete EXIF package if it appears, and update exif. + if (jpeg_decoder.getEXIFPos() != 0) { + int new_length = compressed_jpeg_image->length - jpeg_decoder.getEXIFSize() - 4; + memcpy((uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() - 4, + (uint8_t*)compressed_jpeg_image->data + jpeg_decoder.getEXIFPos() + + jpeg_decoder.getEXIFSize(), + compressed_jpeg_image->length - jpeg_decoder.getEXIFPos() - jpeg_decoder.getEXIFSize()); + compressed_jpeg_image->length = new_length; + exif.data = jpeg_decoder.getEXIFPtr(); + exif.length = jpeg_decoder.getEXIFSize(); + } + + jpegr_exif_struct new_exif; + if (exif.data == nullptr) { + new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH; + } else { + new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH; + } + new_exif.data = new uint8_t[new_exif.length]; + std::unique_ptr<uint8_t[]> new_exif_data; + new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data)); + JPEGR_CHECK(updateExif(&exif, &new_exif)); + if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) { return ERROR_JPEGR_RESOLUTION_MISMATCH; @@ -278,7 +471,8 @@ status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, compressed_map.data = compressed_map_data.get(); JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map)); - JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest)); + JPEGR_CHECK(appendRecoveryMap( + compressed_jpeg_image, &compressed_map, &new_exif, &metadata, dest)); return NO_ERROR; } @@ -614,52 +808,84 @@ status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_imag return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest); } +// JPEG/R structure: +// SOI (ff d8) +// APP1 (ff e1) +// 2 bytes of length (2 + length of exif package) +// EXIF package (this includes the first two bytes representing the package length) +// APP1 (ff e1) +// 2 bytes of length (2 + 29 + length of xmp package) +// name space ("http://ns.adobe.com/xap/1.0/\0") +// xmp +// primary image (without the first two bytes (SOI) and without EXIF, may have other packages) +// secondary image (the recovery map) +// +// Metadata versions we are using: +// ECMA TR-98 for JFIF marker +// Exif 2.2 spec for EXIF marker +// Adobe XMP spec part 3 for XMP marker +// ICC v4.3 spec for ICC status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, jr_compressed_ptr compressed_recovery_map, + jr_exif_ptr exif, jr_metadata_ptr metadata, jr_compressed_ptr dest) { if (compressed_jpeg_image == nullptr || compressed_recovery_map == nullptr + || exif == nullptr || metadata == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } - const string xmp = generateXmp(compressed_recovery_map->length, *metadata); - const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; - const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator - - // 2 bytes: APP1 sign (ff e1) - // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", - // x bytes: length of xmp packet - - const int length = 3 + nameSpaceLength + xmp.size(); - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - int pos = 0; - // JPEG/R structure: - // SOI (ff d8) - // APP1 (ff e1) - // 2 bytes of length (2 + 29 + length of xmp packet) - // name space ("http://ns.adobe.com/xap/1.0/\0") - // xmp - // primary image (without the first two bytes, the SOI sign) - // secondary image (the recovery map) + // Write SOI JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); - 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, (void*)nameSpace.c_str(), nameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + + // Write EXIF + { + const int length = 2 + exif->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)); + } + + // Prepare and write XMP + { + const string xmp = generateXmp(compressed_recovery_map->length, *metadata); + const string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; + const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator + // 2 bytes: representing the length of the package + // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0", + // x bytes: length of xmp packet + const int length = 3 + nameSpaceLength + xmp.size(); + 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, (void*)nameSpace.c_str(), nameSpaceLength, pos)); + JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos)); + } + + // Write primary image JPEGR_CHECK(Write(dest, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); + + // Write secondary image JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); + + // Set back length dest->length = pos; + // Done! return NO_ERROR; } |