blob: ff9644732031c93f9153a81e28f70614ab2286d4 [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>
Dichen Zhang61ede362023-02-22 18:50:13 +000018#include <utils/Log.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000019#include <image_io/xml/xml_reader.h>
Dichen Zhangdc8452b2022-11-23 17:17:56 +000020#include <image_io/xml/xml_writer.h>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000021#include <image_io/base/message_handler.h>
22#include <image_io/xml/xml_element_rules.h>
23#include <image_io/xml/xml_handler.h>
24#include <image_io/xml/xml_rule.h>
Dichen Zhang61ede362023-02-22 18:50:13 +000025#include <cmath>
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000026
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000027using namespace photos_editing_formats::image_io;
28using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000029
Dichen Zhangb2ed8302023-02-10 23:05:04 +000030namespace android::jpegrecoverymap {
Dichen Zhangdc8452b2022-11-23 17:17:56 +000031/*
32 * Helper function used for generating XMP metadata.
33 *
34 * @param prefix The prefix part of the name.
35 * @param suffix The suffix part of the name.
36 * @return A name of the form "prefix:suffix".
37 */
Dichen Zhang61ede362023-02-22 18:50:13 +000038static inline string Name(const string &prefix, const string &suffix) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +000039 std::stringstream ss;
40 ss << prefix << ":" << suffix;
41 return ss.str();
42}
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +000043
Dichen Zhang61ede362023-02-22 18:50:13 +000044DataStruct::DataStruct(int s) {
45 data = malloc(s);
46 length = s;
47 memset(data, 0, s);
48 writePos = 0;
49}
50
51DataStruct::~DataStruct() {
52 if (data != nullptr) {
53 free(data);
54 }
55}
56
57void* DataStruct::getData() {
58 return data;
59}
60
61int DataStruct::getLength() {
62 return length;
63}
64
65int DataStruct::getBytesWritten() {
66 return writePos;
67}
68
69bool DataStruct::write8(uint8_t value) {
70 uint8_t v = value;
71 return write(&v, 1);
72}
73
74bool DataStruct::write16(uint16_t value) {
75 uint16_t v = value;
76 return write(&v, 2);
77}
78bool DataStruct::write32(uint32_t value) {
79 uint32_t v = value;
80 return write(&v, 4);
81}
82
83bool DataStruct::write(const void* src, int size) {
84 if (writePos + size > length) {
85 ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d",
86 writePos, size, length);
87 return false;
88 }
89 memcpy((uint8_t*) data + writePos, src, size);
90 writePos += size;
91 return true;
92}
93
Dichen Zhang91dfc572023-01-19 11:22:43 -080094/*
95 * Helper function used for writing data to destination.
96 */
97status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
98 if (position + length > destination->maxLength) {
99 return ERROR_JPEGR_BUFFER_TOO_SMALL;
100 }
101
102 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
103 position += length;
104 return NO_ERROR;
105}
106
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000107// Extremely simple XML Handler - just searches for interesting elements
108class XMPXmlHandler : public XmlHandler {
109public:
110
111 XMPXmlHandler() : XmlHandler() {
Dichen Zhang61ede362023-02-22 18:50:13 +0000112 state = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000113 }
114
115 enum ParseState {
116 NotStrarted,
117 Started,
118 Done
119 };
120
121 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
122 string val;
123 if (context.BuildTokenValue(&val)) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000124 if (!val.compare(containerName)) {
125 state = Started;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000126 } else {
Dichen Zhang61ede362023-02-22 18:50:13 +0000127 if (state != Done) {
128 state = NotStrarted;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000129 }
130 }
131 }
132 return context.GetResult();
133 }
134
135 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000136 if (state == Started) {
137 state = Done;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000138 lastAttributeName = "";
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000139 }
140 return context.GetResult();
141 }
142
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000143 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000144 string val;
Dichen Zhang61ede362023-02-22 18:50:13 +0000145 if (state == Started) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000146 if (context.BuildTokenValue(&val)) {
Nick Deakin01759062023-02-02 18:21:43 -0500147 if (!val.compare(maxContentBoostAttrName)) {
148 lastAttributeName = maxContentBoostAttrName;
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500149 } else if (!val.compare(minContentBoostAttrName)) {
150 lastAttributeName = minContentBoostAttrName;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000151 } else {
152 lastAttributeName = "";
153 }
154 }
155 }
156 return context.GetResult();
157 }
158
159 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
160 string val;
Dichen Zhang61ede362023-02-22 18:50:13 +0000161 if (state == Started) {
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000162 if (context.BuildTokenValue(&val, true)) {
Nick Deakin01759062023-02-02 18:21:43 -0500163 if (!lastAttributeName.compare(maxContentBoostAttrName)) {
164 maxContentBoostStr = val;
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500165 } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
166 minContentBoostStr = val;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000167 }
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000168 }
169 }
170 return context.GetResult();
171 }
172
Nick Deakin01759062023-02-02 18:21:43 -0500173 bool getMaxContentBoost(float* max_content_boost) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000174 if (state == Done) {
Nick Deakin01759062023-02-02 18:21:43 -0500175 stringstream ss(maxContentBoostStr);
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000176 float val;
177 if (ss >> val) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000178 *max_content_boost = exp2(val);
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000179 return true;
180 } else {
181 return false;
182 }
183 } else {
184 return false;
185 }
186 }
187
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500188 bool getMinContentBoost(float* min_content_boost) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000189 if (state == Done) {
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500190 stringstream ss(minContentBoostStr);
191 float val;
192 if (ss >> val) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000193 *min_content_boost = exp2(val);
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500194 return true;
195 } else {
196 return false;
197 }
198 } else {
199 return false;
200 }
201 }
202
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000203private:
Dichen Zhang61ede362023-02-22 18:50:13 +0000204 static const string containerName;
Nick Deakin01759062023-02-02 18:21:43 -0500205 static const string maxContentBoostAttrName;
206 string maxContentBoostStr;
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500207 static const string minContentBoostAttrName;
208 string minContentBoostStr;
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000209 string lastAttributeName;
Dichen Zhang61ede362023-02-22 18:50:13 +0000210 ParseState state;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000211};
212
Nick Deakin7dfe0162023-01-19 18:27:09 -0500213// GContainer XMP constants - URI and namespace prefix
214const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
Nick Deakin01759062023-02-02 18:21:43 -0500215const string kContainerPrefix = "Container";
Fyodor Kyslovad58a342022-12-12 02:10:35 +0000216
Nick Deakin7dfe0162023-01-19 18:27:09 -0500217// GContainer XMP constants - element and attribute names
218const string kConDirectory = Name(kContainerPrefix, "Directory");
219const string kConItem = Name(kContainerPrefix, "Item");
Nick Deakin7dfe0162023-01-19 18:27:09 -0500220
221// GContainer XMP constants - names for XMP handlers
Dichen Zhang61ede362023-02-22 18:50:13 +0000222const string XMPXmlHandler::containerName = "rdf:Description";
Nick Deakin01759062023-02-02 18:21:43 -0500223// Item XMP constants - URI and namespace prefix
224const string kItemUri = "http://ns.google.com/photos/1.0/container/item/";
225const string kItemPrefix = "Item";
226
227// Item XMP constants - element and attribute names
228const string kItemLength = Name(kItemPrefix, "Length");
229const string kItemMime = Name(kItemPrefix, "Mime");
230const string kItemSemantic = Name(kItemPrefix, "Semantic");
231
232// Item XMP constants - element and attribute values
233const string kSemanticPrimary = "Primary";
234const string kSemanticRecoveryMap = "RecoveryMap";
235const string kMimeImageJpeg = "image/jpeg";
236
Nick Deakin7dfe0162023-01-19 18:27:09 -0500237// RecoveryMap XMP constants - URI and namespace prefix
Dichen Zhang61ede362023-02-22 18:50:13 +0000238const string kRecoveryMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/";
239const string kRecoveryMapPrefix = "hdrgm";
Nick Deakin7dfe0162023-01-19 18:27:09 -0500240
241// RecoveryMap XMP constants - element and attribute names
Nick Deakin01759062023-02-02 18:21:43 -0500242const string kMapVersion = Name(kRecoveryMapPrefix, "Version");
Dichen Zhang61ede362023-02-22 18:50:13 +0000243const string kMapGainMapMin = Name(kRecoveryMapPrefix, "GainMapMin");
244const string kMapGainMapMax = Name(kRecoveryMapPrefix, "GainMapMax");
245const string kMapGamma = Name(kRecoveryMapPrefix, "Gamma");
246const string kMapOffsetSdr = Name(kRecoveryMapPrefix, "OffsetSDR");
247const string kMapOffsetHdr = Name(kRecoveryMapPrefix, "OffsetHDR");
248const string kMapHDRCapacityMin = Name(kRecoveryMapPrefix, "HDRCapacityMin");
249const string kMapHDRCapacityMax = Name(kRecoveryMapPrefix, "HDRCapacityMax");
250const string kMapBaseRendition = Name(kRecoveryMapPrefix, "BaseRendition");
Nick Deakin7dfe0162023-01-19 18:27:09 -0500251
252// RecoveryMap XMP constants - names for XMP handlers
Dichen Zhang61ede362023-02-22 18:50:13 +0000253const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
254const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000255
Dichen Zhange286f1c2023-03-14 00:22:00 +0000256bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, jpegr_metadata_struct* metadata) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000257 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
258
259 if (xmp_size < nameSpace.size()+2) {
260 // Data too short
261 return false;
262 }
263
264 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
265 // Not correct namespace
266 return false;
267 }
268
269 // Position the pointers to the start of XMP XML portion
270 xmp_data += nameSpace.size()+1;
271 xmp_size -= nameSpace.size()+1;
272 XMPXmlHandler handler;
273
274 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
275 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
276 xmp_size--;
277 }
278
279 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
280 MessageHandler msg_handler;
281 unique_ptr<XmlRule> rule(new XmlElementRule);
282 XmlReader reader(&handler, &msg_handler);
283 reader.StartParse(std::move(rule));
284 reader.Parse(str);
285 reader.FinishParse();
286 if (reader.HasErrors()) {
287 // Parse error
288 return false;
289 }
290
Nick Deakin01759062023-02-02 18:21:43 -0500291 if (!handler.getMaxContentBoost(&metadata->maxContentBoost)) {
Fyodor Kyslov6c87e7b2022-11-23 03:12:14 +0000292 return false;
293 }
294
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500295 if (!handler.getMinContentBoost(&metadata->minContentBoost)) {
296 return false;
297 }
298
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000299 return true;
300}
301
Dichen Zhang61ede362023-02-22 18:50:13 +0000302string generateXmpForPrimaryImage(int secondary_image_length) {
Nick Deakin7dfe0162023-01-19 18:27:09 -0500303 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
304 const vector<string> kLiItem({string("rdf:li"), kConItem});
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000305
306 std::stringstream ss;
307 photos_editing_formats::image_io::XmlWriter writer(ss);
308 writer.StartWritingElement("x:xmpmeta");
309 writer.WriteXmlns("x", "adobe:ns:meta/");
310 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
311 writer.StartWritingElement("rdf:RDF");
312 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
313 writer.StartWritingElement("rdf:Description");
314 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin01759062023-02-02 18:21:43 -0500315 writer.WriteXmlns(kItemPrefix, kItemUri);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000316 writer.StartWritingElements(kConDirSeq);
317 size_t item_depth = writer.StartWritingElements(kLiItem);
Nick Deakin01759062023-02-02 18:21:43 -0500318 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
319 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000320 writer.FinishWritingElementsToDepth(item_depth);
321 writer.StartWritingElements(kLiItem);
Nick Deakin01759062023-02-02 18:21:43 -0500322 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticRecoveryMap);
323 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
324 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
Dichen Zhang61ede362023-02-22 18:50:13 +0000325 writer.FinishWriting();
326
327 return ss.str();
328}
329
Dichen Zhange286f1c2023-03-14 00:22:00 +0000330string generateXmpForSecondaryImage(jpegr_metadata_struct& metadata) {
Dichen Zhang61ede362023-02-22 18:50:13 +0000331 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
332 const vector<string> kLiItem({string("rdf:li"), kConItem});
333
334 std::stringstream ss;
335 photos_editing_formats::image_io::XmlWriter writer(ss);
336 writer.StartWritingElement("x:xmpmeta");
337 writer.WriteXmlns("x", "adobe:ns:meta/");
338 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
339 writer.StartWritingElement("rdf:RDF");
340 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
341 writer.StartWritingElement("rdf:Description");
342 writer.WriteXmlns(kRecoveryMapPrefix, kRecoveryMapUri);
Nick Deakin3f89a3e2023-02-14 20:35:42 -0500343 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
Dichen Zhang61ede362023-02-22 18:50:13 +0000344 writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
345 writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
346 writer.WriteAttributeNameAndValue(kMapGamma, "1");
347 writer.WriteAttributeNameAndValue(kMapOffsetSdr, "0");
348 writer.WriteAttributeNameAndValue(kMapOffsetHdr, "0");
349 writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, "0");
350 writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, "2.3");
351 writer.WriteAttributeNameAndValue(kMapBaseRendition, "SDR");
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000352 writer.FinishWriting();
353
354 return ss.str();
355}
356
Dichen Zhangb2ed8302023-02-10 23:05:04 +0000357} // namespace android::jpegrecoverymap