Loading libs/jpegrecoverymap/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ cc_library { "jpegr.cpp", "recoverymapmath.cpp", "jpegrutils.cpp", "multipictureformat.cpp", ], shared_libs: [ Loading @@ -40,6 +41,7 @@ cc_library { "libjpegencoder", "libjpegdecoder", "liblog", "libutils", ], static_libs: ["libskia"], Loading libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +55 −10 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H #include <jpegrecoverymap/jpegr.h> #include <utils/RefBase.h> #include <sstream> #include <stdint.h> Loading @@ -27,6 +28,26 @@ namespace android::jpegrecoverymap { struct jpegr_metadata; /* * Mutable data structure. Holds information for metadata. */ class DataStruct : public RefBase { private: void* data; int writePos; int length; ~DataStruct(); public: DataStruct(int s); void* getData(); int getLength(); int getBytesWritten(); bool write8(uint8_t value); bool write16(uint16_t value); bool write32(uint32_t value); bool write(const void* src, int size); }; /* * Helper function used for writing data to destination. Loading @@ -51,12 +72,10 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata); /* * This method generates XMP metadata. * This method generates XMP metadata for the primary image. * * below is an example of the XMP metadata that this function generates where * secondary_image_length = 1000 * max_content_boost = 8.0 * min_content_boost = 0.5 * * <x:xmpmeta * xmlns:x="adobe:ns:meta/" Loading @@ -65,8 +84,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> * <rdf:Description * xmlns:Container="http://ns.google.com/photos/1.0/container/" * xmlns:Item="http://ns.google.com/photos/1.0/container/item/" * xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/"> * xmlns:Item="http://ns.google.com/photos/1.0/container/item/"> * <Container:Directory> * <rdf:Seq> * <rdf:li> Loading @@ -78,10 +96,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * <Container:Item * Item:Semantic="RecoveryMap" * Item:Mime="image/jpeg" * Item:Length="1000" * RecoveryMap:Version="1" * RecoveryMap:MaxContentBoost="8.0" * RecoveryMap:MinContentBoost="0.5"/> * Item:Length="1000"/> * </rdf:li> * </rdf:Seq> * </Container:Directory> Loading @@ -90,10 +105,40 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * </x:xmpmeta> * * @param secondary_image_length length of secondary image * @return XMP metadata in type of string */ std::string generateXmpForPrimaryImage(int secondary_image_length); /* * This method generates XMP metadata for the recovery map image. * * below is an example of the XMP metadata that this function generates where * max_content_boost = 8.0 * min_content_boost = 0.5 * * <x:xmpmeta * xmlns:x="adobe:ns:meta/" * x:xmptk="Adobe XMP Core 5.1.2"> * <rdf:RDF * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> * <rdf:Description * xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" * hdrgm:Version="1" * hdrgm:GainMapMin="0.5" * hdrgm:GainMapMax="8.5" * hdrgm:Gamma="1" * hdrgm:OffsetSDR="0" * hdrgm:OffsetHDR="0" * hdrgm:HDRCapacityMin="0.5" * hdrgm:HDRCapacityMax="8.5" * hdrgm:BaseRendition="SDR"/> * </rdf:RDF> * </x:xmpmeta> * * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); std::string generateXmpForSecondaryImage(jpegr_metadata& metadata); } // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H #define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H #include <jpegrecoverymap/jpegrutils.h> namespace android::jpegrecoverymap { static constexpr uint32_t EndianSwap32(uint32_t value) { return ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) | (value >> 24); } static inline uint16_t EndianSwap16(uint16_t value) { return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8)); } #define USE_BIG_ENDIAN true #if USE_BIG_ENDIAN #define Endian_SwapBE32(n) EndianSwap32(n) #define Endian_SwapBE16(n) EndianSwap16(n) #else #define Endian_SwapBE32(n) (n) #define Endian_SwapBE16(n) (n) #endif constexpr size_t kNumPictures = 2; constexpr size_t kMpEndianSize = 4; constexpr uint16_t kTagSerializedCount = 3; constexpr uint32_t kTagSize = 12; constexpr uint16_t kTypeLong = 0x4; constexpr uint16_t kTypeUndefined = 0x7; static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'}; constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00}; constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A}; constexpr uint16_t kVersionTag = 0xB000; constexpr uint16_t kVersionType = kTypeUndefined; constexpr uint32_t kVersionCount = 4; constexpr size_t kVersionSize = 4; constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'}; constexpr uint16_t kNumberOfImagesTag = 0xB001; constexpr uint16_t kNumberOfImagesType = kTypeLong; constexpr uint32_t kNumberOfImagesCount = 1; constexpr uint16_t kMPEntryTag = 0xB002; constexpr uint16_t kMPEntryType = kTypeUndefined; constexpr uint32_t kMPEntrySize = 16; constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000; constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000; size_t calculateMpfSize(); sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, int secondary_image_size, int secondary_image_offset); } // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H libs/jpegrecoverymap/jpegr.cpp +103 −46 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <jpegrecoverymap/jpegdecoderhelper.h> #include <jpegrecoverymap/recoverymapmath.h> #include <jpegrecoverymap/jpegrutils.h> #include <jpegrecoverymap/multipictureformat.h> #include <image_io/jpeg/jpeg_marker.h> #include <image_io/jpeg/jpeg_info.h> Loading Loading @@ -327,8 +328,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } Loading Loading @@ -399,8 +399,8 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()), jpeg_decoder.getXMPSize(), &metadata)) { if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()), recovery_map_decoder.getXMPSize(), &metadata)) { return ERROR_JPEGR_DECODE_ERROR; } Loading Loading @@ -790,11 +790,22 @@ status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, // (Required, XMP package) APP1 (ff e1) // 2 bytes of length (2 + 29 + length of xmp package) // name space ("http://ns.adobe.com/xap/1.0/\0") // xmp // XMP // // (Required, MPF package) APP2 (ff e2) // 2 bytes of length // MPF // // (Required) primary image (without the first two bytes (SOI), may have other packages) // // (Required) secondary image (the recovery map) // SOI (ff d8) // // (Required, XMP package) APP1 (ff e1) // 2 bytes of length (2 + 29 + length of xmp package) // name space ("http://ns.adobe.com/xap/1.0/\0") // XMP // // (Required) secondary image (the recovery map, without the first two bytes (SOI)) // // Metadata versions we are using: // ECMA TR-98 for JFIF marker Loading @@ -815,6 +826,10 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, int pos = 0; const string xmp_primary = generateXmpForPrimaryImage(compressed_recovery_map->length); const string xmp_secondary = generateXmpForSecondaryImage(*metadata); // Begin primary image // 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)); Loading @@ -833,13 +848,12 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, // 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 = 2 + nameSpaceLength + xmp.size(); const int length = 2 + nameSpaceLength + xmp_primary.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)); Loading @@ -847,15 +861,59 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, 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)); JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); } // Prepare and write MPF { const int length = 2 + calculateMpfSize(); const uint8_t lengthH = ((length >> 8) & 0xff); const uint8_t lengthL = (length & 0xff); int primary_image_size = pos + length + compressed_jpeg_image->length; int secondary_image_offset = primary_image_size; int secondary_image_size = xmp_secondary.size() + compressed_recovery_map->length; sp<DataStruct> mpf = generateMpf(0, /* primary_image_offset */ primary_image_size, secondary_image_offset, secondary_image_size); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); 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, (void*)mpf->getData(), mpf->getLength(), pos)); } // Write primary image JPEGR_CHECK(Write(dest, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); // Finish primary image // Begin secondary image (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)); // Prepare and write XMP { 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 = 2 + nameSpaceLength + xmp_secondary.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_secondary.c_str(), xmp_secondary.size(), pos)); } // Write secondary image JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); JPEGR_CHECK(Write(dest, (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos)); // Set back length dest->length = pos; Loading @@ -864,8 +922,7 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return NO_ERROR; } status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { if (src == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } Loading libs/jpegrecoverymap/jpegrutils.cpp +108 −29 Original line number Diff line number Diff line Loading @@ -15,18 +15,19 @@ */ #include <jpegrecoverymap/jpegrutils.h> #include <utils/Log.h> #include <image_io/xml/xml_reader.h> #include <image_io/xml/xml_writer.h> #include <image_io/base/message_handler.h> #include <image_io/xml/xml_element_rules.h> #include <image_io/xml/xml_handler.h> #include <image_io/xml/xml_rule.h> #include <cmath> using namespace photos_editing_formats::image_io; using namespace std; namespace android::jpegrecoverymap { /* * Helper function used for generating XMP metadata. * Loading @@ -34,12 +35,62 @@ namespace android::jpegrecoverymap { * @param suffix The suffix part of the name. * @return A name of the form "prefix:suffix". */ string Name(const string &prefix, const string &suffix) { static inline string Name(const string &prefix, const string &suffix) { std::stringstream ss; ss << prefix << ":" << suffix; return ss.str(); } DataStruct::DataStruct(int s) { data = malloc(s); length = s; memset(data, 0, s); writePos = 0; } DataStruct::~DataStruct() { if (data != nullptr) { free(data); } } void* DataStruct::getData() { return data; } int DataStruct::getLength() { return length; } int DataStruct::getBytesWritten() { return writePos; } bool DataStruct::write8(uint8_t value) { uint8_t v = value; return write(&v, 1); } bool DataStruct::write16(uint16_t value) { uint16_t v = value; return write(&v, 2); } bool DataStruct::write32(uint32_t value) { uint32_t v = value; return write(&v, 4); } bool DataStruct::write(const void* src, int size) { if (writePos + size > length) { ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", writePos, size, length); return false; } memcpy((uint8_t*) data + writePos, src, size); writePos += size; return true; } /* * Helper function used for writing data to destination. */ Loading @@ -58,7 +109,7 @@ class XMPXmlHandler : public XmlHandler { public: XMPXmlHandler() : XmlHandler() { gContainerItemState = NotStrarted; state = NotStrarted; } enum ParseState { Loading @@ -70,11 +121,11 @@ public: virtual DataMatchResult StartElement(const XmlTokenContext& context) { string val; if (context.BuildTokenValue(&val)) { if (!val.compare(gContainerItemName)) { gContainerItemState = Started; if (!val.compare(containerName)) { state = Started; } else { if (gContainerItemState != Done) { gContainerItemState = NotStrarted; if (state != Done) { state = NotStrarted; } } } Loading @@ -82,8 +133,8 @@ public: } virtual DataMatchResult FinishElement(const XmlTokenContext& context) { if (gContainerItemState == Started) { gContainerItemState = Done; if (state == Started) { state = Done; lastAttributeName = ""; } return context.GetResult(); Loading @@ -91,7 +142,7 @@ public: virtual DataMatchResult AttributeName(const XmlTokenContext& context) { string val; if (gContainerItemState == Started) { if (state == Started) { if (context.BuildTokenValue(&val)) { if (!val.compare(maxContentBoostAttrName)) { lastAttributeName = maxContentBoostAttrName; Loading @@ -107,7 +158,7 @@ public: virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { string val; if (gContainerItemState == Started) { if (state == Started) { if (context.BuildTokenValue(&val, true)) { if (!lastAttributeName.compare(maxContentBoostAttrName)) { maxContentBoostStr = val; Loading @@ -120,11 +171,11 @@ public: } bool getMaxContentBoost(float* max_content_boost) { if (gContainerItemState == Done) { if (state == Done) { stringstream ss(maxContentBoostStr); float val; if (ss >> val) { *max_content_boost = val; *max_content_boost = exp2(val); return true; } else { return false; Loading @@ -135,11 +186,11 @@ public: } bool getMinContentBoost(float* min_content_boost) { if (gContainerItemState == Done) { if (state == Done) { stringstream ss(minContentBoostStr); float val; if (ss >> val) { *min_content_boost = val; *min_content_boost = exp2(val); return true; } else { return false; Loading @@ -150,13 +201,13 @@ public: } private: static const string gContainerItemName; static const string containerName; static const string maxContentBoostAttrName; string maxContentBoostStr; static const string minContentBoostAttrName; string minContentBoostStr; string lastAttributeName; ParseState gContainerItemState; ParseState state; }; // GContainer XMP constants - URI and namespace prefix Loading @@ -168,8 +219,7 @@ const string kConDirectory = Name(kContainerPrefix, "Directory"); const string kConItem = Name(kContainerPrefix, "Item"); // GContainer XMP constants - names for XMP handlers const string XMPXmlHandler::gContainerItemName = kConItem; const string XMPXmlHandler::containerName = "rdf:Description"; // Item XMP constants - URI and namespace prefix const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; const string kItemPrefix = "Item"; Loading @@ -185,17 +235,23 @@ const string kSemanticRecoveryMap = "RecoveryMap"; const string kMimeImageJpeg = "image/jpeg"; // RecoveryMap XMP constants - URI and namespace prefix const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/"; const string kRecoveryMapPrefix = "RecoveryMap"; const string kRecoveryMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; const string kRecoveryMapPrefix = "hdrgm"; // RecoveryMap XMP constants - element and attribute names const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost"); const string kMapMinContentBoost = Name(kRecoveryMapPrefix, "MinContentBoost"); const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin"); const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax"); const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma"); const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR"); const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR"); const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin"); const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax"); const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); // RecoveryMap XMP constants - names for XMP handlers const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost; const string XMPXmlHandler::minContentBoostAttrName = kMapMinContentBoost; const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; Loading Loading @@ -243,7 +299,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta return true; } string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { string generateXmpForPrimaryImage(int secondary_image_length) { const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector<string> kLiItem({string("rdf:li"), kConItem}); Loading @@ -257,7 +313,6 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kContainerPrefix, kContainerUri); writer.WriteXmlns(kItemPrefix, kItemUri); writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); writer.StartWritingElements(kConDirSeq); size_t item_depth = writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); Loading @@ -267,9 +322,33 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); writer.FinishWriting(); return ss.str(); } string generateXmpForSecondaryImage(jpegr_metadata& metadata) { const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector<string> kLiItem({string("rdf:li"), kConItem}); std::stringstream ss; photos_editing_formats::image_io::XmlWriter writer(ss); writer.StartWritingElement("x:xmpmeta"); writer.WriteXmlns("x", "adobe:ns:meta/"); writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); writer.StartWritingElement("rdf:RDF"); writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost); writer.WriteAttributeNameAndValue(kMapMinContentBoost, metadata.minContentBoost); writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); writer.WriteAttributeNameAndValue(kMapGamma, "1"); writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0"); writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3"); writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR"); writer.FinishWriting(); return ss.str(); Loading Loading
libs/jpegrecoverymap/Android.bp +2 −0 Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ cc_library { "jpegr.cpp", "recoverymapmath.cpp", "jpegrutils.cpp", "multipictureformat.cpp", ], shared_libs: [ Loading @@ -40,6 +41,7 @@ cc_library { "libjpegencoder", "libjpegdecoder", "liblog", "libutils", ], static_libs: ["libskia"], Loading
libs/jpegrecoverymap/include/jpegrecoverymap/jpegrutils.h +55 −10 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ #define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H #include <jpegrecoverymap/jpegr.h> #include <utils/RefBase.h> #include <sstream> #include <stdint.h> Loading @@ -27,6 +28,26 @@ namespace android::jpegrecoverymap { struct jpegr_metadata; /* * Mutable data structure. Holds information for metadata. */ class DataStruct : public RefBase { private: void* data; int writePos; int length; ~DataStruct(); public: DataStruct(int s); void* getData(); int getLength(); int getBytesWritten(); bool write8(uint8_t value); bool write16(uint16_t value); bool write32(uint32_t value); bool write(const void* src, int size); }; /* * Helper function used for writing data to destination. Loading @@ -51,12 +72,10 @@ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata); /* * This method generates XMP metadata. * This method generates XMP metadata for the primary image. * * below is an example of the XMP metadata that this function generates where * secondary_image_length = 1000 * max_content_boost = 8.0 * min_content_boost = 0.5 * * <x:xmpmeta * xmlns:x="adobe:ns:meta/" Loading @@ -65,8 +84,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> * <rdf:Description * xmlns:Container="http://ns.google.com/photos/1.0/container/" * xmlns:Item="http://ns.google.com/photos/1.0/container/item/" * xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/"> * xmlns:Item="http://ns.google.com/photos/1.0/container/item/"> * <Container:Directory> * <rdf:Seq> * <rdf:li> Loading @@ -78,10 +96,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * <Container:Item * Item:Semantic="RecoveryMap" * Item:Mime="image/jpeg" * Item:Length="1000" * RecoveryMap:Version="1" * RecoveryMap:MaxContentBoost="8.0" * RecoveryMap:MinContentBoost="0.5"/> * Item:Length="1000"/> * </rdf:li> * </rdf:Seq> * </Container:Directory> Loading @@ -90,10 +105,40 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta * </x:xmpmeta> * * @param secondary_image_length length of secondary image * @return XMP metadata in type of string */ std::string generateXmpForPrimaryImage(int secondary_image_length); /* * This method generates XMP metadata for the recovery map image. * * below is an example of the XMP metadata that this function generates where * max_content_boost = 8.0 * min_content_boost = 0.5 * * <x:xmpmeta * xmlns:x="adobe:ns:meta/" * x:xmptk="Adobe XMP Core 5.1.2"> * <rdf:RDF * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> * <rdf:Description * xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/" * hdrgm:Version="1" * hdrgm:GainMapMin="0.5" * hdrgm:GainMapMax="8.5" * hdrgm:Gamma="1" * hdrgm:OffsetSDR="0" * hdrgm:OffsetHDR="0" * hdrgm:HDRCapacityMin="0.5" * hdrgm:HDRCapacityMax="8.5" * hdrgm:BaseRendition="SDR"/> * </rdf:RDF> * </x:xmpmeta> * * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ std::string generateXmp(int secondary_image_length, jpegr_metadata& metadata); std::string generateXmpForSecondaryImage(jpegr_metadata& metadata); } // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H
libs/jpegrecoverymap/include/jpegrecoverymap/multipictureformat.h 0 → 100644 +76 −0 Original line number Diff line number Diff line /* * Copyright 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H #define ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H #include <jpegrecoverymap/jpegrutils.h> namespace android::jpegrecoverymap { static constexpr uint32_t EndianSwap32(uint32_t value) { return ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) | (value >> 24); } static inline uint16_t EndianSwap16(uint16_t value) { return static_cast<uint16_t>((value >> 8) | ((value & 0xFF) << 8)); } #define USE_BIG_ENDIAN true #if USE_BIG_ENDIAN #define Endian_SwapBE32(n) EndianSwap32(n) #define Endian_SwapBE16(n) EndianSwap16(n) #else #define Endian_SwapBE32(n) (n) #define Endian_SwapBE16(n) (n) #endif constexpr size_t kNumPictures = 2; constexpr size_t kMpEndianSize = 4; constexpr uint16_t kTagSerializedCount = 3; constexpr uint32_t kTagSize = 12; constexpr uint16_t kTypeLong = 0x4; constexpr uint16_t kTypeUndefined = 0x7; static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'}; constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00}; constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A}; constexpr uint16_t kVersionTag = 0xB000; constexpr uint16_t kVersionType = kTypeUndefined; constexpr uint32_t kVersionCount = 4; constexpr size_t kVersionSize = 4; constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'}; constexpr uint16_t kNumberOfImagesTag = 0xB001; constexpr uint16_t kNumberOfImagesType = kTypeLong; constexpr uint32_t kNumberOfImagesCount = 1; constexpr uint16_t kMPEntryTag = 0xB002; constexpr uint16_t kMPEntryType = kTypeUndefined; constexpr uint32_t kMPEntrySize = 16; constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000; constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000; size_t calculateMpfSize(); sp<DataStruct> generateMpf(int primary_image_size, int primary_image_offset, int secondary_image_size, int secondary_image_offset); } // namespace android::jpegrecoverymap #endif //ANDROID_JPEGRECOVERYMAP_MULTIPICTUREFORMAT_H
libs/jpegrecoverymap/jpegr.cpp +103 −46 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ #include <jpegrecoverymap/jpegdecoderhelper.h> #include <jpegrecoverymap/recoverymapmath.h> #include <jpegrecoverymap/jpegrutils.h> #include <jpegrecoverymap/multipictureformat.h> #include <image_io/jpeg/jpeg_marker.h> #include <image_io/jpeg/jpeg_info.h> Loading Loading @@ -327,8 +328,7 @@ status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image, return NO_ERROR; } status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) { if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } Loading Loading @@ -399,8 +399,8 @@ status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image, uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth(); uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight(); if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()), jpeg_decoder.getXMPSize(), &metadata)) { if (!getMetadataFromXMP(static_cast<uint8_t*>(recovery_map_decoder.getXMPPtr()), recovery_map_decoder.getXMPSize(), &metadata)) { return ERROR_JPEGR_DECODE_ERROR; } Loading Loading @@ -790,11 +790,22 @@ status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image, // (Required, XMP package) APP1 (ff e1) // 2 bytes of length (2 + 29 + length of xmp package) // name space ("http://ns.adobe.com/xap/1.0/\0") // xmp // XMP // // (Required, MPF package) APP2 (ff e2) // 2 bytes of length // MPF // // (Required) primary image (without the first two bytes (SOI), may have other packages) // // (Required) secondary image (the recovery map) // SOI (ff d8) // // (Required, XMP package) APP1 (ff e1) // 2 bytes of length (2 + 29 + length of xmp package) // name space ("http://ns.adobe.com/xap/1.0/\0") // XMP // // (Required) secondary image (the recovery map, without the first two bytes (SOI)) // // Metadata versions we are using: // ECMA TR-98 for JFIF marker Loading @@ -815,6 +826,10 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, int pos = 0; const string xmp_primary = generateXmpForPrimaryImage(compressed_recovery_map->length); const string xmp_secondary = generateXmpForSecondaryImage(*metadata); // Begin primary image // 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)); Loading @@ -833,13 +848,12 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, // 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 = 2 + nameSpaceLength + xmp.size(); const int length = 2 + nameSpaceLength + xmp_primary.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)); Loading @@ -847,15 +861,59 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, 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)); JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); } // Prepare and write MPF { const int length = 2 + calculateMpfSize(); const uint8_t lengthH = ((length >> 8) & 0xff); const uint8_t lengthL = (length & 0xff); int primary_image_size = pos + length + compressed_jpeg_image->length; int secondary_image_offset = primary_image_size; int secondary_image_size = xmp_secondary.size() + compressed_recovery_map->length; sp<DataStruct> mpf = generateMpf(0, /* primary_image_offset */ primary_image_size, secondary_image_offset, secondary_image_size); JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); 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, (void*)mpf->getData(), mpf->getLength(), pos)); } // Write primary image JPEGR_CHECK(Write(dest, (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos)); // Finish primary image // Begin secondary image (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)); // Prepare and write XMP { 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 = 2 + nameSpaceLength + xmp_secondary.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_secondary.c_str(), xmp_secondary.size(), pos)); } // Write secondary image JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos)); JPEGR_CHECK(Write(dest, (uint8_t*)compressed_recovery_map->data + 2, compressed_recovery_map->length - 2, pos)); // Set back length dest->length = pos; Loading @@ -864,8 +922,7 @@ status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image, return NO_ERROR; } status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) { if (src == nullptr || dest == nullptr) { return ERROR_JPEGR_INVALID_NULL_PTR; } Loading
libs/jpegrecoverymap/jpegrutils.cpp +108 −29 Original line number Diff line number Diff line Loading @@ -15,18 +15,19 @@ */ #include <jpegrecoverymap/jpegrutils.h> #include <utils/Log.h> #include <image_io/xml/xml_reader.h> #include <image_io/xml/xml_writer.h> #include <image_io/base/message_handler.h> #include <image_io/xml/xml_element_rules.h> #include <image_io/xml/xml_handler.h> #include <image_io/xml/xml_rule.h> #include <cmath> using namespace photos_editing_formats::image_io; using namespace std; namespace android::jpegrecoverymap { /* * Helper function used for generating XMP metadata. * Loading @@ -34,12 +35,62 @@ namespace android::jpegrecoverymap { * @param suffix The suffix part of the name. * @return A name of the form "prefix:suffix". */ string Name(const string &prefix, const string &suffix) { static inline string Name(const string &prefix, const string &suffix) { std::stringstream ss; ss << prefix << ":" << suffix; return ss.str(); } DataStruct::DataStruct(int s) { data = malloc(s); length = s; memset(data, 0, s); writePos = 0; } DataStruct::~DataStruct() { if (data != nullptr) { free(data); } } void* DataStruct::getData() { return data; } int DataStruct::getLength() { return length; } int DataStruct::getBytesWritten() { return writePos; } bool DataStruct::write8(uint8_t value) { uint8_t v = value; return write(&v, 1); } bool DataStruct::write16(uint16_t value) { uint16_t v = value; return write(&v, 2); } bool DataStruct::write32(uint32_t value) { uint32_t v = value; return write(&v, 4); } bool DataStruct::write(const void* src, int size) { if (writePos + size > length) { ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", writePos, size, length); return false; } memcpy((uint8_t*) data + writePos, src, size); writePos += size; return true; } /* * Helper function used for writing data to destination. */ Loading @@ -58,7 +109,7 @@ class XMPXmlHandler : public XmlHandler { public: XMPXmlHandler() : XmlHandler() { gContainerItemState = NotStrarted; state = NotStrarted; } enum ParseState { Loading @@ -70,11 +121,11 @@ public: virtual DataMatchResult StartElement(const XmlTokenContext& context) { string val; if (context.BuildTokenValue(&val)) { if (!val.compare(gContainerItemName)) { gContainerItemState = Started; if (!val.compare(containerName)) { state = Started; } else { if (gContainerItemState != Done) { gContainerItemState = NotStrarted; if (state != Done) { state = NotStrarted; } } } Loading @@ -82,8 +133,8 @@ public: } virtual DataMatchResult FinishElement(const XmlTokenContext& context) { if (gContainerItemState == Started) { gContainerItemState = Done; if (state == Started) { state = Done; lastAttributeName = ""; } return context.GetResult(); Loading @@ -91,7 +142,7 @@ public: virtual DataMatchResult AttributeName(const XmlTokenContext& context) { string val; if (gContainerItemState == Started) { if (state == Started) { if (context.BuildTokenValue(&val)) { if (!val.compare(maxContentBoostAttrName)) { lastAttributeName = maxContentBoostAttrName; Loading @@ -107,7 +158,7 @@ public: virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { string val; if (gContainerItemState == Started) { if (state == Started) { if (context.BuildTokenValue(&val, true)) { if (!lastAttributeName.compare(maxContentBoostAttrName)) { maxContentBoostStr = val; Loading @@ -120,11 +171,11 @@ public: } bool getMaxContentBoost(float* max_content_boost) { if (gContainerItemState == Done) { if (state == Done) { stringstream ss(maxContentBoostStr); float val; if (ss >> val) { *max_content_boost = val; *max_content_boost = exp2(val); return true; } else { return false; Loading @@ -135,11 +186,11 @@ public: } bool getMinContentBoost(float* min_content_boost) { if (gContainerItemState == Done) { if (state == Done) { stringstream ss(minContentBoostStr); float val; if (ss >> val) { *min_content_boost = val; *min_content_boost = exp2(val); return true; } else { return false; Loading @@ -150,13 +201,13 @@ public: } private: static const string gContainerItemName; static const string containerName; static const string maxContentBoostAttrName; string maxContentBoostStr; static const string minContentBoostAttrName; string minContentBoostStr; string lastAttributeName; ParseState gContainerItemState; ParseState state; }; // GContainer XMP constants - URI and namespace prefix Loading @@ -168,8 +219,7 @@ const string kConDirectory = Name(kContainerPrefix, "Directory"); const string kConItem = Name(kContainerPrefix, "Item"); // GContainer XMP constants - names for XMP handlers const string XMPXmlHandler::gContainerItemName = kConItem; const string XMPXmlHandler::containerName = "rdf:Description"; // Item XMP constants - URI and namespace prefix const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; const string kItemPrefix = "Item"; Loading @@ -185,17 +235,23 @@ const string kSemanticRecoveryMap = "RecoveryMap"; const string kMimeImageJpeg = "image/jpeg"; // RecoveryMap XMP constants - URI and namespace prefix const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/"; const string kRecoveryMapPrefix = "RecoveryMap"; const string kRecoveryMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; const string kRecoveryMapPrefix = "hdrgm"; // RecoveryMap XMP constants - element and attribute names const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost"); const string kMapMinContentBoost = Name(kRecoveryMapPrefix, "MinContentBoost"); const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin"); const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax"); const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma"); const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR"); const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR"); const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin"); const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax"); const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); // RecoveryMap XMP constants - names for XMP handlers const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost; const string XMPXmlHandler::minContentBoostAttrName = kMapMinContentBoost; const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; Loading Loading @@ -243,7 +299,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta return true; } string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { string generateXmpForPrimaryImage(int secondary_image_length) { const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector<string> kLiItem({string("rdf:li"), kConItem}); Loading @@ -257,7 +313,6 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kContainerPrefix, kContainerUri); writer.WriteXmlns(kItemPrefix, kItemUri); writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); writer.StartWritingElements(kConDirSeq); size_t item_depth = writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); Loading @@ -267,9 +322,33 @@ string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); writer.FinishWriting(); return ss.str(); } string generateXmpForSecondaryImage(jpegr_metadata& metadata) { const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector<string> kLiItem({string("rdf:li"), kConItem}); std::stringstream ss; photos_editing_formats::image_io::XmlWriter writer(ss); writer.StartWritingElement("x:xmpmeta"); writer.WriteXmlns("x", "adobe:ns:meta/"); writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2"); writer.StartWritingElement("rdf:RDF"); writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"); writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost); writer.WriteAttributeNameAndValue(kMapMinContentBoost, metadata.minContentBoost); writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); writer.WriteAttributeNameAndValue(kMapGamma, "1"); writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0"); writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3"); writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR"); writer.FinishWriting(); return ss.str(); Loading