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

Commit dc8452b8 authored by Dichen Zhang's avatar Dichen Zhang
Browse files

Update XMP metadata

Bug: b/252835416
Change-Id: Iced2c5bf6b5e2dfd7139c43fad6b0bca0d1b2d1c
Test: build, print generated xmp
parent 42afe5fa
Loading
Loading
Loading
Loading
+3 −43
Original line number Diff line number Diff line
@@ -30,8 +30,9 @@ typedef enum {

// Transfer functions as defined for XMP metadata
typedef enum {
  JPEGR_TF_HLG = 0,
  JPEGR_TF_PQ = 1,
  JPEGR_TF_LINEAR = 0,
  JPEGR_TF_HLG = 1,
  JPEGR_TF_PQ = 2,
} jpegr_transfer_function;

struct jpegr_info_struct {
@@ -343,47 +344,6 @@ private:
                               jr_metadata_ptr metadata,
                               jr_compressed_ptr dest);

    /*
     * This method generates XMP metadata.
     *
     * below is an example of the XMP metadata that this function generates where
     * secondary_image_length = 1000
     * range_scaling_factor = 1.25
     *
     * <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:GContainer="http://ns.google.com/photos/1.0/container/">
     *       <GContainer:Version>1</GContainer:Version>
     *       <GContainer:RangeScalingFactor>1.25</GContainer:RangeScalingFactor>
     *       <GContainer:Directory>
     *         <rdf:Seq>
     *           <rdf:li>
     *             <GContainer:Item
     *               Item:Semantic="Primary"
     *               Item:Mime="image/jpeg"/>
     *           </rdf:li>
     *           <rdf:li>
     *             <GContainer:Item
     *               Item:Semantic="RecoveryMap"
     *               Item:Mime="image/jpeg"
     *               Item:Length="1000"/>
     *           </rdf:li>
     *         </rdf:Seq>
     *       </GContainer:Directory>
     *     </rdf:Description>
     *   </rdf:RDF>
     * </x:xmpmeta>
     *
     * @param secondary_image_length length of secondary image
     * @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);

    /*
     * This method will tone map a HDR image to an SDR image.
     *
+49 −1
Original line number Diff line number Diff line
@@ -17,10 +17,11 @@
#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H

#include <sstream>
#include <stdint.h>
#include <string>
#include <cstdio>


namespace android::recoverymap {

struct jpegr_metadata;
@@ -35,6 +36,53 @@ struct jpegr_metadata;
*/
bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata);

/*
 * This method generates XMP metadata.
 *
 * below is an example of the XMP metadata that this function generates where
 * secondary_image_length = 1000
 * range_scaling_factor = 1.25
 *
 * <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:GContainer="http://ns.google.com/photos/1.0/container/"
 *       xmlns:RecoveryMap="http://ns.google.com/photos/1.0/recoverymap/">
 *       <GContainer:Version>1</GContainer:Version>
 *       <GContainer:Directory>
 *         <rdf:Seq>
 *           <rdf:li>
 *             <GContainer:Item
 *               Item:Semantic="Primary"
 *               Item:Mime="image/jpeg"
 *               RecoveryMap:Version=”1”
 *               RecoveryMap:RangeScalingFactor=”1.25”
 *               RecoveryMap:TransferFunction=”2”/>
 *               <RecoveryMap:HDR10Metadata
 *                 // some attributes
 *                 // some elements
 *               </RecoveryMap:HDR10Metadata>
 *           </rdf:li>
 *           <rdf:li>
 *             <GContainer:Item
 *               Item:Semantic="RecoveryMap"
 *               Item:Mime="image/jpeg"
 *               Item:Length="1000"/>
 *           </rdf:li>
 *         </rdf:Seq>
 *       </GContainer:Directory>
 *     </rdf:Description>
 *   </rdf:RDF>
 * </x:xmpmeta>
 *
 * @param secondary_image_length length of secondary image
 * @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);
}

#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
 No newline at end of file
+6 −65
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@
#include <jpegrecoverymap/recoverymaputils.h>

#include <image_io/jpeg/jpeg_marker.h>
#include <image_io/xml/xml_writer.h>
#include <image_io/jpeg/jpeg_info.h>
#include <image_io/jpeg/jpeg_scanner.h>
#include <image_io/jpeg/jpeg_info_builder.h>
@@ -64,19 +63,6 @@ static const st2086_metadata kSt2086Metadata = {
  1.0f,
};

/*
 * Helper function used for generating XMP metadata.
 *
 * @param prefix The prefix part of the name.
 * @param suffix The suffix part of the name.
 * @return A name of the form "prefix:suffix".
 */
string Name(const string &prefix, const string &suffix) {
  std::stringstream ss;
  ss << prefix << ":" << suffix;
  return ss.str();
}

/*
 * Helper function used for writing data to destination.
 *
@@ -447,6 +433,9 @@ status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_4
  ColorTransformFn hdrInvOetf = nullptr;
  float hdr_white_nits = 0.0f;
  switch (metadata->transferFunction) {
    case JPEGR_TF_LINEAR:
      hdrInvOetf = identityConversion;
      break;
    case JPEGR_TF_HLG:
      hdrInvOetf = hlgInvOetf;
      hdr_white_nits = kHlgMaxNits;
@@ -544,6 +533,9 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_

  ColorTransformFn hdrOetf = nullptr;
  switch (metadata->transferFunction) {
    case JPEGR_TF_LINEAR:
      hdrOetf = identityConversion;
      break;
    case JPEGR_TF_HLG:
      hdrOetf = hlgOetf;
      break;
@@ -680,57 +672,6 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
  return NO_ERROR;
}

string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
  const string kContainerPrefix = "GContainer";
  const string kContainerUri    = "http://ns.google.com/photos/1.0/container/";
  const string kItemPrefix      = "Item";
  const string kRecoveryMap     = "RecoveryMap";
  const string kDirectory       = "Directory";
  const string kImageJpeg       = "image/jpeg";
  const string kItem            = "Item";
  const string kLength          = "Length";
  const string kMime            = "Mime";
  const string kPrimary         = "Primary";
  const string kSemantic        = "Semantic";
  const string kVersion         = "Version";

  const string kConDir          = Name(kContainerPrefix, kDirectory);
  const string kContainerItem   = Name(kContainerPrefix, kItem);
  const string kItemLength      = Name(kItemPrefix, kLength);
  const string kItemMime        = Name(kItemPrefix, kMime);
  const string kItemSemantic    = Name(kItemPrefix, kSemantic);

  const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
  const vector<string> kLiItem({string("rdf:li"), kContainerItem});

  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(kContainerPrefix, kContainerUri);
  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
  writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
                                metadata.rangeScalingFactor);
  // TODO: determine structure for hdr10 metadata
  // TODO: write rest of metadata
  writer.StartWritingElements(kConDirSeq);
  size_t item_depth = writer.StartWritingElements(kLiItem);
  writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
  writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
  writer.FinishWritingElementsToDepth(item_depth);
  writer.StartWritingElements(kLiItem);
  writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
  writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
  writer.FinishWriting();

  return ss.str();
}

status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
                              jr_uncompressed_ptr dest) {
  if (uncompressed_p010_image == nullptr || dest == nullptr) {
+135 −3
Original line number Diff line number Diff line
@@ -17,19 +17,29 @@
#include <jpegrecoverymap/recoverymaputils.h>
#include <jpegrecoverymap/recoverymap.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 <string>
#include <sstream>

using namespace photos_editing_formats::image_io;
using namespace std;

namespace android::recoverymap {

/*
 * Helper function used for generating XMP metadata.
 *
 * @param prefix The prefix part of the name.
 * @param suffix The suffix part of the name.
 * @return A name of the form "prefix:suffix".
 */
string Name(const string &prefix, const string &suffix) {
  std::stringstream ss;
  ss << prefix << ":" << suffix;
  return ss.str();
}

// Extremely simple XML Handler - just searches for interesting elements
class XMPXmlHandler : public XmlHandler {
@@ -104,6 +114,36 @@ private:

const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor";

const string kContainerPrefix   = "GContainer";
const string kContainerUri      = "http://ns.google.com/photos/1.0/container/";
const string kRecoveryMapUri    = "http://ns.google.com/photos/1.0/recoverymap/";
const string kItemPrefix        = "Item";
const string kRecoveryMap       = "RecoveryMap";
const string kDirectory         = "Directory";
const string kImageJpeg         = "image/jpeg";
const string kItem              = "Item";
const string kLength            = "Length";
const string kMime              = "Mime";
const string kPrimary           = "Primary";
const string kSemantic          = "Semantic";
const string kVersion           = "Version";
const string kHdr10Metadata     = "HDR10Metadata";
const string kSt2086Metadata    = "ST2086Metadata";
const string kSt2086Coordinate  = "ST2086Coordinate";
const string kSt2086CoordinateX = "ST2086CoordinateX";
const string kSt2086CoordinateY = "ST2086CoordinateY";
const string kSt2086Primary     = "ST2086Primary";
const int kSt2086PrimaryRed     = 0;
const int kSt2086PrimaryGreen   = 1;
const int kSt2086PrimaryBlue    = 2;
const int kSt2086PrimaryWhite   = 3;
const int kGContainerVersion    = 1;

const string kConDir            = Name(kContainerPrefix, kDirectory);
const string kContainerItem     = Name(kContainerPrefix, kItem);
const string kItemLength        = Name(kItemPrefix, kLength);
const string kItemMime          = Name(kItemPrefix, kMime);
const string kItemSemantic      = Name(kItemPrefix, kSemantic);

bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
@@ -150,4 +190,96 @@ bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* meta
    return true;
}

string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
  const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
  const vector<string> kLiItem({string("rdf:li"), kContainerItem});

  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(kContainerPrefix, kContainerUri);
  writer.WriteXmlns(kRecoveryMap, kRecoveryMapUri);
  writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kGContainerVersion);
  writer.StartWritingElements(kConDirSeq);
  size_t item_depth = writer.StartWritingElements(kLiItem);
  writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
  writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
  writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kVersion), metadata.version);
  writer.WriteAttributeNameAndValue(
      Name(kRecoveryMap, "RangeScalingFactor"), metadata.rangeScalingFactor);
  writer.WriteAttributeNameAndValue(
      Name(kRecoveryMap, "TransferFunction"), metadata.transferFunction);
  if (metadata.transferFunction == JPEGR_TF_PQ) {
    writer.StartWritingElement(Name(kRecoveryMap, kHdr10Metadata));
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, "HDR10MaxFALL"), metadata.hdr10Metadata.maxFALL);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, "HDR10MaxCLL"), metadata.hdr10Metadata.maxCLL);
    writer.StartWritingElement(Name(kRecoveryMap, kSt2086Metadata));
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, "ST2086MaxLuminance"),
        metadata.hdr10Metadata.st2086Metadata.maxLuminance);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, "ST2086MinLuminance"),
        metadata.hdr10Metadata.st2086Metadata.minLuminance);

    // red
    writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
    writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryRed);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateX),
        metadata.hdr10Metadata.st2086Metadata.redPrimary.x);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateY),
        metadata.hdr10Metadata.st2086Metadata.redPrimary.y);
    writer.FinishWritingElement();

    // green
    writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
    writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryGreen);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateX),
        metadata.hdr10Metadata.st2086Metadata.greenPrimary.x);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateY),
        metadata.hdr10Metadata.st2086Metadata.greenPrimary.y);
    writer.FinishWritingElement();

    // blue
    writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
    writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryBlue);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateX),
        metadata.hdr10Metadata.st2086Metadata.bluePrimary.x);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateY),
        metadata.hdr10Metadata.st2086Metadata.bluePrimary.y);
    writer.FinishWritingElement();

    // white
    writer.StartWritingElement(Name(kRecoveryMap, kSt2086Coordinate));
    writer.WriteAttributeNameAndValue(Name(kRecoveryMap, kSt2086Primary), kSt2086PrimaryWhite);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateX),
        metadata.hdr10Metadata.st2086Metadata.whitePoint.x);
    writer.WriteAttributeNameAndValue(
        Name(kRecoveryMap, kSt2086CoordinateY),
        metadata.hdr10Metadata.st2086Metadata.whitePoint.y);
    writer.FinishWritingElement();
  }
  writer.FinishWritingElementsToDepth(item_depth);
  writer.StartWritingElements(kLiItem);
  writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
  writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
  writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
  writer.FinishWriting();

  return ss.str();
}

} // namespace android::recoverymap
 No newline at end of file
+14 −0
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */

#include <jpegrecoverymap/recoverymap.h>
#include <jpegrecoverymap/recoverymaputils.h>
#include <fcntl.h>
#include <fstream>
#include <gtest/gtest.h>
@@ -94,6 +95,19 @@ TEST_F(RecoveryMapTest, build) {
  recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
}

TEST_F(RecoveryMapTest, writeXmpThenRead) {
  jpegr_metadata metadata_expected;
  metadata_expected.transferFunction = JPEGR_TF_HLG;
  metadata_expected.rangeScalingFactor = 1.25;
  int length_expected = 1000;
  std::string xmp = generateXmp(1000, metadata_expected);

  jpegr_metadata metadata_read;
  EXPECT_TRUE(getMetadataFromXMP(reinterpret_cast<uint8_t*>(xmp[0]), xmp.size(), &metadata_read));
  ASSERT_EQ(metadata_expected.transferFunction, metadata_read.transferFunction);
  ASSERT_EQ(metadata_expected.rangeScalingFactor, metadata_read.rangeScalingFactor);

}
TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
  int ret;