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

Commit f2731fb5 authored by Dichen Zhang's avatar Dichen Zhang Committed by Android (Google) Code Review
Browse files

Merge "Update XMP metadata"

parents 77088579 dc8452b8
Loading
Loading
Loading
Loading
+3 −43
Original line number Original line Diff line number Diff line
@@ -30,8 +30,9 @@ typedef enum {


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


struct jpegr_info_struct {
struct jpegr_info_struct {
@@ -343,47 +344,6 @@ private:
                               jr_metadata_ptr metadata,
                               jr_metadata_ptr metadata,
                               jr_compressed_ptr dest);
                               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.
     * This method will tone map a HDR image to an SDR image.
     *
     *
+49 −1
Original line number Original line Diff line number Diff line
@@ -17,10 +17,11 @@
#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
#ifndef ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
#define ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H


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



namespace android::recoverymap {
namespace android::recoverymap {


struct jpegr_metadata;
struct jpegr_metadata;
@@ -35,6 +36,53 @@ struct jpegr_metadata;
*/
*/
bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* 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
#endif //ANDROID_JPEGRECOVERYMAP_RECOVERYMAPUTILS_H
 No newline at end of file
+6 −65
Original line number Original line Diff line number Diff line
@@ -21,7 +21,6 @@
#include <jpegrecoverymap/recoverymaputils.h>
#include <jpegrecoverymap/recoverymaputils.h>


#include <image_io/jpeg/jpeg_marker.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_info.h>
#include <image_io/jpeg/jpeg_scanner.h>
#include <image_io/jpeg/jpeg_scanner.h>
#include <image_io/jpeg/jpeg_info_builder.h>
#include <image_io/jpeg/jpeg_info_builder.h>
@@ -64,19 +63,6 @@ static const st2086_metadata kSt2086Metadata = {
  1.0f,
  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.
 * 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;
  ColorTransformFn hdrInvOetf = nullptr;
  float hdr_white_nits = 0.0f;
  float hdr_white_nits = 0.0f;
  switch (metadata->transferFunction) {
  switch (metadata->transferFunction) {
    case JPEGR_TF_LINEAR:
      hdrInvOetf = identityConversion;
      break;
    case JPEGR_TF_HLG:
    case JPEGR_TF_HLG:
      hdrInvOetf = hlgInvOetf;
      hdrInvOetf = hlgInvOetf;
      hdr_white_nits = kHlgMaxNits;
      hdr_white_nits = kHlgMaxNits;
@@ -544,6 +533,9 @@ status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_


  ColorTransformFn hdrOetf = nullptr;
  ColorTransformFn hdrOetf = nullptr;
  switch (metadata->transferFunction) {
  switch (metadata->transferFunction) {
    case JPEGR_TF_LINEAR:
      hdrOetf = identityConversion;
      break;
    case JPEGR_TF_HLG:
    case JPEGR_TF_HLG:
      hdrOetf = hlgOetf;
      hdrOetf = hlgOetf;
      break;
      break;
@@ -680,57 +672,6 @@ status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
  return NO_ERROR;
  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,
status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
                              jr_uncompressed_ptr dest) {
                              jr_uncompressed_ptr dest) {
  if (uncompressed_p010_image == nullptr || dest == nullptr) {
  if (uncompressed_p010_image == nullptr || dest == nullptr) {
+135 −3
Original line number Original line Diff line number Diff line
@@ -17,19 +17,29 @@
#include <jpegrecoverymap/recoverymaputils.h>
#include <jpegrecoverymap/recoverymaputils.h>
#include <jpegrecoverymap/recoverymap.h>
#include <jpegrecoverymap/recoverymap.h>
#include <image_io/xml/xml_reader.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/base/message_handler.h>
#include <image_io/xml/xml_element_rules.h>
#include <image_io/xml/xml_element_rules.h>
#include <image_io/xml/xml_handler.h>
#include <image_io/xml/xml_handler.h>
#include <image_io/xml/xml_rule.h>
#include <image_io/xml/xml_rule.h>


#include <string>
#include <sstream>

using namespace photos_editing_formats::image_io;
using namespace photos_editing_formats::image_io;
using namespace std;
using namespace std;


namespace android::recoverymap {
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
// Extremely simple XML Handler - just searches for interesting elements
class XMPXmlHandler : public XmlHandler {
class XMPXmlHandler : public XmlHandler {
@@ -104,6 +114,36 @@ private:


const string XMPXmlHandler::rangeScalingFactorName = "GContainer:rangeScalingFactor";
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) {
bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
    string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
    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;
    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
} // namespace android::recoverymap
 No newline at end of file
+14 −0
Original line number Original line Diff line number Diff line
@@ -15,6 +15,7 @@
 */
 */


#include <jpegrecoverymap/recoverymap.h>
#include <jpegrecoverymap/recoverymap.h>
#include <jpegrecoverymap/recoverymaputils.h>
#include <fcntl.h>
#include <fcntl.h>
#include <fstream>
#include <fstream>
#include <gtest/gtest.h>
#include <gtest/gtest.h>
@@ -94,6 +95,19 @@ TEST_F(RecoveryMapTest, build) {
  recovery_map.decodeJPEGR(nullptr, nullptr, nullptr, false);
  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) {
TEST_F(RecoveryMapTest, encodeFromP010ThenDecode) {
  int ret;
  int ret;