Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 61ede360 authored by Dichen Zhang's avatar Dichen Zhang
Browse files

jpegr library: add multi-picture format support

test: jpegr_test
bug: b/264715926
Change-Id: I1bd299ddc0435e54f7c8554b92b3cd6eb2f6eb2b
(cherry picked from commit 283c64b1)
parent 3fcbb3bf
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ cc_library {
        "jpegr.cpp",
        "recoverymapmath.cpp",
        "jpegrutils.cpp",
        "multipictureformat.cpp",
    ],

    shared_libs: [
@@ -40,6 +41,7 @@ cc_library {
        "libjpegencoder",
        "libjpegdecoder",
        "liblog",
        "libutils",
    ],

    static_libs: ["libskia"],
+55 −10
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@
#define ANDROID_JPEGRECOVERYMAP_JPEGRUTILS_H

#include <jpegrecoverymap/jpegr.h>
#include <utils/RefBase.h>

#include <sstream>
#include <stdint.h>
@@ -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.
@@ -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/"
@@ -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>
@@ -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>
@@ -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
+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
+103 −46
Original line number Diff line number Diff line
@@ -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>
@@ -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;
  }
@@ -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;
  }

@@ -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
@@ -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));
@@ -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));
@@ -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;
@@ -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;
  }
+108 −29
Original line number Diff line number Diff line
@@ -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.
 *
@@ -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.
 */
@@ -58,7 +109,7 @@ class XMPXmlHandler : public XmlHandler {
public:

    XMPXmlHandler() : XmlHandler() {
        gContainerItemState = NotStrarted;
        state = NotStrarted;
    }

    enum ParseState {
@@ -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;
                }
            }
        }
@@ -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();
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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
@@ -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";
@@ -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";
@@ -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});

@@ -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);
@@ -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