blob: 4a90053b12a33873c629f5d75825a5ad32904d7e [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,
67 jr_exif_ptr /* exif */,
68 float /* hdr_ratio */) {
Dichen Zhang6947d532022-10-22 02:16:21 +000069 if (uncompressed_p010_image == nullptr
70 || uncompressed_yuv_420_image == nullptr
71 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +000072 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +000073 }
74
Dichen Zhangffa34012022-11-03 23:21:13 +000075 if (quality < 0 || quality > 100) {
76 return ERROR_JPEGR_INVALID_INPUT_TYPE;
77 }
78
Nick Deakinf6bca5a2022-11-04 10:43:43 -040079 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
80 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
81 return ERROR_JPEGR_RESOLUTION_MISMATCH;
82 }
83
84 jpegr_uncompressed_struct map;
85 JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map));
86 std::unique_ptr<uint8_t[]> map_data;
87 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
88
89 jpegr_compressed_struct compressed_map;
90 std::unique_ptr<uint8_t[]> compressed_map_data =
91 std::make_unique<uint8_t[]>(map.width * map.height);
92 compressed_map.data = compressed_map_data.get();
93 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
94
95 JpegEncoder jpeg_encoder;
96 // TODO: what quality to use?
97 // TODO: ICC data - need color space information
98 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
99 uncompressed_yuv_420_image->width,
100 uncompressed_yuv_420_image->height, 95, nullptr, 0)) {
101 return ERROR_JPEGR_ENCODE_ERROR;
102 }
103 jpegr_compressed_struct jpeg;
104 jpeg.data = jpeg_encoder.getCompressedImagePtr();
105 jpeg.length = jpeg_encoder.getCompressedImageSize();
106
107 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, dest));
108
Dichen Zhang6947d532022-10-22 02:16:21 +0000109 return NO_ERROR;
110}
111
112status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
113 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400114 jr_compressed_ptr compressed_jpeg_image,
115 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000116 float /* hdr_ratio */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000117 if (uncompressed_p010_image == nullptr
118 || uncompressed_yuv_420_image == nullptr
119 || compressed_jpeg_image == nullptr
120 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000121 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000122 }
123
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400124 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
125 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
126 return ERROR_JPEGR_RESOLUTION_MISMATCH;
127 }
128
129 jpegr_uncompressed_struct map;
130 JPEGR_CHECK(generateRecoveryMap(uncompressed_yuv_420_image, uncompressed_p010_image, &map));
131 std::unique_ptr<uint8_t[]> map_data;
132 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
133
134 jpegr_compressed_struct compressed_map;
135 std::unique_ptr<uint8_t[]> compressed_map_data =
136 std::make_unique<uint8_t[]>(map.width * map.height);
137 compressed_map.data = compressed_map_data.get();
138 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
139
140 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest));
141
Dichen Zhang6947d532022-10-22 02:16:21 +0000142 return NO_ERROR;
143}
144
145status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400146 jr_compressed_ptr compressed_jpeg_image,
147 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000148 float /* hdr_ratio */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000149 if (uncompressed_p010_image == nullptr
150 || compressed_jpeg_image == nullptr
151 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000152 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000153 }
154
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400155 JpegDecoder jpeg_decoder;
156 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
157 return ERROR_JPEGR_DECODE_ERROR;
158 }
159 jpegr_uncompressed_struct uncompressed_yuv_420_image;
160 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
161 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
162 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
163
164 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
165 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
166 return ERROR_JPEGR_RESOLUTION_MISMATCH;
167 }
168
169 jpegr_uncompressed_struct map;
170 JPEGR_CHECK(generateRecoveryMap(&uncompressed_yuv_420_image, uncompressed_p010_image, &map));
171 std::unique_ptr<uint8_t[]> map_data;
172 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
173
174 jpegr_compressed_struct compressed_map;
175 std::unique_ptr<uint8_t[]> compressed_map_data =
176 std::make_unique<uint8_t[]>(map.width * map.height);
177 compressed_map.data = compressed_map_data.get();
178 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
179
180 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, dest));
181
Dichen Zhang6947d532022-10-22 02:16:21 +0000182 return NO_ERROR;
183}
184
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400185status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000186 jr_uncompressed_ptr dest,
187 jr_exif_ptr /* exif */,
188 bool /* request_sdr */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000189 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000190 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000191 }
192
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400193 jpegr_compressed_struct compressed_map;
194 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
195
196 jpegr_uncompressed_struct map;
197 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
198
199 JpegDecoder jpeg_decoder;
200 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
201 return ERROR_JPEGR_DECODE_ERROR;
202 }
203
204 jpegr_uncompressed_struct uncompressed_yuv_420_image;
205 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
206 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
207 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
208
209 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
210
Dichen Zhang6947d532022-10-22 02:16:21 +0000211 return NO_ERROR;
212}
213
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400214status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
215 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700216 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000217 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700218 }
219
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400220 JpegDecoder jpeg_decoder;
221 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
222 compressed_recovery_map->length)) {
223 return ERROR_JPEGR_DECODE_ERROR;
224 }
225
226 dest->data = jpeg_decoder.getDecompressedImagePtr();
227 dest->width = jpeg_decoder.getDecompressedImageWidth();
228 dest->height = jpeg_decoder.getDecompressedImageHeight();
229
Dichen Zhang6947d532022-10-22 02:16:21 +0000230 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700231}
232
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400233status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
234 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700235 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000236 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700237 }
238
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400239 // TODO: should we have ICC data?
240 JpegEncoder jpeg_encoder;
241 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
242 uncompressed_recovery_map->height, 85, nullptr, 0,
243 true /* isSingleChannel */)) {
244 return ERROR_JPEGR_ENCODE_ERROR;
245 }
246
247 if (dest->length < jpeg_encoder.getCompressedImageSize()) {
248 return ERROR_JPEGR_BUFFER_TOO_SMALL;
249 }
250
251 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
252 dest->length = jpeg_encoder.getCompressedImageSize();
253
Dichen Zhang6947d532022-10-22 02:16:21 +0000254 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700255}
256
Dichen Zhang6947d532022-10-22 02:16:21 +0000257status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
258 jr_uncompressed_ptr uncompressed_p010_image,
259 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700260 if (uncompressed_yuv_420_image == nullptr
261 || uncompressed_p010_image == nullptr
262 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000263 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700264 }
265
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400266 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
267 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
268 return ERROR_JPEGR_RESOLUTION_MISMATCH;
269 }
270
271 size_t image_width = uncompressed_yuv_420_image->width;
272 size_t image_height = uncompressed_yuv_420_image->height;
273 size_t map_width = image_width / kMapDimensionScaleFactor;
274 size_t map_height = image_height / kMapDimensionScaleFactor;
275
276 dest->width = map_width;
277 dest->height = map_height;
278 dest->data = new uint8_t[map_width * map_height];
279 std::unique_ptr<uint8_t[]> map_data;
280 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
281
282 uint16_t yp_hdr_max = 0;
283 for (size_t y = 0; y < image_height; ++y) {
284 for (size_t x = 0; x < image_width; ++x) {
285 size_t pixel_idx = x + y * image_width;
286 uint16_t yp_hdr = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[pixel_idx];
287 if (yp_hdr > yp_hdr_max) {
288 yp_hdr_max = yp_hdr;
289 }
290 }
291 }
292
293 float y_hdr_max_nits = hlgInvOetf(yp_hdr_max);
294 float hdr_ratio = y_hdr_max_nits / kSdrWhiteNits;
295
296 for (size_t y = 0; y < map_height; ++y) {
297 for (size_t x = 0; x < map_width; ++x) {
298 float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
299 float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
300
301 float y_sdr_nits = srgbInvOetf(yp_sdr);
302 float y_hdr_nits = hlgInvOetf(yp_hdr);
303
304 size_t pixel_idx = x + y * map_width;
305 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
306 encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio);
307 }
308 }
309
310 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000311 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700312}
313
Dichen Zhang6947d532022-10-22 02:16:21 +0000314status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
315 jr_uncompressed_ptr uncompressed_recovery_map,
316 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700317 if (uncompressed_yuv_420_image == nullptr
318 || uncompressed_recovery_map == nullptr
319 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000320 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700321 }
322
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400323 // TODO: need to get this from the XMP; should probably be a function
324 // parameter
325 float hdr_ratio = 4.0f;
326
327 size_t width = uncompressed_yuv_420_image->width;
328 size_t height = uncompressed_yuv_420_image->height;
329
330 dest->width = width;
331 dest->height = height;
332 size_t pixel_count = width * height;
333
334 for (size_t y = 0; y < height; ++y) {
335 for (size_t x = 0; x < width; ++x) {
336 size_t pixel_y_idx = x + y * width;
337
338 size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
339
340 Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
341 Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
342 Color rgb_sdr = srgbInvOetf(rgbp_sdr);
343
344 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
345 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
346
347 Color rgbp_hdr = hlgOetf(rgb_hdr);
348 Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr);
349
350 reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
351 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
352 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
353 }
354 }
355
Dichen Zhang6947d532022-10-22 02:16:21 +0000356 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700357}
358
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400359status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
360 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000361 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000362 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700363 }
364
365 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000366 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700367}
368
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400369status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
370 jr_compressed_ptr compressed_recovery_map,
371 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000372 if (compressed_jpeg_image == nullptr
373 || compressed_recovery_map == nullptr
374 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000375 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700376 }
377
378 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000379 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700380}
381
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000382string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
383 const string kContainerPrefix = "GContainer";
384 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
385 const string kItemPrefix = "Item";
386 const string kRecoveryMap = "RecoveryMap";
387 const string kDirectory = "Directory";
388 const string kImageJpeg = "image/jpeg";
389 const string kItem = "Item";
390 const string kLength = "Length";
391 const string kMime = "Mime";
392 const string kPrimary = "Primary";
393 const string kSemantic = "Semantic";
394 const string kVersion = "Version";
395 const int kVersionValue = 1;
396
397 const string kConDir = Name(kContainerPrefix, kDirectory);
398 const string kContainerItem = Name(kContainerPrefix, kItem);
399 const string kItemLength = Name(kItemPrefix, kLength);
400 const string kItemMime = Name(kItemPrefix, kMime);
401 const string kItemSemantic = Name(kItemPrefix, kSemantic);
402
403 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
404 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
405
406 std::stringstream ss;
407 photos_editing_formats::image_io::XmlWriter writer(ss);
408 writer.StartWritingElement("x:xmpmeta");
409 writer.WriteXmlns("x", "adobe:ns:meta/");
410 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
411 writer.StartWritingElement("rdf:RDF");
412 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
413 writer.StartWritingElement("rdf:Description");
414 writer.WriteXmlns(kContainerPrefix, kContainerUri);
415 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
416 writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
417 writer.StartWritingElements(kConDirSeq);
418 size_t item_depth = writer.StartWritingElements(kLiItem);
419 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
420 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
421 writer.FinishWritingElementsToDepth(item_depth);
422 writer.StartWritingElements(kLiItem);
423 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
424 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
425 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
426 writer.FinishWriting();
427
428 return ss.str();
429}
430
Dichen Zhang85b37562022-10-11 11:08:28 -0700431} // namespace android::recoverymap