blob: 86eb8a8f3e1cc6242c0cbd980366c7a660164e25 [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
Nick Deakin594a4ca2022-11-16 20:57:42 -0500304 float hdr_y_nits_max = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400305 for (size_t y = 0; y < image_height; ++y) {
306 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500307 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
308 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
309 Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
310 float hdr_y_nits = bt2100Luminance(hdr_rgb);
311
312 if (hdr_y_nits > hdr_y_nits_max) {
313 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400314 }
315 }
316 }
317
Nick Deakin594a4ca2022-11-16 20:57:42 -0500318 hdr_ratio = hdr_y_nits_max / kSdrWhiteNits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400319
320 for (size_t y = 0; y < map_height; ++y) {
321 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500322 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
323 kMapDimensionScaleFactor, x, y);
324 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
325 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
326 float sdr_y_nits = srgbLuminance(sdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400327
Nick Deakin594a4ca2022-11-16 20:57:42 -0500328 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
329 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
330 Color hdr_rgb = hlgInvOetf(hdr_rgb_gamma);
331 float hdr_y_nits = bt2100Luminance(hdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400332
333 size_t pixel_idx = x + y * map_width;
334 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin594a4ca2022-11-16 20:57:42 -0500335 encodeRecovery(sdr_y_nits, hdr_y_nits, hdr_ratio);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400336 }
337 }
338
339 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000340 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700341}
342
Dichen Zhang6947d532022-10-22 02:16:21 +0000343status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
344 jr_uncompressed_ptr uncompressed_recovery_map,
345 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700346 if (uncompressed_yuv_420_image == nullptr
347 || uncompressed_recovery_map == nullptr
348 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000349 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700350 }
351
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400352 // TODO: need to get this from the XMP; should probably be a function
353 // parameter
354 float hdr_ratio = 4.0f;
355
356 size_t width = uncompressed_yuv_420_image->width;
357 size_t height = uncompressed_yuv_420_image->height;
358
359 dest->width = width;
360 dest->height = height;
361 size_t pixel_count = width * height;
362
363 for (size_t y = 0; y < height; ++y) {
364 for (size_t x = 0; x < width; ++x) {
365 size_t pixel_y_idx = x + y * width;
366
367 size_t pixel_uv_idx = x / 2 + (y / 2) * (width / 2);
368
369 Color ypuv_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
370 Color rgbp_sdr = srgbYuvToRgb(ypuv_sdr);
371 Color rgb_sdr = srgbInvOetf(rgbp_sdr);
372
373 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
374 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
375
376 Color rgbp_hdr = hlgOetf(rgb_hdr);
Nick Deakin594a4ca2022-11-16 20:57:42 -0500377 // TODO: actually just leave in RGB and convert to RGBA1010102 instead.
378 Color ypuv_hdr = srgbRgbToYuv(rgbp_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400379
380 reinterpret_cast<uint16_t*>(dest->data)[pixel_y_idx] = ypuv_hdr.r;
381 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx] = ypuv_hdr.g;
382 reinterpret_cast<uint16_t*>(dest->data)[pixel_count + pixel_uv_idx + 1] = ypuv_hdr.b;
383 }
384 }
385
Dichen Zhang6947d532022-10-22 02:16:21 +0000386 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700387}
388
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400389status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
390 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000391 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000392 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700393 }
394
395 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000396 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700397}
398
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400399status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
400 jr_compressed_ptr compressed_recovery_map,
Dichen Zhanga8766262022-11-07 23:48:24 +0000401 float hdr_ratio,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400402 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000403 if (compressed_jpeg_image == nullptr
404 || compressed_recovery_map == nullptr
405 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000406 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700407 }
408
Dichen Zhanga8766262022-11-07 23:48:24 +0000409 string xmp = generateXmp(compressed_recovery_map->length, hdr_ratio);
410 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
411
412 // 2 bytes: APP1 sign (ff e1)
413 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
414 // x bytes: length of xmp packet
415 int length = 2 + nameSpace.size() + xmp.size();
416 uint8_t lengthH = ((length >> 8) & 0xff);
417 uint8_t lengthL = (length & 0xff);
418
419 int pos = 0;
420
421 // JPEG/R structure:
422 // SOI (ff d8)
423 // APP1 (ff e1)
424 // 2 bytes of length (2 + 29 + length of xmp packet)
425 // name space ("http://ns.adobe.com/xap/1.0/\0")
426 // xmp
427 // primary image (without the first two bytes, the SOI sign)
428 // secondary image (the recovery map)
429 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
430 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
431 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
432 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
433 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
434 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
435 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
436 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
437 JPEGR_CHECK(Write(dest,
438 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
439 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
440 dest->length = pos;
441
Dichen Zhang6947d532022-10-22 02:16:21 +0000442 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700443}
444
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000445string RecoveryMap::generateXmp(int secondary_image_length, float hdr_ratio) {
446 const string kContainerPrefix = "GContainer";
447 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
448 const string kItemPrefix = "Item";
449 const string kRecoveryMap = "RecoveryMap";
450 const string kDirectory = "Directory";
451 const string kImageJpeg = "image/jpeg";
452 const string kItem = "Item";
453 const string kLength = "Length";
454 const string kMime = "Mime";
455 const string kPrimary = "Primary";
456 const string kSemantic = "Semantic";
457 const string kVersion = "Version";
458 const int kVersionValue = 1;
459
460 const string kConDir = Name(kContainerPrefix, kDirectory);
461 const string kContainerItem = Name(kContainerPrefix, kItem);
462 const string kItemLength = Name(kItemPrefix, kLength);
463 const string kItemMime = Name(kItemPrefix, kMime);
464 const string kItemSemantic = Name(kItemPrefix, kSemantic);
465
466 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
467 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
468
469 std::stringstream ss;
470 photos_editing_formats::image_io::XmlWriter writer(ss);
471 writer.StartWritingElement("x:xmpmeta");
472 writer.WriteXmlns("x", "adobe:ns:meta/");
473 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
474 writer.StartWritingElement("rdf:RDF");
475 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
476 writer.StartWritingElement("rdf:Description");
477 writer.WriteXmlns(kContainerPrefix, kContainerUri);
478 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), kVersionValue);
479 writer.WriteElementAndContent(Name(kContainerPrefix, "HdrRatio"), hdr_ratio);
480 writer.StartWritingElements(kConDirSeq);
481 size_t item_depth = writer.StartWritingElements(kLiItem);
482 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
483 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
484 writer.FinishWritingElementsToDepth(item_depth);
485 writer.StartWritingElements(kLiItem);
486 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
487 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
488 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
489 writer.FinishWriting();
490
491 return ss.str();
492}
493
Dichen Zhang85b37562022-10-11 11:08:28 -0700494} // namespace android::recoverymap