/* * 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. */ #include #include #include #include #include #include #include #include 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 { public: XMPXmlHandler() : XmlHandler() { gContainerItemState = NotStrarted; } enum ParseState { NotStrarted, Started, Done }; virtual DataMatchResult StartElement(const XmlTokenContext& context) { string val; if (context.BuildTokenValue(&val)) { if (!val.compare(gContainerItemName)) { gContainerItemState = Started; } else { if (gContainerItemState != Done) { gContainerItemState = NotStrarted; } } } return context.GetResult(); } virtual DataMatchResult FinishElement(const XmlTokenContext& context) { if (gContainerItemState == Started) { gContainerItemState = Done; lastAttributeName = ""; } return context.GetResult(); } virtual DataMatchResult AttributeName(const XmlTokenContext& context) { string val; if (gContainerItemState == Started) { if (context.BuildTokenValue(&val)) { if (!val.compare(rangeScalingFactorAttrName)) { lastAttributeName = rangeScalingFactorAttrName; } else if (!val.compare(transferFunctionAttrName)) { lastAttributeName = transferFunctionAttrName; } else { lastAttributeName = ""; } } } return context.GetResult(); } virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { string val; if (gContainerItemState == Started) { if (context.BuildTokenValue(&val, true)) { if (!lastAttributeName.compare(rangeScalingFactorAttrName)) { rangeScalingFactorStr = val; } else if (!lastAttributeName.compare(transferFunctionAttrName)) { transferFunctionStr = val; } } } return context.GetResult(); } bool getRangeScalingFactor(float* scaling_factor) { if (gContainerItemState == Done) { stringstream ss(rangeScalingFactorStr); float val; if (ss >> val) { *scaling_factor = val; return true; } else { return false; } } else { return false; } } bool getTransferFunction(jpegr_transfer_function* transfer_function) { if (gContainerItemState == Done) { stringstream ss(transferFunctionStr); int val; if (ss >> val) { *transfer_function = static_cast(val); return true; } else { return false; } } else { return false; } return true; } private: static const string gContainerItemName; static const string rangeScalingFactorAttrName; static const string transferFunctionAttrName; string rangeScalingFactorStr; string transferFunctionStr; string lastAttributeName; ParseState gContainerItemState; }; const string XMPXmlHandler::gContainerItemName = "GContainer:Item"; const string XMPXmlHandler::rangeScalingFactorAttrName = "RecoveryMap:RangeScalingFactor"; const string XMPXmlHandler::transferFunctionAttrName = "RecoveryMap:TransferFunction"; 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"; if (xmp_size < nameSpace.size()+2) { // Data too short return false; } if (strncmp(reinterpret_cast(xmp_data), nameSpace.c_str(), nameSpace.size())) { // Not correct namespace return false; } // Position the pointers to the start of XMP XML portion xmp_data += nameSpace.size()+1; xmp_size -= nameSpace.size()+1; XMPXmlHandler handler; // We need to remove tail data until the closing tag. Otherwise parser will throw an error. while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) { xmp_size--; } string str(reinterpret_cast(xmp_data), xmp_size); MessageHandler msg_handler; unique_ptr rule(new XmlElementRule); XmlReader reader(&handler, &msg_handler); reader.StartParse(std::move(rule)); reader.Parse(str); reader.FinishParse(); if (reader.HasErrors()) { // Parse error return false; } if (!handler.getRangeScalingFactor(&metadata->rangeScalingFactor)) { return false; } if (!handler.getTransferFunction(&metadata->transferFunction)) { return false; } return true; } string generateXmp(int secondary_image_length, jpegr_metadata& metadata) { const vector kConDirSeq({kConDir, string("rdf:Seq")}); const vector 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