/* * 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 #include using namespace photos_editing_formats::image_io; using namespace std; namespace android::jpegrecoverymap { /* * 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". */ static inline string Name(const string &prefix, const string &suffix) { std::stringstream ss; ss << prefix << ":" << suffix; return ss.str(); } DataStruct::DataStruct(int s) { data = malloc(s); length = s; memset(data, 0, s); writePos = 0; } DataStruct::~DataStruct() { if (data != nullptr) { free(data); } } void* DataStruct::getData() { return data; } int DataStruct::getLength() { return length; } int DataStruct::getBytesWritten() { return writePos; } bool DataStruct::write8(uint8_t value) { uint8_t v = value; return write(&v, 1); } bool DataStruct::write16(uint16_t value) { uint16_t v = value; return write(&v, 2); } bool DataStruct::write32(uint32_t value) { uint32_t v = value; return write(&v, 4); } bool DataStruct::write(const void* src, int size) { if (writePos + size > length) { ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d", writePos, size, length); return false; } memcpy((uint8_t*) data + writePos, src, size); writePos += size; return true; } /* * Helper function used for writing data to destination. */ status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) { if (position + length > destination->maxLength) { return ERROR_JPEGR_BUFFER_TOO_SMALL; } memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); position += length; return NO_ERROR; } // Extremely simple XML Handler - just searches for interesting elements class XMPXmlHandler : public XmlHandler { public: XMPXmlHandler() : XmlHandler() { state = NotStrarted; } enum ParseState { NotStrarted, Started, Done }; virtual DataMatchResult StartElement(const XmlTokenContext& context) { string val; if (context.BuildTokenValue(&val)) { if (!val.compare(containerName)) { state = Started; } else { if (state != Done) { state = NotStrarted; } } } return context.GetResult(); } virtual DataMatchResult FinishElement(const XmlTokenContext& context) { if (state == Started) { state = Done; lastAttributeName = ""; } return context.GetResult(); } virtual DataMatchResult AttributeName(const XmlTokenContext& context) { string val; if (state == Started) { if (context.BuildTokenValue(&val)) { if (!val.compare(maxContentBoostAttrName)) { lastAttributeName = maxContentBoostAttrName; } else if (!val.compare(minContentBoostAttrName)) { lastAttributeName = minContentBoostAttrName; } else { lastAttributeName = ""; } } } return context.GetResult(); } virtual DataMatchResult AttributeValue(const XmlTokenContext& context) { string val; if (state == Started) { if (context.BuildTokenValue(&val, true)) { if (!lastAttributeName.compare(maxContentBoostAttrName)) { maxContentBoostStr = val; } else if (!lastAttributeName.compare(minContentBoostAttrName)) { minContentBoostStr = val; } } } return context.GetResult(); } bool getMaxContentBoost(float* max_content_boost) { if (state == Done) { stringstream ss(maxContentBoostStr); float val; if (ss >> val) { *max_content_boost = exp2(val); return true; } else { return false; } } else { return false; } } bool getMinContentBoost(float* min_content_boost) { if (state == Done) { stringstream ss(minContentBoostStr); float val; if (ss >> val) { *min_content_boost = exp2(val); return true; } else { return false; } } else { return false; } } private: static const string containerName; static const string maxContentBoostAttrName; string maxContentBoostStr; static const string minContentBoostAttrName; string minContentBoostStr; string lastAttributeName; ParseState state; }; // GContainer XMP constants - URI and namespace prefix const string kContainerUri = "http://ns.google.com/photos/1.0/container/"; const string kContainerPrefix = "Container"; // GContainer XMP constants - element and attribute names const string kConDirectory = Name(kContainerPrefix, "Directory"); const string kConItem = Name(kContainerPrefix, "Item"); // GContainer XMP constants - names for XMP handlers const string XMPXmlHandler::containerName = "rdf:Description"; // Item XMP constants - URI and namespace prefix const string kItemUri = "http://ns.google.com/photos/1.0/container/item/"; const string kItemPrefix = "Item"; // Item XMP constants - element and attribute names const string kItemLength = Name(kItemPrefix, "Length"); const string kItemMime = Name(kItemPrefix, "Mime"); const string kItemSemantic = Name(kItemPrefix, "Semantic"); // Item XMP constants - element and attribute values const string kSemanticPrimary = "Primary"; const string kSemanticRecoveryMap = "RecoveryMap"; const string kMimeImageJpeg = "image/jpeg"; // RecoveryMap XMP constants - URI and namespace prefix const string kRecoveryMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/"; const string kRecoveryMapPrefix = "hdrgm"; // RecoveryMap XMP constants - element and attribute names const string kMapVersion = Name(kRecoveryMapPrefix, "Version"); const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin"); const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax"); const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma"); const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR"); const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR"); const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin"); const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax"); const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition"); // RecoveryMap XMP constants - names for XMP handlers const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin; const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax; 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.getMaxContentBoost(&metadata->maxContentBoost)) { return false; } if (!handler.getMinContentBoost(&metadata->minContentBoost)) { return false; } return true; } string generateXmpForPrimaryImage(int secondary_image_length) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector kLiItem({string("rdf:li"), kConItem}); 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(kItemPrefix, kItemUri); writer.StartWritingElements(kConDirSeq); size_t item_depth = writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.FinishWritingElementsToDepth(item_depth); writer.StartWritingElements(kLiItem); writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap); writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg); writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length); writer.FinishWriting(); return ss.str(); } string generateXmpForSecondaryImage(jpegr_metadata& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector kLiItem({string("rdf:li"), kConItem}); 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(kRecoveryMapPrefix, kRecoveryMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); writer.WriteAttributeNameAndValue(kMapGamma, "1"); writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0"); writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0"); writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0"); writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3"); writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR"); writer.FinishWriting(); return ss.str(); } } // namespace android::jpegrecoverymap