blob: a744d157fde0bec909beff07c012c9833ab59b2c [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
17#include <jpegrecoverymap/recoverymap.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040018#include <jpegrecoverymap/jpegencoder.h>
19#include <jpegrecoverymap/jpegdecoder.h>
20#include <jpegrecoverymap/recoverymapmath.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000021#include <jpegrecoverymap/recoverymaputils.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040022
Dichen Zhanga8766262022-11-07 23:48:24 +000023#include <image_io/jpeg/jpeg_marker.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040024#include <image_io/xml/xml_writer.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000025#include <image_io/jpeg/jpeg_info.h>
26#include <image_io/jpeg/jpeg_scanner.h>
27#include <image_io/jpeg/jpeg_info_builder.h>
28#include <image_io/base/data_segment_data_source.h>
29#include <utils/Log.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040030
31#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000032#include <sstream>
33#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000034#include <cmath>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000035
36using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000037using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070038
39namespace android::recoverymap {
40
Nick Deakinf6bca5a2022-11-04 10:43:43 -040041#define JPEGR_CHECK(x) \
42 { \
43 status_t status = (x); \
44 if ((status) != NO_ERROR) { \
45 return status; \
46 } \
47 }
48
Nick Deakin6bd90432022-11-20 16:26:37 -050049// The current JPEGR version that we encode to
50static const uint32_t kJpegrVersion = 1;
51
Nick Deakinf6bca5a2022-11-04 10:43:43 -040052// Map is quarter res / sixteenth size
53static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000054// JPEG compress quality (0 ~ 100) for recovery map
55static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040056
Nick Deakin6bd90432022-11-20 16:26:37 -050057// TODO: fill in st2086 metadata
58static const st2086_metadata kSt2086Metadata = {
59 {0.0f, 0.0f},
60 {0.0f, 0.0f},
61 {0.0f, 0.0f},
62 {0.0f, 0.0f},
63 0,
64 1.0f,
65};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040066
Dichen Zhang72fd2b12022-11-01 06:11:50 +000067/*
68 * Helper function used for generating XMP metadata.
69 *
70 * @param prefix The prefix part of the name.
71 * @param suffix The suffix part of the name.
72 * @return A name of the form "prefix:suffix".
73 */
74string Name(const string &prefix, const string &suffix) {
75 std::stringstream ss;
76 ss << prefix << ":" << suffix;
77 return ss.str();
78}
79
Dichen Zhanga8766262022-11-07 23:48:24 +000080/*
81 * Helper function used for writing data to destination.
82 *
83 * @param destination destination of the data to be written.
84 * @param source source of data being written.
85 * @param length length of the data to be written.
86 * @param position cursor in desitination where the data is to be written.
87 * @return status of succeed or error code.
88 */
89status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000090 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000091 return ERROR_JPEGR_BUFFER_TOO_SMALL;
92 }
93
94 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
95 position += length;
96 return NO_ERROR;
97}
98
Dichen Zhang636f5242022-12-07 20:25:44 +000099/* Encode API-0 */
100status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
101 jpegr_transfer_function hdr_tf,
102 jr_compressed_ptr dest,
103 int quality,
104 jr_exif_ptr /* exif */) {
105 if (uncompressed_p010_image == nullptr || dest == nullptr) {
106 return ERROR_JPEGR_INVALID_NULL_PTR;
107 }
108
109 if (quality < 0 || quality > 100) {
110 return ERROR_JPEGR_INVALID_INPUT_TYPE;
111 }
112
113 jpegr_metadata metadata;
114 metadata.version = kJpegrVersion;
115 metadata.transferFunction = hdr_tf;
116 if (hdr_tf == JPEGR_TF_PQ) {
117 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
118 }
119
120 jpegr_uncompressed_struct uncompressed_yuv_420_image;
121 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
122
123 jpegr_uncompressed_struct map;
124 JPEGR_CHECK(generateRecoveryMap(
125 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
126 std::unique_ptr<uint8_t[]> map_data;
127 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
128
129 jpegr_compressed_struct compressed_map;
130 compressed_map.maxLength = map.width * map.height;
131 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
132 compressed_map.data = compressed_map_data.get();
133 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
134
135 JpegEncoder jpeg_encoder;
136 // TODO: determine ICC data based on color gamut information
137 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
138 uncompressed_yuv_420_image.width,
139 uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
140 return ERROR_JPEGR_ENCODE_ERROR;
141 }
142 jpegr_compressed_struct jpeg;
143 jpeg.data = jpeg_encoder.getCompressedImagePtr();
144 jpeg.length = jpeg_encoder.getCompressedImageSize();
145
146 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
147
148 return NO_ERROR;
149}
150
151/* Encode API-1 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000152status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
153 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500154 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400155 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000156 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000157 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000158 if (uncompressed_p010_image == nullptr
159 || uncompressed_yuv_420_image == nullptr
160 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000161 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000162 }
163
Dichen Zhangffa34012022-11-03 23:21:13 +0000164 if (quality < 0 || quality > 100) {
165 return ERROR_JPEGR_INVALID_INPUT_TYPE;
166 }
167
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400168 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
169 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
170 return ERROR_JPEGR_RESOLUTION_MISMATCH;
171 }
172
Nick Deakin6bd90432022-11-20 16:26:37 -0500173 jpegr_metadata metadata;
174 metadata.version = kJpegrVersion;
175 metadata.transferFunction = hdr_tf;
176 if (hdr_tf == JPEGR_TF_PQ) {
177 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
178 }
179
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400180 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000181 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500182 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400183 std::unique_ptr<uint8_t[]> map_data;
184 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
185
186 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000187 compressed_map.maxLength = map.width * map.height;
188 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400189 compressed_map.data = compressed_map_data.get();
190 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
191
192 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500193 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400194 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
195 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000196 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400197 return ERROR_JPEGR_ENCODE_ERROR;
198 }
199 jpegr_compressed_struct jpeg;
200 jpeg.data = jpeg_encoder.getCompressedImagePtr();
201 jpeg.length = jpeg_encoder.getCompressedImageSize();
202
Nick Deakin6bd90432022-11-20 16:26:37 -0500203 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400204
Dichen Zhang6947d532022-10-22 02:16:21 +0000205 return NO_ERROR;
206}
207
Dichen Zhang636f5242022-12-07 20:25:44 +0000208/* Encode API-2 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000209status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
210 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400211 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500212 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000213 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000214 if (uncompressed_p010_image == nullptr
215 || uncompressed_yuv_420_image == nullptr
216 || compressed_jpeg_image == nullptr
217 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000218 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000219 }
220
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400221 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
222 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
223 return ERROR_JPEGR_RESOLUTION_MISMATCH;
224 }
225
Nick Deakin6bd90432022-11-20 16:26:37 -0500226 jpegr_metadata metadata;
227 metadata.version = kJpegrVersion;
228 metadata.transferFunction = hdr_tf;
229 if (hdr_tf == JPEGR_TF_PQ) {
230 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
231 }
232
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400233 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000234 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500235 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400236 std::unique_ptr<uint8_t[]> map_data;
237 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
238
239 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000240 compressed_map.maxLength = map.width * map.height;
241 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400242 compressed_map.data = compressed_map_data.get();
243 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
244
Nick Deakin6bd90432022-11-20 16:26:37 -0500245 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400246
Dichen Zhang6947d532022-10-22 02:16:21 +0000247 return NO_ERROR;
248}
249
Dichen Zhang636f5242022-12-07 20:25:44 +0000250/* Encode API-3 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000251status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400252 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500253 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000254 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000255 if (uncompressed_p010_image == nullptr
256 || compressed_jpeg_image == nullptr
257 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000258 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000259 }
260
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400261 JpegDecoder jpeg_decoder;
262 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
263 return ERROR_JPEGR_DECODE_ERROR;
264 }
265 jpegr_uncompressed_struct uncompressed_yuv_420_image;
266 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
267 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
268 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500269 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400270
271 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
272 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
273 return ERROR_JPEGR_RESOLUTION_MISMATCH;
274 }
275
Nick Deakin6bd90432022-11-20 16:26:37 -0500276 jpegr_metadata metadata;
277 metadata.version = kJpegrVersion;
278 metadata.transferFunction = hdr_tf;
279 if (hdr_tf == JPEGR_TF_PQ) {
280 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
281 }
282
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400283 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000284 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500285 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400286 std::unique_ptr<uint8_t[]> map_data;
287 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
288
289 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000290 compressed_map.maxLength = map.width * map.height;
291 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400292 compressed_map.data = compressed_map_data.get();
293 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
294
Nick Deakin6bd90432022-11-20 16:26:37 -0500295 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400296
Dichen Zhang6947d532022-10-22 02:16:21 +0000297 return NO_ERROR;
298}
299
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000300status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
301 jr_info_ptr jpegr_info) {
302 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
303 return ERROR_JPEGR_INVALID_NULL_PTR;
304 }
305
306 jpegr_compressed_struct primary_image, recovery_map;
307 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
308 &primary_image, &recovery_map));
309
310 JpegDecoder jpeg_decoder;
311 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
312 &jpegr_info->width, &jpegr_info->height,
313 jpegr_info->iccData, jpegr_info->exifData)) {
314 return ERROR_JPEGR_DECODE_ERROR;
315 }
316
317 return NO_ERROR;
318}
319
Dichen Zhang636f5242022-12-07 20:25:44 +0000320/* Decode API */
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400321status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000322 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000323 jr_exif_ptr exif,
324 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000325 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000326 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000327 }
328
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000329 // TODO: fill EXIF data
330 (void) exif;
331
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400332 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500333 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000334 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400335
336 jpegr_uncompressed_struct map;
337 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
338
339 JpegDecoder jpeg_decoder;
340 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
341 return ERROR_JPEGR_DECODE_ERROR;
342 }
343
344 jpegr_uncompressed_struct uncompressed_yuv_420_image;
345 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
346 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
347 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
348
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000349 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
350 jpeg_decoder.getXMPSize(), &metadata)) {
351 return ERROR_JPEGR_DECODE_ERROR;
352 }
353
354 if (request_sdr) {
355 memcpy(dest->data, uncompressed_yuv_420_image.data,
356 uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
357 dest->width = uncompressed_yuv_420_image.width;
358 dest->height = uncompressed_yuv_420_image.height;
359 } else {
360 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
361 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400362
Dichen Zhang6947d532022-10-22 02:16:21 +0000363 return NO_ERROR;
364}
365
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400366status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
367 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700368 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000369 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700370 }
371
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400372 JpegDecoder jpeg_decoder;
373 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
374 compressed_recovery_map->length)) {
375 return ERROR_JPEGR_DECODE_ERROR;
376 }
377
378 dest->data = jpeg_decoder.getDecompressedImagePtr();
379 dest->width = jpeg_decoder.getDecompressedImageWidth();
380 dest->height = jpeg_decoder.getDecompressedImageHeight();
381
Dichen Zhang6947d532022-10-22 02:16:21 +0000382 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700383}
384
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400385status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
386 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700387 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000388 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700389 }
390
Nick Deakin6bd90432022-11-20 16:26:37 -0500391 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400392 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000393 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
394 uncompressed_recovery_map->width,
395 uncompressed_recovery_map->height,
396 kMapCompressQuality,
397 nullptr,
398 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400399 true /* isSingleChannel */)) {
400 return ERROR_JPEGR_ENCODE_ERROR;
401 }
402
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000403 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400404 return ERROR_JPEGR_BUFFER_TOO_SMALL;
405 }
406
407 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
408 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500409 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400410
Dichen Zhang6947d532022-10-22 02:16:21 +0000411 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700412}
413
Dichen Zhang6947d532022-10-22 02:16:21 +0000414status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
415 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500416 jr_metadata_ptr metadata,
417 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700418 if (uncompressed_yuv_420_image == nullptr
419 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500420 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700421 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000422 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700423 }
424
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400425 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
426 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
427 return ERROR_JPEGR_RESOLUTION_MISMATCH;
428 }
429
Nick Deakin6bd90432022-11-20 16:26:37 -0500430 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
431 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
432 return ERROR_JPEGR_INVALID_COLORGAMUT;
433 }
434
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400435 size_t image_width = uncompressed_yuv_420_image->width;
436 size_t image_height = uncompressed_yuv_420_image->height;
437 size_t map_width = image_width / kMapDimensionScaleFactor;
438 size_t map_height = image_height / kMapDimensionScaleFactor;
439
440 dest->width = map_width;
441 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500442 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400443 dest->data = new uint8_t[map_width * map_height];
444 std::unique_ptr<uint8_t[]> map_data;
445 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
446
Nick Deakin6bd90432022-11-20 16:26:37 -0500447 ColorTransformFn hdrInvOetf = nullptr;
448 switch (metadata->transferFunction) {
449 case JPEGR_TF_HLG:
450 hdrInvOetf = hlgInvOetf;
451 break;
452 case JPEGR_TF_PQ:
453 hdrInvOetf = pqInvOetf;
454 break;
455 }
456
457 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
458 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
459
460 ColorCalculationFn luminanceFn = nullptr;
461 switch (uncompressed_yuv_420_image->colorGamut) {
462 case JPEGR_COLORGAMUT_BT709:
463 luminanceFn = srgbLuminance;
464 break;
465 case JPEGR_COLORGAMUT_P3:
466 luminanceFn = p3Luminance;
467 break;
468 case JPEGR_COLORGAMUT_BT2100:
469 luminanceFn = bt2100Luminance;
470 break;
471 case JPEGR_COLORGAMUT_UNSPECIFIED:
472 // Should be impossible to hit after input validation.
473 return ERROR_JPEGR_INVALID_COLORGAMUT;
474 }
475
Nick Deakin594a4ca2022-11-16 20:57:42 -0500476 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500477 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400478 for (size_t y = 0; y < image_height; ++y) {
479 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500480 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
481 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500482 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
483 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
484 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakin594a4ca2022-11-16 20:57:42 -0500485
Nick Deakin6bd90432022-11-20 16:26:37 -0500486 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500487 if (hdr_y_nits > hdr_y_nits_max) {
488 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400489 }
490 }
491 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500492 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400493
Nick Deakin6bd90432022-11-20 16:26:37 -0500494 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
495 if (metadata->transferFunction == JPEGR_TF_PQ) {
496 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
497 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
498 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400499
500 for (size_t y = 0; y < map_height; ++y) {
501 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500502 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
503 kMapDimensionScaleFactor, x, y);
504 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
505 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500506 float sdr_y_nits = luminanceFn(sdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400507
Nick Deakin594a4ca2022-11-16 20:57:42 -0500508 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
509 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500510 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
511 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
512 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400513
514 size_t pixel_idx = x + y * map_width;
515 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500516 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400517 }
518 }
519
520 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000521 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700522}
523
Dichen Zhang6947d532022-10-22 02:16:21 +0000524status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
525 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500526 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000527 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700528 if (uncompressed_yuv_420_image == nullptr
529 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500530 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700531 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000532 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700533 }
534
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400535 size_t width = uncompressed_yuv_420_image->width;
536 size_t height = uncompressed_yuv_420_image->height;
537
538 dest->width = width;
539 dest->height = height;
540 size_t pixel_count = width * height;
541
Nick Deakin6bd90432022-11-20 16:26:37 -0500542 ColorTransformFn hdrOetf = nullptr;
543 switch (metadata->transferFunction) {
544 case JPEGR_TF_HLG:
545 hdrOetf = hlgOetf;
546 break;
547 case JPEGR_TF_PQ:
548 hdrOetf = pqOetf;
549 break;
550 }
551
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400552 for (size_t y = 0; y < height; ++y) {
553 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500554 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
555 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
556 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400557
Nick Deakin6bd90432022-11-20 16:26:37 -0500558 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400559 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500560 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400561
Nick Deakin6bd90432022-11-20 16:26:37 -0500562 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
563 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400564
Nick Deakin6bd90432022-11-20 16:26:37 -0500565 size_t pixel_idx = x + y * width;
566 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400567 }
568 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000569 return NO_ERROR;
570}
571
572status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
573 jr_compressed_ptr primary_image,
574 jr_compressed_ptr recovery_map) {
575 if (compressed_jpegr_image == nullptr) {
576 return ERROR_JPEGR_INVALID_NULL_PTR;
577 }
578
579 MessageHandler msg_handler;
580 std::shared_ptr<DataSegment> seg =
581 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
582 static_cast<const uint8_t*>(compressed_jpegr_image->data),
583 DataSegment::BufferDispositionPolicy::kDontDelete);
584 DataSegmentDataSource data_source(seg);
585 JpegInfoBuilder jpeg_info_builder;
586 jpeg_info_builder.SetImageLimit(2);
587 JpegScanner jpeg_scanner(&msg_handler);
588 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
589 data_source.Reset();
590
591 if (jpeg_scanner.HasError()) {
592 return ERROR_JPEGR_INVALID_INPUT_TYPE;
593 }
594
595 const auto& jpeg_info = jpeg_info_builder.GetInfo();
596 const auto& image_ranges = jpeg_info.GetImageRanges();
597 if (image_ranges.empty()) {
598 return ERROR_JPEGR_INVALID_INPUT_TYPE;
599 }
600
601 if (image_ranges.size() != 2) {
602 // Must be 2 JPEG Images
603 return ERROR_JPEGR_INVALID_INPUT_TYPE;
604 }
605
606 if (primary_image != nullptr) {
607 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
608 image_ranges[0].GetBegin();
609 primary_image->length = image_ranges[0].GetLength();
610 }
611
612 if (recovery_map != nullptr) {
613 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
614 image_ranges[1].GetBegin();
615 recovery_map->length = image_ranges[1].GetLength();
616 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400617
Dichen Zhang6947d532022-10-22 02:16:21 +0000618 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700619}
620
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000621
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400622status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000623 jr_compressed_ptr dest) {
624 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000625 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700626 }
627
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000628 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700629}
630
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400631status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
632 jr_compressed_ptr compressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500633 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400634 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000635 if (compressed_jpeg_image == nullptr
636 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500637 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000638 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000639 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700640 }
641
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000642 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
643 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
644 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +0000645
646 // 2 bytes: APP1 sign (ff e1)
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000647 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
Dichen Zhanga8766262022-11-07 23:48:24 +0000648 // x bytes: length of xmp packet
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000649
650 const int length = 3 + nameSpaceLength + xmp.size();
651 const uint8_t lengthH = ((length >> 8) & 0xff);
652 const uint8_t lengthL = (length & 0xff);
Dichen Zhanga8766262022-11-07 23:48:24 +0000653
654 int pos = 0;
655
656 // JPEG/R structure:
657 // SOI (ff d8)
658 // APP1 (ff e1)
659 // 2 bytes of length (2 + 29 + length of xmp packet)
660 // name space ("http://ns.adobe.com/xap/1.0/\0")
661 // xmp
662 // primary image (without the first two bytes, the SOI sign)
663 // secondary image (the recovery map)
664 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
665 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
666 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
667 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
668 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
669 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000670 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhanga8766262022-11-07 23:48:24 +0000671 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
672 JPEGR_CHECK(Write(dest,
673 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
674 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
675 dest->length = pos;
676
Dichen Zhang6947d532022-10-22 02:16:21 +0000677 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700678}
679
Nick Deakin6bd90432022-11-20 16:26:37 -0500680string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000681 const string kContainerPrefix = "GContainer";
682 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
683 const string kItemPrefix = "Item";
684 const string kRecoveryMap = "RecoveryMap";
685 const string kDirectory = "Directory";
686 const string kImageJpeg = "image/jpeg";
687 const string kItem = "Item";
688 const string kLength = "Length";
689 const string kMime = "Mime";
690 const string kPrimary = "Primary";
691 const string kSemantic = "Semantic";
692 const string kVersion = "Version";
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000693
694 const string kConDir = Name(kContainerPrefix, kDirectory);
695 const string kContainerItem = Name(kContainerPrefix, kItem);
696 const string kItemLength = Name(kItemPrefix, kLength);
697 const string kItemMime = Name(kItemPrefix, kMime);
698 const string kItemSemantic = Name(kItemPrefix, kSemantic);
699
700 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
701 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
702
703 std::stringstream ss;
704 photos_editing_formats::image_io::XmlWriter writer(ss);
705 writer.StartWritingElement("x:xmpmeta");
706 writer.WriteXmlns("x", "adobe:ns:meta/");
707 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
708 writer.StartWritingElement("rdf:RDF");
709 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
710 writer.StartWritingElement("rdf:Description");
711 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin6bd90432022-11-20 16:26:37 -0500712 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
713 writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
714 metadata.rangeScalingFactor);
715 // TODO: determine structure for hdr10 metadata
716 // TODO: write rest of metadata
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000717 writer.StartWritingElements(kConDirSeq);
718 size_t item_depth = writer.StartWritingElements(kLiItem);
719 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
720 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
721 writer.FinishWritingElementsToDepth(item_depth);
722 writer.StartWritingElements(kLiItem);
723 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
724 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
725 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
726 writer.FinishWriting();
727
728 return ss.str();
729}
730
Dichen Zhang636f5242022-12-07 20:25:44 +0000731status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
732 jr_uncompressed_ptr dest) {
733 if (uncompressed_p010_image == nullptr || dest == nullptr) {
734 return ERROR_JPEGR_INVALID_NULL_PTR;
735 }
736
737 dest->width = uncompressed_p010_image->width;
738 dest->height = uncompressed_p010_image->height;
739 unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
740 dest->data = dest_data.get();
741
742 // TODO: Tone map algorighm here.
743
744 return NO_ERROR;
745}
746
Dichen Zhang85b37562022-10-11 11:08:28 -0700747} // namespace android::recoverymap