blob: d46025c4415e05dcb5b8d190a3ca6a864532ad21 [file] [log] [blame]
Dichen Zhang85b37562022-10-11 11:08:28 -07001/*
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
Nick Deakinf6bca5a2022-11-04 10:43:43 -040017// TODO: need to clean up handling around hdr_ratio and passing it around
18// TODO: need to handle color space information; currently we assume everything
19// is srgb in.
20// TODO: handle PQ encode/decode (currently only HLG)
Dichen Zhang72fd2b12022-11-01 06:11:50 +000021
Dichen Zhang85b37562022-10-11 11:08:28 -070022#include <jpegrecoverymap/recoverymap.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040023
24#include <jpegrecoverymap/jpegencoder.h>
25#include <jpegrecoverymap/jpegdecoder.h>
26#include <jpegrecoverymap/recoverymapmath.h>
27
28#include <image_io/xml/xml_writer.h>
29
30#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000031#include <sstream>
32#include <string>
33
34using namespace std;
Dichen Zhang85b37562022-10-11 11:08:28 -070035
36namespace android::recoverymap {
37
Nick Deakinf6bca5a2022-11-04 10:43:43 -040038#define JPEGR_CHECK(x) \
39 { \
40 status_t status = (x); \
41 if ((status) != NO_ERROR) { \
42 return status; \
43 } \
44 }
45
46// Map is quarter res / sixteenth size
47static const size_t kMapDimensionScaleFactor = 4;
48
49
Dichen Zhang72fd2b12022-11-01 06:11:50 +000050/*
51 * Helper function used for generating XMP metadata.
52 *
53 * @param prefix The prefix part of the name.
54 * @param suffix The suffix part of the name.
55 * @return A name of the form "prefix:suffix".
56 */
57string Name(const string &prefix, const string &suffix) {
58 std::stringstream ss;
59 ss << prefix << ":" << suffix;
60 return ss.str();
61}
62
Dichen Zhang6947d532022-10-22 02:16:21 +000063status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
64 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -040065 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +000066 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +000067 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +000068 if (uncompressed_p010_image == nullptr
69 || uncompressed_yuv_420_image == nullptr
70 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +000071 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +000072 }
73
Dichen Zhangffa34012022-11-03 23:21:13 +000074 if (quality < 0 || quality > 100) {
75 return ERROR_JPEGR_INVALID_INPUT_TYPE;
76 }
77
Nick Deakinf6bca5a2022-11-04 10:43:43 -040078 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
79 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
80 return ERROR_JPEGR_RESOLUTION_MISMATCH;
81 }
82
83 jpegr_uncompressed_struct map;
84 JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map));
85 std::unique_ptr<uint8_t[]> map_data;
86 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
87
88 jpegr_compressed_struct compressed_map;
89 std::unique_ptr<uint8_t[]> compressed_map_data =
90 std::make_unique<uint8_t[]>(map.width * map.height);
91 compressed_map.data = compressed_map_data.get();
92 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
93
94 JpegEncoder jpeg_encoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040095 // TODO: ICC data - need color space information
96 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
97 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +000098 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -040099 return ERROR_JPEGR_ENCODE_ERROR;
100 }
101 jpegr_compressed_struct jpeg;
102 jpeg.data = jpeg_encoder.getCompressedImagePtr();
103 jpeg.length = jpeg_encoder.getCompressedImageSize();
104
105 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, dest));
106
Dichen Zhang6947d532022-10-22 02:16:21 +0000107 return NO_ERROR;
108}
109
110status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
111 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400112 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000113 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000114 if (uncompressed_p010_image == nullptr
115 || uncompressed_yuv_420_image == nullptr
116 || compressed_jpeg_image == nullptr
117 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000118 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000119 }
120
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400121 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
122 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
123 return ERROR_JPEGR_RESOLUTION_MISMATCH;
124 }
125
126 jpegr_uncompressed_struct map;
127 JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map));
128 std::unique_ptr<uint8_t[]> map_data;
129 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
130
131 jpegr_compressed_struct compressed_map;
132 std::unique_ptr<uint8_t[]> compressed_map_data =
133 std::make_unique<uint8_t[]>(map.width * map.height);
134 compressed_map.data = compressed_map_data.get();
135 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
136
137 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest));
138
Dichen Zhang6947d532022-10-22 02:16:21 +0000139 return NO_ERROR;
140}
141
142status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400143 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000144 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000145 if (uncompressed_p010_image == nullptr
146 || compressed_jpeg_image == nullptr
147 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000148 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000149 }
150
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400151 JpegDecoder jpeg_decoder;
152 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
153 return ERROR_JPEGR_DECODE_ERROR;
154 }
155 jpegr_uncompressed_struct uncompressed_yuv_420_image;
156 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
157 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
158 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
159
160 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
161 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
162 return ERROR_JPEGR_RESOLUTION_MISMATCH;
163 }
164
165 jpegr_uncompressed_struct map;
166 JPEGR_CHECK(generateRecoveryMap(&uncompressed_yuv_420_image, uncompressed_p010_image, &map));
167 std::unique_ptr<uint8_t[]> map_data;
168 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
169
170 jpegr_compressed_struct compressed_map;
171 std::unique_ptr<uint8_t[]> compressed_map_data =
172 std::make_unique<uint8_t[]>(map.width * map.height);
173 compressed_map.data = compressed_map_data.get();
174 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
175
176 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest));
177
Dichen Zhang6947d532022-10-22 02:16:21 +0000178 return NO_ERROR;
179}
180
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400181status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000182 jr_uncompressed_ptr dest,
183 jr_exif_ptr /* exif */,
184 bool /* request_sdr */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000185 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000186 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000187 }
188
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400189 jpegr_compressed_struct compressed_map;
190 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
191
192 jpegr_uncompressed_struct map;
193 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
194
195 JpegDecoder jpeg_decoder;
196 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
197 return ERROR_JPEGR_DECODE_ERROR;
198 }
199
200 jpegr_uncompressed_struct uncompressed_yuv_420_image;
201 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
202 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
203 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
204
205 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
206
Dichen Zhang6947d532022-10-22 02:16:21 +0000207 return NO_ERROR;
208}
209
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400210status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
211 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700212 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000213 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700214 }
215
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400216 JpegDecoder jpeg_decoder;
217 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
218 compressed_recovery_map->length)) {
219 return ERROR_JPEGR_DECODE_ERROR;
220 }
221
222 dest->data = jpeg_decoder.getDecompressedImagePtr();
223 dest->width = jpeg_decoder.getDecompressedImageWidth();
224 dest->height = jpeg_decoder.getDecompressedImageHeight();
225
Dichen Zhang6947d532022-10-22 02:16:21 +0000226 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700227}
228
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400229status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
230 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700231 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000232 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700233 }
234
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400235 // TODO: should we have ICC data?
236 JpegEncoder jpeg_encoder;
237 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
238 uncompressed_recovery_map->height, 85, nullptr, 0,
239 true /* isSingleChannel */)) {
240 return ERROR_JPEGR_ENCODE_ERROR;
241 }
242
243 if (dest->length < jpeg_encoder.getCompressedImageSize()) {
244 return ERROR_JPEGR_BUFFER_TOO_SMALL;
245 }
246
247 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
248 dest->length = jpeg_encoder.getCompressedImageSize();
249
Dichen Zhang6947d532022-10-22 02:16:21 +0000250 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700251}
252
Dichen Zhang6947d532022-10-22 02:16:21 +0000253status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
254 jr_uncompressed_ptr uncompressed_p010_image,
255 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700256 if (uncompressed_yuv_420_image == nullptr
257 || uncompressed_p010_image == nullptr
258 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000259 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700260 }
261
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400262 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
263 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
264 return ERROR_JPEGR_RESOLUTION_MISMATCH;
265 }
266
267 size_t image_width = uncompressed_yuv_420_image->width;
268 size_t image_height = uncompressed_yuv_420_image->height;
269 size_t map_width = image_width / kMapDimensionScaleFactor;
270 size_t map_height = image_height / kMapDimensionScaleFactor;
271
272 dest->width = map_width;
273 dest->height = map_height;
274 dest->data = new uint8_t[map_width * map_height];
275 std::unique_ptr<uint8_t[]> map_data;
276 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
277
278 uint16_t yp_hdr_max = 0;
279 for (size_t y = 0; y < image_height; ++y) {
280 for (size_t x = 0; x < image_width; ++x) {
281 size_t pixel_idx = x + y * image_width;
282 uint16_t yp_hdr = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[pixel_idx];
283 if (yp_hdr > yp_hdr_max) {
284 yp_hdr_max = yp_hdr;
285 }
286 }
287 }
288
289 float y_hdr_max_nits = hlgInvOetf(yp_hdr_max);
290 float hdr_ratio = y_hdr_max_nits / kSdrWhiteNits;
291
292 for (size_t y = 0; y < map_height; ++y) {
293 for (size_t x = 0; x < map_width; ++x) {
294 float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
295 float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
296
297 float y_sdr_nits = srgbInvOetf(yp_sdr);
298 float y_hdr_nits = hlgInvOetf(yp_hdr);
299
300 size_t pixel_idx = x + y * map_width;
301 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
302 encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio);
303 }
304 }
305
306 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000307 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700308}
309
Dichen Zhang6947d532022-10-22 02:16:21 +0000310status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
311 jr_uncompressed_ptr uncompressed_recovery_map,
312 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700313 if (uncompressed_yuv_420_image == nullptr
314 || uncompressed_recovery_map == nullptr
315 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000316 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700317 }
318
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400319 // TODO: need to get this from the XMP; should probably be a function
320 // parameter
321 float hdr_ratio = 4.0f;
322
323 size_t width = uncompressed_yuv_420_image->width;
324 size_t height = uncompressed_yuv_420_image->height;
325
326 dest->width = width;
327 dest->height = height;
328 size_t pixel_count = width * height;
329
330 for (size_t y = 0; y < height; ++y) {
331 for (size_t x = 0; x < width; ++x) {
332 size_t pixel_y_idx = x + y * width;
333
334 size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
335
336 Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
337 Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
338 Color rgb_sdr = srgbInvOetf(rgbp_sdr);
339
340 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
341 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
342
343 Color rgbp_hdr = hlgOetf(rgb_hdr);
344 Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr);
345
346 reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
347 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
348 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
349 }
350 }
351
Dichen Zhang6947d532022-10-22 02:16:21 +0000352 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700353}
354
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400355status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
356 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000357 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000358 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700359 }
360
361 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000362 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700363}
364
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400365status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
366 jr_compressed_ptr compressed_recovery_map,
367 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000368 if (compressed_jpeg_image == nullptr
369 || compressed_recovery_map == nullptr
370 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000371 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700372 }
373
374 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000375 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700376}
377
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000378string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
379 const string kContainerPrefix = "GContainer";
380 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
381 const string kItemPrefix = "Item";
382 const string kRecoveryMap = "RecoveryMap";
383 const string kDirectory = "Directory";
384 const string kImageJpeg = "image/jpeg";
385 const string kItem = "Item";
386 const string kLength = "Length";
387 const string kMime = "Mime";
388 const string kPrimary = "Primary";
389 const string kSemantic = "Semantic";
390 const string kVersion = "Version";
391 const int kVersionValue = 1;
392
393 const string kConDir = Name(kContainerPrefix, kDirectory);
394 const string kContainerItem = Name(kContainerPrefix, kItem);
395 const string kItemLength = Name(kItemPrefix, kLength);
396 const string kItemMime = Name(kItemPrefix, kMime);
397 const string kItemSemantic = Name(kItemPrefix, kSemantic);
398
399 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
400 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
401
402 std::stringstream ss;
403 photos_editing_formats::image_io::XmlWriter writer(ss);
404 writer.StartWritingElement("x:xmpmeta");
405 writer.WriteXmlns("x", "adobe:ns:meta/");
406 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
407 writer.StartWritingElement("rdf:RDF");
408 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
409 writer.StartWritingElement("rdf:Description");
410 writer.WriteXmlns(kContainerPrefix, kContainerUri);
411 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
412 writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
413 writer.StartWritingElements(kConDirSeq);
414 size_t item_depth = writer.StartWritingElements(kLiItem);
415 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
416 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
417 writer.FinishWritingElementsToDepth(item_depth);
418 writer.StartWritingElements(kLiItem);
419 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
420 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
421 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
422 writer.FinishWriting();
423
424 return ss.str();
425}
426
Dichen Zhang85b37562022-10-11 11:08:28 -0700427} // namespace android::recoverymap