blob: 49526c800f618c43406a6c4628ab02698ed7ab3b [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
Dichen Zhangb2ed8302023-02-10 23:05:04 +000028namespace android::jpegrecoverymap {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000029
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;
Nick Deakin3f89a3e2023-02-14 20:35:42 -050098 } else if (!val.compare(minContentBoostAttrName)) {
99 lastAttributeName = minContentBoostAttrName;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000100 } else {
101 lastAttributeName = "";
102 }
103 }
104 }
105 return context.GetResult();
106 }
107
108 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
109 string val;
110 if (gContainerItemState == Started) {
111 if (context.BuildTokenValue(&val, true)) {
Nick Deakin01759062023-02-02 18:21:43 -0500112 if (!lastAttributeName.compare(maxContentBoostAttrName)) {
113 maxContentBoostStr = val;
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500114 } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
115 minContentBoostStr = val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000116 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000117 }
118 }
119 return context.GetResult();
120 }
121
Nick Deakin01759062023-02-02 18:21:43 -0500122 bool getMaxContentBoost(float* max_content_boost) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000123 if (gContainerItemState == Done) {
Nick Deakin01759062023-02-02 18:21:43 -0500124 stringstream ss(maxContentBoostStr);
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000125 float val;
126 if (ss >> val) {
Nick Deakin01759062023-02-02 18:21:43 -0500127 *max_content_boost = val;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000128 return true;
129 } else {
130 return false;
131 }
132 } else {
133 return false;
134 }
135 }
136
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500137 bool getMinContentBoost(float* min_content_boost) {
138 if (gContainerItemState == Done) {
139 stringstream ss(minContentBoostStr);
140 float val;
141 if (ss >> val) {
142 *min_content_boost = val;
143 return true;
144 } else {
145 return false;
146 }
147 } else {
148 return false;
149 }
150 }
151
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000152private:
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000153 static const string gContainerItemName;
Nick Deakin01759062023-02-02 18:21:43 -0500154 static const string maxContentBoostAttrName;
155 string maxContentBoostStr;
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500156 static const string minContentBoostAttrName;
157 string minContentBoostStr;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000158 string lastAttributeName;
159 ParseState gContainerItemState;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000160};
161
Nick Deakin7dfe0162023-01-19 18:27:09 -0500162// GContainer XMP constants - URI and namespace prefix
163const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
Nick Deakin01759062023-02-02 18:21:43 -0500164const string kContainerPrefix = "Container";
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000165
Nick Deakin7dfe0162023-01-19 18:27:09 -0500166// GContainer XMP constants - element and attribute names
167const string kConDirectory = Name(kContainerPrefix, "Directory");
168const string kConItem = Name(kContainerPrefix, "Item");
Nick Deakin7dfe0162023-01-19 18:27:09 -0500169
170// GContainer XMP constants - names for XMP handlers
171const string XMPXmlHandler::gContainerItemName = kConItem;
172
Nick Deakin01759062023-02-02 18:21:43 -0500173// Item XMP constants - URI and namespace prefix
174const string kItemUri = "http://ns.google.com/photos/1.0/container/item/";
175const string kItemPrefix = "Item";
176
177// Item XMP constants - element and attribute names
178const string kItemLength = Name(kItemPrefix, "Length");
179const string kItemMime = Name(kItemPrefix, "Mime");
180const string kItemSemantic = Name(kItemPrefix, "Semantic");
181
182// Item XMP constants - element and attribute values
183const string kSemanticPrimary = "Primary";
184const string kSemanticRecoveryMap = "RecoveryMap";
185const string kMimeImageJpeg = "image/jpeg";
186
Nick Deakin7dfe0162023-01-19 18:27:09 -0500187// RecoveryMap XMP constants - URI and namespace prefix
188const string kRecoveryMapUri = "http://ns.google.com/photos/1.0/recoverymap/";
189const string kRecoveryMapPrefix = "RecoveryMap";
190
191// RecoveryMap XMP constants - element and attribute names
Nick Deakin01759062023-02-02 18:21:43 -0500192const string kMapMaxContentBoost = Name(kRecoveryMapPrefix, "MaxContentBoost");
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500193const string kMapMinContentBoost = Name(kRecoveryMapPrefix, "MinContentBoost");
Nick Deakin01759062023-02-02 18:21:43 -0500194const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
Nick Deakin7dfe0162023-01-19 18:27:09 -0500195
196// RecoveryMap XMP constants - names for XMP handlers
Nick Deakin01759062023-02-02 18:21:43 -0500197const string XMPXmlHandler::maxContentBoostAttrName = kMapMaxContentBoost;
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500198const string XMPXmlHandler::minContentBoostAttrName = kMapMinContentBoost;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000199
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000200bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000201 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
202
203 if (xmp_size < nameSpace.size()+2) {
204 // Data too short
205 return false;
206 }
207
208 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
209 // Not correct namespace
210 return false;
211 }
212
213 // Position the pointers to the start of XMP XML portion
214 xmp_data += nameSpace.size()+1;
215 xmp_size -= nameSpace.size()+1;
216 XMPXmlHandler handler;
217
218 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
219 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
220 xmp_size--;
221 }
222
223 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
224 MessageHandler msg_handler;
225 unique_ptr<XmlRule> rule(new XmlElementRule);
226 XmlReader reader(&handler, &msg_handler);
227 reader.StartParse(std::move(rule));
228 reader.Parse(str);
229 reader.FinishParse();
230 if (reader.HasErrors()) {
231 // Parse error
232 return false;
233 }
234
Nick Deakin01759062023-02-02 18:21:43 -0500235 if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000236 return false;
237 }
238
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500239 if (!handler.getMinContentBoost(&metadata->minContentBoost)) {
240 return false;
241 }
242
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000243 return true;
244}
245
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000246string generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500247 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
248 const vector<string> kLiItem({string("rdf:li"), kConItem});
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000249
250 std::stringstream ss;
251 photos_editing_formats::image_io::XmlWriter writer(ss);
252 writer.StartWritingElement("x:xmpmeta");
253 writer.WriteXmlns("x", "adobe:ns:meta/");
254 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
255 writer.StartWritingElement("rdf:RDF");
256 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
257 writer.StartWritingElement("rdf:Description");
258 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin01759062023-02-02 18:21:43 -0500259 writer.WriteXmlns(kItemPrefix, kItemUri);
Nick Deakin7dfe0162023-01-19 18:27:09 -0500260 writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000261 writer.StartWritingElements(kConDirSeq);
262 size_t item_depth = writer.StartWritingElements(kLiItem);
Nick Deakin01759062023-02-02 18:21:43 -0500263 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
264 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000265 writer.FinishWritingElementsToDepth(item_depth);
266 writer.StartWritingElements(kLiItem);
Nick Deakin01759062023-02-02 18:21:43 -0500267 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
268 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
269 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500270 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
271 writer.WriteAttributeNameAndValue(kMapMaxContentBoost, metadata.maxContentBoost);
272 writer.WriteAttributeNameAndValue(kMapMinContentBoost, metadata.minContentBoost);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000273 writer.FinishWriting();
274
275 return ss.str();
276}
277
Dichen Zhangb2ed8302023-02-10 23:05:04 +0000278} // namespace android::jpegrecoverymap