blob: bd16a68b0d047887bdd7d8fbc2efe299414aff8a [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#include <jpegrecoverymap/jpegencoder.h>
24#include <jpegrecoverymap/jpegdecoder.h>
25#include <jpegrecoverymap/recoverymapmath.h>
26
Dichen Zhanga8766262022-11-07 23:48:24 +000027#include <image_io/jpeg/jpeg_marker.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040028#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 Zhanga8766262022-11-07 23:48:24 +000063/*
64 * Helper function used for writing data to destination.
65 *
66 * @param destination destination of the data to be written.
67 * @param source source of data being written.
68 * @param length length of the data to be written.
69 * @param position cursor in desitination where the data is to be written.
70 * @return status of succeed or error code.
71 */
72status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
73 if (position + length > destination->length) {
74 return ERROR_JPEGR_BUFFER_TOO_SMALL;
75 }
76
77 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
78 position += length;
79 return NO_ERROR;
80}
81
Dichen Zhang6947d532022-10-22 02:16:21 +000082status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
83 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -040084 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +000085 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +000086 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +000087 if (uncompressed_p010_image == nullptr
88 || uncompressed_yuv_420_image == nullptr
89 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +000090 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +000091 }
92
Dichen Zhangffa34012022-11-03 23:21:13 +000093 if (quality < 0 || quality > 100) {
94 return ERROR_JPEGR_INVALID_INPUT_TYPE;
95 }
96
Nick Deakinf6bca5a2022-11-04 10:43:43 -040097 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
98 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
99 return ERROR_JPEGR_RESOLUTION_MISMATCH;
100 }
101
102 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000103 float hdr_ratio = 0.0f;
104 JPEGR_CHECK(generateRecoveryMap(
105 uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400106 std::unique_ptr<uint8_t[]> map_data;
107 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
108
109 jpegr_compressed_struct compressed_map;
110 std::unique_ptr<uint8_t[]> compressed_map_data =
111 std::make_unique<uint8_t[]>(map.width * map.height);
112 compressed_map.data = compressed_map_data.get();
113 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
114
115 JpegEncoder jpeg_encoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400116 // TODO: ICC data - need color space information
117 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
118 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000119 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400120 return ERROR_JPEGR_ENCODE_ERROR;
121 }
122 jpegr_compressed_struct jpeg;
123 jpeg.data = jpeg_encoder.getCompressedImagePtr();
124 jpeg.length = jpeg_encoder.getCompressedImageSize();
125
Dichen Zhanga8766262022-11-07 23:48:24 +0000126 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, hdr_ratio, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400127
Dichen Zhang6947d532022-10-22 02:16:21 +0000128 return NO_ERROR;
129}
130
131status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
132 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400133 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000134 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000135 if (uncompressed_p010_image == nullptr
136 || uncompressed_yuv_420_image == nullptr
137 || compressed_jpeg_image == nullptr
138 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000139 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000140 }
141
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400142 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
143 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
144 return ERROR_JPEGR_RESOLUTION_MISMATCH;
145 }
146
147 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000148 float hdr_ratio = 0.0f;
149 JPEGR_CHECK(generateRecoveryMap(
150 uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400151 std::unique_ptr<uint8_t[]> map_data;
152 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
153
154 jpegr_compressed_struct compressed_map;
155 std::unique_ptr<uint8_t[]> compressed_map_data =
156 std::make_unique<uint8_t[]>(map.width * map.height);
157 compressed_map.data = compressed_map_data.get();
158 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
159
Dichen Zhanga8766262022-11-07 23:48:24 +0000160 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400161
Dichen Zhang6947d532022-10-22 02:16:21 +0000162 return NO_ERROR;
163}
164
165status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400166 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000167 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000168 if (uncompressed_p010_image == nullptr
169 || compressed_jpeg_image == nullptr
170 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000171 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000172 }
173
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400174 JpegDecoder jpeg_decoder;
175 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
176 return ERROR_JPEGR_DECODE_ERROR;
177 }
178 jpegr_uncompressed_struct uncompressed_yuv_420_image;
179 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
180 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
181 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
182
183 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
184 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
185 return ERROR_JPEGR_RESOLUTION_MISMATCH;
186 }
187
188 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000189 float hdr_ratio = 0.0f;
190 JPEGR_CHECK(generateRecoveryMap(
191 &uncompressed_yuv_420_image, uncompressed_p010_image, &map, hdr_ratio));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400192 std::unique_ptr<uint8_t[]> map_data;
193 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
194
195 jpegr_compressed_struct compressed_map;
196 std::unique_ptr<uint8_t[]> compressed_map_data =
197 std::make_unique<uint8_t[]>(map.width * map.height);
198 compressed_map.data = compressed_map_data.get();
199 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
200
Dichen Zhanga8766262022-11-07 23:48:24 +0000201 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, hdr_ratio, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400202
Dichen Zhang6947d532022-10-22 02:16:21 +0000203 return NO_ERROR;
204}
205
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400206status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000207 jr_uncompressed_ptr dest,
208 jr_exif_ptr /* exif */,
209 bool /* request_sdr */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000210 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000211 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000212 }
213
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400214 jpegr_compressed_struct compressed_map;
215 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
216
217 jpegr_uncompressed_struct map;
218 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
219
220 JpegDecoder jpeg_decoder;
221 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
222 return ERROR_JPEGR_DECODE_ERROR;
223 }
224
225 jpegr_uncompressed_struct uncompressed_yuv_420_image;
226 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
227 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
228 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
229
230 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, dest));
231
Dichen Zhang6947d532022-10-22 02:16:21 +0000232 return NO_ERROR;
233}
234
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400235status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
236 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700237 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000238 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700239 }
240
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400241 JpegDecoder jpeg_decoder;
242 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
243 compressed_recovery_map->length)) {
244 return ERROR_JPEGR_DECODE_ERROR;
245 }
246
247 dest->data = jpeg_decoder.getDecompressedImagePtr();
248 dest->width = jpeg_decoder.getDecompressedImageWidth();
249 dest->height = jpeg_decoder.getDecompressedImageHeight();
250
Dichen Zhang6947d532022-10-22 02:16:21 +0000251 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700252}
253
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400254status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
255 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700256 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000257 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700258 }
259
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400260 // TODO: should we have ICC data?
261 JpegEncoder jpeg_encoder;
262 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
263 uncompressed_recovery_map->height, 85, nullptr, 0,
264 true /* isSingleChannel */)) {
265 return ERROR_JPEGR_ENCODE_ERROR;
266 }
267
268 if (dest->length < jpeg_encoder.getCompressedImageSize()) {
269 return ERROR_JPEGR_BUFFER_TOO_SMALL;
270 }
271
272 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
273 dest->length = jpeg_encoder.getCompressedImageSize();
274
Dichen Zhang6947d532022-10-22 02:16:21 +0000275 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700276}
277
Dichen Zhang6947d532022-10-22 02:16:21 +0000278status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
279 jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhanga8766262022-11-07 23:48:24 +0000280 jr_uncompressed_ptr dest,
281 float &hdr_ratio) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700282 if (uncompressed_yuv_420_image == nullptr
283 || uncompressed_p010_image == nullptr
284 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000285 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700286 }
287
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400288 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
289 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
290 return ERROR_JPEGR_RESOLUTION_MISMATCH;
291 }
292
293 size_t image_width = uncompressed_yuv_420_image->width;
294 size_t image_height = uncompressed_yuv_420_image->height;
295 size_t map_width = image_width / kMapDimensionScaleFactor;
296 size_t map_height = image_height / kMapDimensionScaleFactor;
297
298 dest->width = map_width;
299 dest->height = map_height;
300 dest->data = new uint8_t[map_width * map_height];
301 std::unique_ptr<uint8_t[]> map_data;
302 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
303
304 uint16_t yp_hdr_max = 0;
305 for (size_t y = 0; y < image_height; ++y) {
306 for (size_t x = 0; x < image_width; ++x) {
307 size_t pixel_idx = x + y * image_width;
308 uint16_t yp_hdr = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[pixel_idx];
309 if (yp_hdr > yp_hdr_max) {
310 yp_hdr_max = yp_hdr;
311 }
312 }
313 }
314
315 float y_hdr_max_nits = hlgInvOetf(yp_hdr_max);
Dichen Zhanga8766262022-11-07 23:48:24 +0000316 hdr_ratio = y_hdr_max_nits / kSdrWhiteNits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400317
318 for (size_t y = 0; y < map_height; ++y) {
319 for (size_t x = 0; x < map_width; ++x) {
320 float yp_sdr = sampleYuv420Y(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
321 float yp_hdr = sampleP010Y(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
322
323 float y_sdr_nits = srgbInvOetf(yp_sdr);
324 float y_hdr_nits = hlgInvOetf(yp_hdr);
325
326 size_t pixel_idx = x + y * map_width;
327 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
328 encodeRecovery(y_sdr_nits, y_hdr_nits, hdr_ratio);
329 }
330 }
331
332 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000333 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700334}
335
Dichen Zhang6947d532022-10-22 02:16:21 +0000336status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
337 jr_uncompressed_ptr uncompressed_recovery_map,
338 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700339 if (uncompressed_yuv_420_image == nullptr
340 || uncompressed_recovery_map == nullptr
341 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000342 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700343 }
344
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400345 // TODO: need to get this from the XMP; should probably be a function
346 // parameter
347 float hdr_ratio = 4.0f;
348
349 size_t width = uncompressed_yuv_420_image->width;
350 size_t height = uncompressed_yuv_420_image->height;
351
352 dest->width = width;
353 dest->height = height;
354 size_t pixel_count = width * height;
355
356 for (size_t y = 0; y < height; ++y) {
357 for (size_t x = 0; x < width; ++x) {
358 size_t pixel_y_idx = x + y * width;
359
360 size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
361
362 Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
363 Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
364 Color rgb_sdr = srgbInvOetf(rgbp_sdr);
365
366 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
367 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
368
369 Color rgbp_hdr = hlgOetf(rgb_hdr);
370 Color ypuv_hdr = bt2100RgbToYuv(rgbp_hdr);
371
372 reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
373 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
374 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
375 }
376 }
377
Dichen Zhang6947d532022-10-22 02:16:21 +0000378 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700379}
380
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400381status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
382 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000383 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000384 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700385 }
386
387 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000388 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700389}
390
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400391status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
392 jr_compressed_ptr compressed_recovery_map,
Dichen Zhanga8766262022-11-07 23:48:24 +0000393 float hdr_ratio,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400394 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000395 if (compressed_jpeg_image == nullptr
396 || compressed_recovery_map == nullptr
397 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000398 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700399 }
400
Dichen Zhanga8766262022-11-07 23:48:24 +0000401 string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio);
402 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
403
404 // 2 bytes: APP1 sign (ff e1)
405 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
406 // x bytes: length of xmp packet
407 int length = 2 + nameSpace.size() + xmp.size();
408 uint8_t lengthH = ((length >> 8) & 0xff);
409 uint8_t lengthL = (length & 0xff);
410
411 int pos = 0;
412
413 // JPEG/R structure:
414 // SOI (ff d8)
415 // APP1 (ff e1)
416 // 2 bytes of length (2 + 29 + length of xmp packet)
417 // name space ("http://ns.adobe.com/xap/1.0/\0")
418 // xmp
419 // primary image (without the first two bytes, the SOI sign)
420 // secondary image (the recovery map)
421 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
422 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
423 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
424 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
425 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
426 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
427 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
428 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
429 JPEGR_CHECK(Write(dest,
430 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
431 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
432 dest->length = pos;
433
Dichen Zhang6947d532022-10-22 02:16:21 +0000434 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700435}
436
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000437string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
438 const string kContainerPrefix = "GContainer";
439 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
440 const string kItemPrefix = "Item";
441 const string kRecoveryMap = "RecoveryMap";
442 const string kDirectory = "Directory";
443 const string kImageJpeg = "image/jpeg";
444 const string kItem = "Item";
445 const string kLength = "Length";
446 const string kMime = "Mime";
447 const string kPrimary = "Primary";
448 const string kSemantic = "Semantic";
449 const string kVersion = "Version";
450 const int kVersionValue = 1;
451
452 const string kConDir = Name(kContainerPrefix, kDirectory);
453 const string kContainerItem = Name(kContainerPrefix, kItem);
454 const string kItemLength = Name(kItemPrefix, kLength);
455 const string kItemMime = Name(kItemPrefix, kMime);
456 const string kItemSemantic = Name(kItemPrefix, kSemantic);
457
458 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
459 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
460
461 std::stringstream ss;
462 photos_editing_formats::image_io::XmlWriter writer(ss);
463 writer.StartWritingElement("x:xmpmeta");
464 writer.WriteXmlns("x", "adobe:ns:meta/");
465 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
466 writer.StartWritingElement("rdf:RDF");
467 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
468 writer.StartWritingElement("rdf:Description");
469 writer.WriteXmlns(kContainerPrefix, kContainerUri);
470 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
471 writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
472 writer.StartWritingElements(kConDirSeq);
473 size_t item_depth = writer.StartWritingElements(kLiItem);
474 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
475 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
476 writer.FinishWritingElementsToDepth(item_depth);
477 writer.StartWritingElements(kLiItem);
478 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
479 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
480 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
481 writer.FinishWriting();
482
483 return ss.str();
484}
485
Dichen Zhang85b37562022-10-11 11:08:28 -0700486} // namespace android::recoverymap