Loading libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +31 −6 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); /* * 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); Loading @@ -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 */ Loading libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading libs/jpegrecoverymap/jpegdecoder.cpp +116 −18 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -83,6 +87,7 @@ static void jpegrerror_exit(j_common_ptr cinfo) { } JpegDecoder::JpegDecoder() { mExifPos = 0; } JpegDecoder::~JpegDecoder() { Loading Loading @@ -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; Loading @@ -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; Loading @@ -143,26 +154,59 @@ 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) { // 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 (len > nameSpace.size() && if (!xmpAppears && len > kXmpNameSpace.size() && !strncmp(reinterpret_cast<const char*>(marker->data), nameSpace.c_str(), nameSpace.size())) { kXmpNameSpace.c_str(), kXmpNameSpace.size())) { mXMPBuffer.resize(len+1, 0); memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len); break; } 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; Loading @@ -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) { Loading @@ -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) { Loading libs/jpegrecoverymap/recoverymap.cpp +258 −32 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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; } int pos = 0; // 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)); // 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: APP1 sign (ff e1) // 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); 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) 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 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; } Loading Loading
libs/jpegrecoverymap/include/jpegrecoverymap/jpegdecoder.h +31 −6 Original line number Diff line number Diff line Loading @@ -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(); Loading @@ -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); /* * 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); Loading @@ -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 */ Loading
libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h +2 −0 Original line number Diff line number Diff line Loading @@ -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); Loading
libs/jpegrecoverymap/jpegdecoder.cpp +116 −18 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -83,6 +87,7 @@ static void jpegrerror_exit(j_common_ptr cinfo) { } JpegDecoder::JpegDecoder() { mExifPos = 0; } JpegDecoder::~JpegDecoder() { Loading Loading @@ -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; Loading @@ -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; Loading @@ -143,26 +154,59 @@ 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) { // 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 (len > nameSpace.size() && if (!xmpAppears && len > kXmpNameSpace.size() && !strncmp(reinterpret_cast<const char*>(marker->data), nameSpace.c_str(), nameSpace.size())) { kXmpNameSpace.c_str(), kXmpNameSpace.size())) { mXMPBuffer.resize(len+1, 0); memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len); break; } 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; Loading @@ -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) { Loading @@ -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) { Loading
libs/jpegrecoverymap/recoverymap.cpp +258 −32 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; Loading @@ -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; } Loading Loading @@ -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; } int pos = 0; // 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)); // 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: APP1 sign (ff e1) // 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); 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) 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 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; } Loading