blob: e5323c7b8576cbcb0faf09fdeb0e31c3e512631a [file] [log] [blame]
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001/*
2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Dichen Zhang761b52d2023-02-10 22:38:38 +000017#include <jpegrecoverymap/jpegrutils.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000018#include <image_io/xml/xml_reader.h>
Dichen Zhangdc8452b2022-11-23 17:17:56 +000019#include <image_io/xml/xml_writer.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000020#include <image_io/base/message_handler.h>
21#include <image_io/xml/xml_element_rules.h>
22#include <image_io/xml/xml_handler.h>
23#include <image_io/xml/xml_rule.h>
24
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000025using namespace photos_editing_formats::image_io;
26using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000027
28namespace android::recoverymap {
29
Dichen Zhangdc8452b2022-11-23 17:17:56 +000030/*
31 * Helper function used for generating XMP metadata.
32 *
33 * @param prefix The prefix part of the name.
34 * @param suffix The suffix part of the name.
35 * @return A name of the form "prefix:suffix".
36 */
37string Name(const string &prefix, const string &suffix) {
38 std::stringstream ss;
39 ss << prefix << ":" << suffix;
40 return ss.str();
41}
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000042
Dichen Zhang91dfc572023-01-19 11:22:43 -080043/*
44 * Helper function used for writing data to destination.
45 */
46status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
47 if (position + length > destination->maxLength) {
48 return ERROR_JPEGR_BUFFER_TOO_SMALL;
49 }
50
51 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
52 position += length;
53 return NO_ERROR;
54}
55
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000056// Extremely simple XML Handler - just searches for interesting elements
57class XMPXmlHandler : public XmlHandler {
58public:
59
60 XMPXmlHandler() : XmlHandler() {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000061 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000062 }
63
64 enum ParseState {
65 NotStrarted,
66 Started,
67 Done
68 };
69
70 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
71 string val;
72 if (context.BuildTokenValue(&val)) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000073 if (!val.compare(gContainerItemName)) {
74 gContainerItemState = Started;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000075 } else {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000076 if (gContainerItemState != Done) {
77 gContainerItemState = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000078 }
79 }
80 }
81 return context.GetResult();
82 }
83
84 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +000085 if (gContainerItemState == Started) {
86 gContainerItemState = Done;
87 lastAttributeName = "";
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000088 }
89 return context.GetResult();
90 }
91
Fyodor Kyslovad58a342022-12-12 02:10:35 +000092 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000093 string val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +000094 if (gContainerItemState == Started) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000095 if (context.BuildTokenValue(&val)) {
Nick Deakin01759062023-02-02 18:21:43 -050096 if (!val.compare(maxContentBoostAttrName)) {
97 lastAttributeName = maxContentBoostAttrName;
Fyodor Kyslovad58a342022-12-12 02:10:35 +000098 } else {
99 lastAttributeName = "";
100 }
101 }
102 }
103 return context.GetResult();
104 }
105
106 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
107 string val;
108 if (gContainerItemState == Started) {
109 if (context.BuildTokenValue(&val, true)) {
Nick Deakin01759062023-02-02 18:21:43 -0500110 if (!lastAttributeName.compare(maxContentBoostAttrName)) {
111 maxContentBoostStr = val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000112 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000113 }
114 }
115 return context.GetResult();
116 }
117
Nick Deakin01759062023-02-02 18:21:43 -0500118 bool getMaxContentBoost(float* max_content_boost) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000119 if (gContainerItemState == Done) {
Nick Deakin01759062023-02-02 18:21:43 -0500120 stringstream ss(maxContentBoostStr);
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000121 float val;
122 if (ss >> val) {
Nick Deakin01759062023-02-02 18:21:43 -0500123 *max_content_boost = val;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000124 return true;
125 } else {
126 return false;
127 }
128 } else {
129 return false;
130 }
131 }
132
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000133private:
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000134 static const string gContainerItemName;
Nick Deakin01759062023-02-02 18:21:43 -0500135 static const string maxContentBoostAttrName;
136 string maxContentBoostStr;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000137 string lastAttributeName;
138 ParseState gContainerItemState;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000139};
140
Nick Deakin7dfe0162023-01-19 18:27:09 -0500141// GContainer XMP constants - URI and namespace prefix
142const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
Nick Deakin01759062023-02-02 18:21:43 -0500143const string kContainerPrefix = "Container";
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000144
Nick Deakin7dfe0162023-01-19 18:27:09 -0500145// GContainer XMP constants - element and attribute names
146const string kConDirectory = Name(kContainerPrefix, "Directory");
147const string kConItem = Name(kContainerPrefix, "Item");
Nick Deakin7dfe0162023-01-19 18:27:09 -0500148
149// GContainer XMP constants - names for XMP handlers
150const string XMPXmlHandler::gContainerItemName = kConItem;
151
Nick Deakin01759062023-02-02 18:21:43 -0500152// Item XMP constants - URI and namespace prefix
153const string kItemUri = "http://ns.google.com/photos/1.0/container/item/";
154const string kItemPrefix = "Item";
155
156// Item XMP constants - element and attribute names
157const string kItemLength = Name(kItemPrefix, "Length");
158const string kItemMime = Name(kItemPrefix, "Mime");
159const string kItemSemantic = Name(kItemPrefix, "Semantic");
160
161// Item XMP constants - element and attribute values
162const string kSemanticPrimary = "Primary";
163const string kSemanticRecoveryMap = "RecoveryMap";
164const string kMimeImageJpeg = "image/jpeg";
165
Nick Deakin7dfe0162023-01-19 18:27:09 -0500166// RecoveryMap XMP constants - URI and namespace prefix
167const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
168const string kRecoveryMapPrefix = "RecoveryMap";
169
170// RecoveryMap XMP constants - element and attribute names
Nick Deakin01759062023-02-02 18:21:43 -0500171const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost");
172const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
Nick Deakin7dfe0162023-01-19 18:27:09 -0500173
174// RecoveryMap XMP constants - names for XMP handlers
Nick Deakin01759062023-02-02 18:21:43 -0500175const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000176
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000177bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000178 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
179
180 if (xmp_size < nameSpace.size()+2) {
181 // Data too short
182 return false;
183 }
184
185 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
186 // Not correct namespace
187 return false;
188 }
189
190 // Position the pointers to the start of XMP XML portion
191 xmp_data += nameSpace.size()+1;
192 xmp_size -= nameSpace.size()+1;
193 XMPXmlHandler handler;
194
195 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
196 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
197 xmp_size--;
198 }
199
200 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
201 MessageHandler msg_handler;
202 unique_ptr<XmlRule> rule(new XmlElementRule);
203 XmlReader reader(&handler, &msg_handler);
204 reader.StartParse(std::move(rule));
205 reader.Parse(str);
206 reader.FinishParse();
207 if (reader.HasErrors()) {
208 // Parse error
209 return false;
210 }
211
Nick Deakin01759062023-02-02 18:21:43 -0500212 if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000213 return false;
214 }
215
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000216 return true;
217}
218
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000219string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500220 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
221 const vector<string> kLiItem({string("rdf:li"), kConItem});
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000222
223 std::stringstream ss;
224 photos_editing_formats::image_io::XmlWriter writer(ss);
225 writer.StartWritingElement("x:xmpmeta");
226 writer.WriteXmlns("x", "adobe:ns:meta/");
227 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
228 writer.StartWritingElement("rdf:RDF");
229 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
230 writer.StartWritingElement("rdf:Description");
231 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin01759062023-02-02 18:21:43 -0500232 writer.WriteXmlns(kItemPrefix, kItemUri);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500233 writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000234 writer.StartWritingElements(kConDirSeq);
235 size_t item_depth = writer.StartWritingElements(kLiItem);
Nick Deakin01759062023-02-02 18:21:43 -0500236 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
237 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500238 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
Nick Deakin01759062023-02-02 18:21:43 -0500239 writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000240 writer.FinishWritingElementsToDepth(item_depth);
241 writer.StartWritingElements(kLiItem);
Nick Deakin01759062023-02-02 18:21:43 -0500242 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
243 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
244 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000245 writer.FinishWriting();
246
247 return ss.str();
248}
249
Dichen Zhang50ff1292023-01-27 18:03:43 +0000250} // namespace android::recoverymap