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;