1 /*
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
17 #include <ultrahdr/jpegrutils.h>
18
19 #include <algorithm>
20 #include <cmath>
21
22 #include <image_io/xml/xml_reader.h>
23 #include <image_io/xml/xml_writer.h>
24 #include <image_io/base/message_handler.h>
25 #include <image_io/xml/xml_element_rules.h>
26 #include <image_io/xml/xml_handler.h>
27 #include <image_io/xml/xml_rule.h>
28 #include <utils/Log.h>
29
30 using namespace photos_editing_formats::image_io;
31 using namespace std;
32
33 namespace android::ultrahdr {
34 /*
35 * Helper function used for generating XMP metadata.
36 *
37 * @param prefix The prefix part of the name.
38 * @param suffix The suffix part of the name.
39 * @return A name of the form "prefix:suffix".
40 */
Name(const string & prefix,const string & suffix)41 static inline string Name(const string &prefix, const string &suffix) {
42 std::stringstream ss;
43 ss << prefix << ":" << suffix;
44 return ss.str();
45 }
46
DataStruct(int s)47 DataStruct::DataStruct(int s) {
48 data = malloc(s);
49 length = s;
50 memset(data, 0, s);
51 writePos = 0;
52 }
53
~DataStruct()54 DataStruct::~DataStruct() {
55 if (data != nullptr) {
56 free(data);
57 }
58 }
59
getData()60 void* DataStruct::getData() {
61 return data;
62 }
63
getLength()64 int DataStruct::getLength() {
65 return length;
66 }
67
getBytesWritten()68 int DataStruct::getBytesWritten() {
69 return writePos;
70 }
71
write8(uint8_t value)72 bool DataStruct::write8(uint8_t value) {
73 uint8_t v = value;
74 return write(&v, 1);
75 }
76
write16(uint16_t value)77 bool DataStruct::write16(uint16_t value) {
78 uint16_t v = value;
79 return write(&v, 2);
80 }
write32(uint32_t value)81 bool DataStruct::write32(uint32_t value) {
82 uint32_t v = value;
83 return write(&v, 4);
84 }
85
write(const void * src,int size)86 bool DataStruct::write(const void* src, int size) {
87 if (writePos + size > length) {
88 ALOGE("Writing out of boundary: write position: %d, size: %d, capacity: %d",
89 writePos, size, length);
90 return false;
91 }
92 memcpy((uint8_t*) data + writePos, src, size);
93 writePos += size;
94 return true;
95 }
96
97 /*
98 * Helper function used for writing data to destination.
99 */
Write(jr_compressed_ptr destination,const void * source,size_t length,int & position)100 status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
101 if (position + length > destination->maxLength) {
102 return ERROR_JPEGR_BUFFER_TOO_SMALL;
103 }
104
105 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
106 position += length;
107 return NO_ERROR;
108 }
109
110 // Extremely simple XML Handler - just searches for interesting elements
111 class XMPXmlHandler : public XmlHandler {
112 public:
113
XMPXmlHandler()114 XMPXmlHandler() : XmlHandler() {
115 state = NotStrarted;
116 versionFound = false;
117 minContentBoostFound = false;
118 maxContentBoostFound = false;
119 gammaFound = false;
120 offsetSdrFound = false;
121 offsetHdrFound = false;
122 hdrCapacityMinFound = false;
123 hdrCapacityMaxFound = false;
124 baseRenditionIsHdrFound = false;
125 }
126
127 enum ParseState {
128 NotStrarted,
129 Started,
130 Done
131 };
132
StartElement(const XmlTokenContext & context)133 virtual DataMatchResult StartElement(const XmlTokenContext& context) {
134 string val;
135 if (context.BuildTokenValue(&val)) {
136 if (!val.compare(containerName)) {
137 state = Started;
138 } else {
139 if (state != Done) {
140 state = NotStrarted;
141 }
142 }
143 }
144 return context.GetResult();
145 }
146
FinishElement(const XmlTokenContext & context)147 virtual DataMatchResult FinishElement(const XmlTokenContext& context) {
148 if (state == Started) {
149 state = Done;
150 lastAttributeName = "";
151 }
152 return context.GetResult();
153 }
154
AttributeName(const XmlTokenContext & context)155 virtual DataMatchResult AttributeName(const XmlTokenContext& context) {
156 string val;
157 if (state == Started) {
158 if (context.BuildTokenValue(&val)) {
159 if (!val.compare(versionAttrName)) {
160 lastAttributeName = versionAttrName;
161 } else if (!val.compare(maxContentBoostAttrName)) {
162 lastAttributeName = maxContentBoostAttrName;
163 } else if (!val.compare(minContentBoostAttrName)) {
164 lastAttributeName = minContentBoostAttrName;
165 } else if (!val.compare(gammaAttrName)) {
166 lastAttributeName = gammaAttrName;
167 } else if (!val.compare(offsetSdrAttrName)) {
168 lastAttributeName = offsetSdrAttrName;
169 } else if (!val.compare(offsetHdrAttrName)) {
170 lastAttributeName = offsetHdrAttrName;
171 } else if (!val.compare(hdrCapacityMinAttrName)) {
172 lastAttributeName = hdrCapacityMinAttrName;
173 } else if (!val.compare(hdrCapacityMaxAttrName)) {
174 lastAttributeName = hdrCapacityMaxAttrName;
175 } else if (!val.compare(baseRenditionIsHdrAttrName)) {
176 lastAttributeName = baseRenditionIsHdrAttrName;
177 } else {
178 lastAttributeName = "";
179 }
180 }
181 }
182 return context.GetResult();
183 }
184
AttributeValue(const XmlTokenContext & context)185 virtual DataMatchResult AttributeValue(const XmlTokenContext& context) {
186 string val;
187 if (state == Started) {
188 if (context.BuildTokenValue(&val, true)) {
189 if (!lastAttributeName.compare(versionAttrName)) {
190 versionStr = val;
191 versionFound = true;
192 } else if (!lastAttributeName.compare(maxContentBoostAttrName)) {
193 maxContentBoostStr = val;
194 maxContentBoostFound = true;
195 } else if (!lastAttributeName.compare(minContentBoostAttrName)) {
196 minContentBoostStr = val;
197 minContentBoostFound = true;
198 } else if (!lastAttributeName.compare(gammaAttrName)) {
199 gammaStr = val;
200 gammaFound = true;
201 } else if (!lastAttributeName.compare(offsetSdrAttrName)) {
202 offsetSdrStr = val;
203 offsetSdrFound = true;
204 } else if (!lastAttributeName.compare(offsetHdrAttrName)) {
205 offsetHdrStr = val;
206 offsetHdrFound = true;
207 } else if (!lastAttributeName.compare(hdrCapacityMinAttrName)) {
208 hdrCapacityMinStr = val;
209 hdrCapacityMinFound = true;
210 } else if (!lastAttributeName.compare(hdrCapacityMaxAttrName)) {
211 hdrCapacityMaxStr = val;
212 hdrCapacityMaxFound = true;
213 } else if (!lastAttributeName.compare(baseRenditionIsHdrAttrName)) {
214 baseRenditionIsHdrStr = val;
215 baseRenditionIsHdrFound = true;
216 }
217 }
218 }
219 return context.GetResult();
220 }
221
getVersion(string * version,bool * present)222 bool getVersion(string* version, bool* present) {
223 if (state == Done) {
224 *version = versionStr;
225 *present = versionFound;
226 return true;
227 } else {
228 return false;
229 }
230 }
231
getMaxContentBoost(float * max_content_boost,bool * present)232 bool getMaxContentBoost(float* max_content_boost, bool* present) {
233 if (state == Done) {
234 *present = maxContentBoostFound;
235 stringstream ss(maxContentBoostStr);
236 float val;
237 if (ss >> val) {
238 *max_content_boost = exp2(val);
239 return true;
240 } else {
241 return false;
242 }
243 } else {
244 return false;
245 }
246 }
247
getMinContentBoost(float * min_content_boost,bool * present)248 bool getMinContentBoost(float* min_content_boost, bool* present) {
249 if (state == Done) {
250 *present = minContentBoostFound;
251 stringstream ss(minContentBoostStr);
252 float val;
253 if (ss >> val) {
254 *min_content_boost = exp2(val);
255 return true;
256 } else {
257 return false;
258 }
259 } else {
260 return false;
261 }
262 }
263
getGamma(float * gamma,bool * present)264 bool getGamma(float* gamma, bool* present) {
265 if (state == Done) {
266 *present = gammaFound;
267 stringstream ss(gammaStr);
268 float val;
269 if (ss >> val) {
270 *gamma = val;
271 return true;
272 } else {
273 return false;
274 }
275 } else {
276 return false;
277 }
278 }
279
280
getOffsetSdr(float * offset_sdr,bool * present)281 bool getOffsetSdr(float* offset_sdr, bool* present) {
282 if (state == Done) {
283 *present = offsetSdrFound;
284 stringstream ss(offsetSdrStr);
285 float val;
286 if (ss >> val) {
287 *offset_sdr = val;
288 return true;
289 } else {
290 return false;
291 }
292 } else {
293 return false;
294 }
295 }
296
297
getOffsetHdr(float * offset_hdr,bool * present)298 bool getOffsetHdr(float* offset_hdr, bool* present) {
299 if (state == Done) {
300 *present = offsetHdrFound;
301 stringstream ss(offsetHdrStr);
302 float val;
303 if (ss >> val) {
304 *offset_hdr = val;
305 return true;
306 } else {
307 return false;
308 }
309 } else {
310 return false;
311 }
312 }
313
314
getHdrCapacityMin(float * hdr_capacity_min,bool * present)315 bool getHdrCapacityMin(float* hdr_capacity_min, bool* present) {
316 if (state == Done) {
317 *present = hdrCapacityMinFound;
318 stringstream ss(hdrCapacityMinStr);
319 float val;
320 if (ss >> val) {
321 *hdr_capacity_min = exp2(val);
322 return true;
323 } else {
324 return false;
325 }
326 } else {
327 return false;
328 }
329 }
330
331
getHdrCapacityMax(float * hdr_capacity_max,bool * present)332 bool getHdrCapacityMax(float* hdr_capacity_max, bool* present) {
333 if (state == Done) {
334 *present = hdrCapacityMaxFound;
335 stringstream ss(hdrCapacityMaxStr);
336 float val;
337 if (ss >> val) {
338 *hdr_capacity_max = exp2(val);
339 return true;
340 } else {
341 return false;
342 }
343 } else {
344 return false;
345 }
346 }
347
348
getBaseRenditionIsHdr(bool * base_rendition_is_hdr,bool * present)349 bool getBaseRenditionIsHdr(bool* base_rendition_is_hdr, bool* present) {
350 if (state == Done) {
351 *present = baseRenditionIsHdrFound;
352 if (!baseRenditionIsHdrStr.compare("False")) {
353 *base_rendition_is_hdr = false;
354 return true;
355 } else if (!baseRenditionIsHdrStr.compare("True")) {
356 *base_rendition_is_hdr = true;
357 return true;
358 } else {
359 return false;
360 }
361 } else {
362 return false;
363 }
364 }
365
366
367
368 private:
369 static const string containerName;
370
371 static const string versionAttrName;
372 string versionStr;
373 bool versionFound;
374 static const string maxContentBoostAttrName;
375 string maxContentBoostStr;
376 bool maxContentBoostFound;
377 static const string minContentBoostAttrName;
378 string minContentBoostStr;
379 bool minContentBoostFound;
380 static const string gammaAttrName;
381 string gammaStr;
382 bool gammaFound;
383 static const string offsetSdrAttrName;
384 string offsetSdrStr;
385 bool offsetSdrFound;
386 static const string offsetHdrAttrName;
387 string offsetHdrStr;
388 bool offsetHdrFound;
389 static const string hdrCapacityMinAttrName;
390 string hdrCapacityMinStr;
391 bool hdrCapacityMinFound;
392 static const string hdrCapacityMaxAttrName;
393 string hdrCapacityMaxStr;
394 bool hdrCapacityMaxFound;
395 static const string baseRenditionIsHdrAttrName;
396 string baseRenditionIsHdrStr;
397 bool baseRenditionIsHdrFound;
398
399 string lastAttributeName;
400 ParseState state;
401 };
402
403 // GContainer XMP constants - URI and namespace prefix
404 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
405 const string kContainerPrefix = "Container";
406
407 // GContainer XMP constants - element and attribute names
408 const string kConDirectory = Name(kContainerPrefix, "Directory");
409 const string kConItem = Name(kContainerPrefix, "Item");
410
411 // GContainer XMP constants - names for XMP handlers
412 const string XMPXmlHandler::containerName = "rdf:Description";
413 // Item XMP constants - URI and namespace prefix
414 const string kItemUri = "http://ns.google.com/photos/1.0/container/item/";
415 const string kItemPrefix = "Item";
416
417 // Item XMP constants - element and attribute names
418 const string kItemLength = Name(kItemPrefix, "Length");
419 const string kItemMime = Name(kItemPrefix, "Mime");
420 const string kItemSemantic = Name(kItemPrefix, "Semantic");
421
422 // Item XMP constants - element and attribute values
423 const string kSemanticPrimary = "Primary";
424 const string kSemanticGainMap = "GainMap";
425 const string kMimeImageJpeg = "image/jpeg";
426
427 // GainMap XMP constants - URI and namespace prefix
428 const string kGainMapUri = "http://ns.adobe.com/hdr-gain-map/1.0/";
429 const string kGainMapPrefix = "hdrgm";
430
431 // GainMap XMP constants - element and attribute names
432 const string kMapVersion = Name(kGainMapPrefix, "Version");
433 const string kMapGainMapMin = Name(kGainMapPrefix, "GainMapMin");
434 const string kMapGainMapMax = Name(kGainMapPrefix, "GainMapMax");
435 const string kMapGamma = Name(kGainMapPrefix, "Gamma");
436 const string kMapOffsetSdr = Name(kGainMapPrefix, "OffsetSDR");
437 const string kMapOffsetHdr = Name(kGainMapPrefix, "OffsetHDR");
438 const string kMapHDRCapacityMin = Name(kGainMapPrefix, "HDRCapacityMin");
439 const string kMapHDRCapacityMax = Name(kGainMapPrefix, "HDRCapacityMax");
440 const string kMapBaseRenditionIsHDR = Name(kGainMapPrefix, "BaseRenditionIsHDR");
441
442 // GainMap XMP constants - names for XMP handlers
443 const string XMPXmlHandler::versionAttrName = kMapVersion;
444 const string XMPXmlHandler::minContentBoostAttrName = kMapGainMapMin;
445 const string XMPXmlHandler::maxContentBoostAttrName = kMapGainMapMax;
446 const string XMPXmlHandler::gammaAttrName = kMapGamma;
447 const string XMPXmlHandler::offsetSdrAttrName = kMapOffsetSdr;
448 const string XMPXmlHandler::offsetHdrAttrName = kMapOffsetHdr;
449 const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin;
450 const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax;
451 const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR;
452
getMetadataFromXMP(uint8_t * xmp_data,size_t xmp_size,ultrahdr_metadata_struct * metadata)453 bool getMetadataFromXMP(uint8_t* xmp_data, size_t xmp_size, ultrahdr_metadata_struct* metadata) {
454 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
455
456 if (xmp_size < nameSpace.size()+2) {
457 // Data too short
458 return false;
459 }
460
461 if (strncmp(reinterpret_cast<char*>(xmp_data), nameSpace.c_str(), nameSpace.size())) {
462 // Not correct namespace
463 return false;
464 }
465
466 // Position the pointers to the start of XMP XML portion
467 xmp_data += nameSpace.size()+1;
468 xmp_size -= nameSpace.size()+1;
469 XMPXmlHandler handler;
470
471 // We need to remove tail data until the closing tag. Otherwise parser will throw an error.
472 while(xmp_data[xmp_size-1]!='>' && xmp_size > 1) {
473 xmp_size--;
474 }
475
476 string str(reinterpret_cast<const char*>(xmp_data), xmp_size);
477 MessageHandler msg_handler;
478 unique_ptr<XmlRule> rule(new XmlElementRule);
479 XmlReader reader(&handler, &msg_handler);
480 reader.StartParse(std::move(rule));
481 reader.Parse(str);
482 reader.FinishParse();
483 if (reader.HasErrors()) {
484 // Parse error
485 return false;
486 }
487
488 // Apply default values to any not-present fields, except for Version,
489 // maxContentBoost, and hdrCapacityMax, which are required. Return false if
490 // we encounter a present field that couldn't be parsed, since this
491 // indicates it is invalid (eg. string where there should be a float).
492 bool present = false;
493 if (!handler.getVersion(&metadata->version, &present) || !present) {
494 return false;
495 }
496 if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) {
497 return false;
498 }
499 if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) {
500 return false;
501 }
502 if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) {
503 if (present) return false;
504 metadata->minContentBoost = 1.0f;
505 }
506 if (!handler.getGamma(&metadata->gamma, &present)) {
507 if (present) return false;
508 metadata->gamma = 1.0f;
509 }
510 if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) {
511 if (present) return false;
512 metadata->offsetSdr = 1.0f / 64.0f;
513 }
514 if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) {
515 if (present) return false;
516 metadata->offsetHdr = 1.0f / 64.0f;
517 }
518 if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) {
519 if (present) return false;
520 metadata->hdrCapacityMin = 1.0f;
521 }
522
523 bool base_rendition_is_hdr;
524 if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) {
525 if (present) return false;
526 base_rendition_is_hdr = false;
527 }
528 if (base_rendition_is_hdr) {
529 ALOGE("Base rendition of HDR is not supported!");
530 return false;
531 }
532
533 return true;
534 }
535
generateXmpForPrimaryImage(int secondary_image_length,ultrahdr_metadata_struct & metadata)536 string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) {
537 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
538 const vector<string> kLiItem({string("rdf:li"), kConItem});
539
540 std::stringstream ss;
541 photos_editing_formats::image_io::XmlWriter writer(ss);
542 writer.StartWritingElement("x:xmpmeta");
543 writer.WriteXmlns("x", "adobe:ns:meta/");
544 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
545 writer.StartWritingElement("rdf:RDF");
546 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
547 writer.StartWritingElement("rdf:Description");
548 writer.WriteXmlns(kContainerPrefix, kContainerUri);
549 writer.WriteXmlns(kItemPrefix, kItemUri);
550 writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
551 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
552
553 writer.StartWritingElements(kConDirSeq);
554
555 size_t item_depth = writer.StartWritingElement("rdf:li");
556 writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
557 writer.StartWritingElement(kConItem);
558 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticPrimary);
559 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
560 writer.FinishWritingElementsToDepth(item_depth);
561
562 writer.StartWritingElement("rdf:li");
563 writer.WriteAttributeNameAndValue("rdf:parseType", "Resource");
564 writer.StartWritingElement(kConItem);
565 writer.WriteAttributeNameAndValue(kItemSemantic, kSemanticGainMap);
566 writer.WriteAttributeNameAndValue(kItemMime, kMimeImageJpeg);
567 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
568
569 writer.FinishWriting();
570
571 return ss.str();
572 }
573
generateXmpForSecondaryImage(ultrahdr_metadata_struct & metadata)574 string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) {
575 const vector<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
576
577 std::stringstream ss;
578 photos_editing_formats::image_io::XmlWriter writer(ss);
579 writer.StartWritingElement("x:xmpmeta");
580 writer.WriteXmlns("x", "adobe:ns:meta/");
581 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
582 writer.StartWritingElement("rdf:RDF");
583 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
584 writer.StartWritingElement("rdf:Description");
585 writer.WriteXmlns(kGainMapPrefix, kGainMapUri);
586 writer.WriteAttributeNameAndValue(kMapVersion, metadata.version);
587 writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost));
588 writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost));
589 writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma);
590 writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr);
591 writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr);
592 writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin));
593 writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax));
594 writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False");
595 writer.FinishWriting();
596
597 return ss.str();
598 }
599
600 } // namespace android::ultrahdr
601