Update XMP metadata

Bug: b/252835416
Change-Id: Iced2c5bf6b5e2dfd7139c43fad6b0bca0d1b2d1c
Test: build, print generated xmp
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
index 74f9776..fca78a0 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymap.h
@@ -30,8 +30,9 @@
 
 // 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 {
@@ -344,47 +345,6 @@
                                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.
      *
      * @param uncompressed_p010_image (input) uncompressed P010 image
diff --git a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
index e35f2d7..e61d0c4 100644
--- a/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
+++ b/libs/jpegrecoverymap/include/jpegrecoverymap/recoverymaputils.h
@@ -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 @@
 */
 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
diff --git a/libs/jpegrecoverymap/recoverymap.cpp b/libs/jpegrecoverymap/recoverymap.cpp
index c9ac921..3d713ce 100644
--- a/libs/jpegrecoverymap/recoverymap.cpp
+++ b/libs/jpegrecoverymap/recoverymap.cpp
@@ -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>
@@ -65,19 +64,6 @@
 };
 
 /*
- * 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.
  *
  * @param destination destination of the data to be written.
@@ -447,6 +433,9 @@
   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 @@
 
   ColorTransformFn hdrOetf = nullptr;
   switch (metadata->transferFunction) {
+    case JPEGR_TF_LINEAR:
+      hdrOetf = identityConversion;
+      break;
     case JPEGR_TF_HLG:
       hdrOetf = hlgOetf;
       break;
@@ -680,57 +672,6 @@
   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) {
diff --git a/libs/jpegrecoverymap/recoverymaputils.cpp b/libs/jpegrecoverymap/recoverymaputils.cpp
index fe46cba..537f86f 100644
--- a/libs/jpegrecoverymap/recoverymaputils.cpp
+++ b/libs/jpegrecoverymap/recoverymaputils.cpp
@@ -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 @@
 
 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 @@
     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
diff --git a/libs/jpegrecoverymap/tests/recoverymap_test.cpp b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
index 0f96723..6dea27f 100644
--- a/libs/jpegrecoverymap/tests/recoverymap_test.cpp
+++ b/libs/jpegrecoverymap/tests/recoverymap_test.cpp
@@ -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 @@
   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;