blob: 222b5f0c9eee2e468a4fce85d9012eb7523c1b11 [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>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000024#include <image_io/jpeg/jpeg_info.h>
25#include <image_io/jpeg/jpeg_scanner.h>
26#include <image_io/jpeg/jpeg_info_builder.h>
27#include <image_io/base/data_segment_data_source.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040028
29#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000030#include <sstream>
31#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000032#include <cmath>
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080033#include <condition_variable>
34#include <deque>
35#include <mutex>
36#include <thread>
37#include <unistd.h>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000038
39using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000040using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070041
42namespace android::recoverymap {
43
Nick Deakinf6bca5a2022-11-04 10:43:43 -040044#define JPEGR_CHECK(x) \
45 { \
46 status_t status = (x); \
47 if ((status) != NO_ERROR) { \
48 return status; \
49 } \
50 }
51
Nick Deakin6bd90432022-11-20 16:26:37 -050052// The current JPEGR version that we encode to
53static const uint32_t kJpegrVersion = 1;
54
Nick Deakinf6bca5a2022-11-04 10:43:43 -040055// Map is quarter res / sixteenth size
56static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000057// JPEG compress quality (0 ~ 100) for recovery map
58static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040059
Nick Deakin6bd90432022-11-20 16:26:37 -050060// TODO: fill in st2086 metadata
61static const st2086_metadata kSt2086Metadata = {
62 {0.0f, 0.0f},
63 {0.0f, 0.0f},
64 {0.0f, 0.0f},
65 {0.0f, 0.0f},
66 0,
67 1.0f,
68};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040069
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080070#define CONFIG_MULTITHREAD 1
71int GetCPUCoreCount() {
72 int cpuCoreCount = 1;
73#if CONFIG_MULTITHREAD
74#if defined(_SC_NPROCESSORS_ONLN)
75 cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
76#else
77 // _SC_NPROC_ONLN must be defined...
78 cpuCoreCount = sysconf(_SC_NPROC_ONLN);
79#endif
80#endif
81 return cpuCoreCount;
82}
83
Dichen Zhang72fd2b12022-11-01 06:11:50 +000084/*
Dichen Zhanga8766262022-11-07 23:48:24 +000085 * Helper function used for writing data to destination.
86 *
87 * @param destination destination of the data to be written.
88 * @param source source of data being written.
89 * @param length length of the data to be written.
90 * @param position cursor in desitination where the data is to be written.
91 * @return status of succeed or error code.
92 */
93status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000094 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000095 return ERROR_JPEGR_BUFFER_TOO_SMALL;
96 }
97
98 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
99 position += length;
100 return NO_ERROR;
101}
102
Dichen Zhangd18bc302022-12-16 20:55:24 +0000103status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
104 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
105 position += length;
106 return NO_ERROR;
107}
108
109// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
110// where the length is represented by this value.
111const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
112// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
113// represented by this value.
114const size_t EXIF_J_R_ENTRY_LENGTH = 12;
115
116/*
117 * Helper function
118 * Add J R entry to existing exif, or create a new one with J R entry if it's null.
119 * EXIF syntax / change:
120 * ori:
121 * FF E1 - APP1
122 * 01 FC - size of APP1 (to be calculated)
123 * -----------------------------------------------------
124 * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
125 * 49 49 2A 00 - TIFF Header
126 * 08 00 00 00 - offset to the IFD (image file directory)
127 * 06 00 - 6 entries
128 * 00 01 - Width Tag
129 * 03 00 - 'Short' type
130 * 01 00 00 00 - one entry
131 * 00 05 00 00 - image with 0x500
132 *--------------------------------------------------------------------------
133 * new:
134 * FF E1 - APP1
135 * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
136 *-----------------------------------------------------
137 * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
138 * 49 49 2A 00 - TIFF Header
139 * 08 00 00 00 - offset to the IFD (image file directory)
140 * 07 00 - +1 entry
141 * 4A 52 Custom ('J''R') Tag
142 * 07 00 - Unknown type
143 * 01 00 00 00 - one element
144 * 00 00 00 00 - empty data
145 * 00 01 - Width Tag
146 * 03 00 - 'Short' type
147 * 01 00 00 00 - one entry
148 * 00 05 00 00 - image with 0x500
149 */
150status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
151 if (exif == nullptr || exif->data == nullptr) {
152 uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
153 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
154 0x49, 0x49, 0x2A, 0x00,
155 0x08, 0x00, 0x00, 0x00,
156 0x01, 0x00,
157 0x4A, 0x52,
158 0x07, 0x00,
159 0x01, 0x00, 0x00, 0x00,
160 0x00, 0x00, 0x00, 0x00};
161 int pos = 0;
162 Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
163 return NO_ERROR;
164 }
165
166 int num_entry = 0;
167 uint8_t num_entry_low = 0;
168 uint8_t num_entry_high = 0;
169 bool use_big_endian = false;
170 if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
171 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
172 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
173 } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
174 use_big_endian = true;
175 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
176 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
177 } else {
178 return ERROR_JPEGR_METADATA_ERROR;
179 }
180 num_entry = (num_entry_high << 8) | num_entry_low;
181 num_entry += 1;
182 num_entry_low = num_entry & 0xff;
183 num_entry_high = (num_entry << 8) & 0xff;
184
185 int pos = 0;
186 Write(dest, (uint8_t*)exif->data, 14, pos);
187
188 if (use_big_endian) {
189 Write(dest, &num_entry_high, 1, pos);
190 Write(dest, &num_entry_low, 1, pos);
191 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
192 0x4A, 0x52,
193 0x07, 0x00,
194 0x01, 0x00, 0x00, 0x00,
195 0x00, 0x00, 0x00, 0x00};
196 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
197 } else {
198 Write(dest, &num_entry_low, 1, pos);
199 Write(dest, &num_entry_high, 1, pos);
200 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
201 0x4A, 0x52,
202 0x00, 0x07,
203 0x00, 0x00, 0x00, 0x01,
204 0x00, 0x00, 0x00, 0x00};
205 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
206 }
207
208 Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
209
210 return NO_ERROR;
211}
212
Dichen Zhang63d92512023-01-04 12:01:16 -0800213/*
214 * Helper function copies the JPEG image from without EXIF.
215 *
216 * @param dest destination of the data to be written.
217 * @param source source of data being written.
218 * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
219 * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>).
220 * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
221 */
222void copyJpegWithoutExif(jr_compressed_ptr dest,
223 jr_compressed_ptr source,
224 size_t exif_pos,
225 size_t exif_size) {
226 memcpy(dest, source, sizeof(jpegr_compressed_struct));
227
228 const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign
229 dest->length = source->length - exif_size - exif_offset;
230 dest->data = malloc(dest->length);
231
232 memcpy(dest->data, source->data, exif_pos - exif_offset);
233 memcpy((uint8_t*)dest->data + exif_pos - exif_offset,
234 (uint8_t*)source->data + exif_pos + exif_size,
235 source->length - exif_pos - exif_size);
236}
237
Dichen Zhang636f5242022-12-07 20:25:44 +0000238/* Encode API-0 */
239status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
240 jpegr_transfer_function hdr_tf,
241 jr_compressed_ptr dest,
242 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000243 jr_exif_ptr exif) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000244 if (uncompressed_p010_image == nullptr || dest == nullptr) {
245 return ERROR_JPEGR_INVALID_NULL_PTR;
246 }
247
248 if (quality < 0 || quality > 100) {
249 return ERROR_JPEGR_INVALID_INPUT_TYPE;
250 }
251
252 jpegr_metadata metadata;
253 metadata.version = kJpegrVersion;
254 metadata.transferFunction = hdr_tf;
255 if (hdr_tf == JPEGR_TF_PQ) {
256 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
257 }
258
259 jpegr_uncompressed_struct uncompressed_yuv_420_image;
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800260 unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
261 uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
262 uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
Dichen Zhang636f5242022-12-07 20:25:44 +0000263 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
264
265 jpegr_uncompressed_struct map;
266 JPEGR_CHECK(generateRecoveryMap(
267 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
268 std::unique_ptr<uint8_t[]> map_data;
269 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
270
271 jpegr_compressed_struct compressed_map;
272 compressed_map.maxLength = map.width * map.height;
273 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
274 compressed_map.data = compressed_map_data.get();
275 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
276
277 JpegEncoder jpeg_encoder;
278 // TODO: determine ICC data based on color gamut information
279 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
280 uncompressed_yuv_420_image.width,
281 uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
282 return ERROR_JPEGR_ENCODE_ERROR;
283 }
284 jpegr_compressed_struct jpeg;
285 jpeg.data = jpeg_encoder.getCompressedImagePtr();
286 jpeg.length = jpeg_encoder.getCompressedImageSize();
287
Dichen Zhangd18bc302022-12-16 20:55:24 +0000288 jpegr_exif_struct new_exif;
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800289 if (exif == nullptr || exif->data == nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +0000290 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
291 } else {
292 new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
293 }
294 new_exif.data = new uint8_t[new_exif.length];
295 std::unique_ptr<uint8_t[]> new_exif_data;
296 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
297 JPEGR_CHECK(updateExif(exif, &new_exif));
298
299 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000300
301 return NO_ERROR;
302}
303
304/* Encode API-1 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000305status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
306 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500307 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400308 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000309 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000310 jr_exif_ptr exif) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000311 if (uncompressed_p010_image == nullptr
312 || uncompressed_yuv_420_image == nullptr
313 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000314 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000315 }
316
Dichen Zhangffa34012022-11-03 23:21:13 +0000317 if (quality < 0 || quality > 100) {
318 return ERROR_JPEGR_INVALID_INPUT_TYPE;
319 }
320
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400321 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
322 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
323 return ERROR_JPEGR_RESOLUTION_MISMATCH;
324 }
325
Nick Deakin6bd90432022-11-20 16:26:37 -0500326 jpegr_metadata metadata;
327 metadata.version = kJpegrVersion;
328 metadata.transferFunction = hdr_tf;
329 if (hdr_tf == JPEGR_TF_PQ) {
330 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
331 }
332
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400333 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000334 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500335 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400336 std::unique_ptr<uint8_t[]> map_data;
337 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
338
339 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000340 compressed_map.maxLength = map.width * map.height;
341 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400342 compressed_map.data = compressed_map_data.get();
343 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
344
345 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500346 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400347 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
348 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000349 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400350 return ERROR_JPEGR_ENCODE_ERROR;
351 }
352 jpegr_compressed_struct jpeg;
353 jpeg.data = jpeg_encoder.getCompressedImagePtr();
354 jpeg.length = jpeg_encoder.getCompressedImageSize();
355
Dichen Zhangd18bc302022-12-16 20:55:24 +0000356 jpegr_exif_struct new_exif;
357 if (exif == nullptr || exif->data == nullptr) {
358 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
359 } else {
360 new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
361 }
362
363 new_exif.data = new uint8_t[new_exif.length];
364 std::unique_ptr<uint8_t[]> new_exif_data;
365 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
366 JPEGR_CHECK(updateExif(exif, &new_exif));
367
368 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400369
Dichen Zhang6947d532022-10-22 02:16:21 +0000370 return NO_ERROR;
371}
372
Dichen Zhang636f5242022-12-07 20:25:44 +0000373/* Encode API-2 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000374status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
375 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400376 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500377 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000378 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000379 if (uncompressed_p010_image == nullptr
380 || uncompressed_yuv_420_image == nullptr
381 || compressed_jpeg_image == nullptr
382 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000383 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000384 }
385
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400386 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
387 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
388 return ERROR_JPEGR_RESOLUTION_MISMATCH;
389 }
390
Nick Deakin6bd90432022-11-20 16:26:37 -0500391 jpegr_metadata metadata;
392 metadata.version = kJpegrVersion;
393 metadata.transferFunction = hdr_tf;
394 if (hdr_tf == JPEGR_TF_PQ) {
395 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
396 }
397
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400398 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000399 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500400 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400401 std::unique_ptr<uint8_t[]> map_data;
402 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
403
404 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000405 compressed_map.maxLength = map.width * map.height;
406 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400407 compressed_map.data = compressed_map_data.get();
408 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
409
Dichen Zhangd18bc302022-12-16 20:55:24 +0000410 // Extract EXIF from JPEG without decoding.
411 JpegDecoder jpeg_decoder;
412 if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
413 return ERROR_JPEGR_DECODE_ERROR;
414 }
415
Dichen Zhang63d92512023-01-04 12:01:16 -0800416 // Update exif.
Dichen Zhangd18bc302022-12-16 20:55:24 +0000417 jpegr_exif_struct exif;
418 exif.data = nullptr;
419 exif.length = 0;
Dichen Zhang63d92512023-01-04 12:01:16 -0800420 jpegr_compressed_struct new_jpeg_image;
421 new_jpeg_image.data = nullptr;
422 new_jpeg_image.length = 0;
Dichen Zhangd18bc302022-12-16 20:55:24 +0000423 if (jpeg_decoder.getEXIFPos() != 0) {
Dichen Zhang63d92512023-01-04 12:01:16 -0800424 copyJpegWithoutExif(&new_jpeg_image,
425 compressed_jpeg_image,
426 jpeg_decoder.getEXIFPos(),
427 jpeg_decoder.getEXIFSize());
Dichen Zhangd18bc302022-12-16 20:55:24 +0000428 exif.data = jpeg_decoder.getEXIFPtr();
429 exif.length = jpeg_decoder.getEXIFSize();
430 }
431
432 jpegr_exif_struct new_exif;
433 if (exif.data == nullptr) {
434 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
435 } else {
436 new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
437 }
438
439 new_exif.data = new uint8_t[new_exif.length];
440 std::unique_ptr<uint8_t[]> new_exif_data;
441 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
442 JPEGR_CHECK(updateExif(&exif, &new_exif));
443
444 JPEGR_CHECK(appendRecoveryMap(
Dichen Zhang63d92512023-01-04 12:01:16 -0800445 new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
446 &compressed_map, &new_exif, &metadata, dest));
447
448 if (new_jpeg_image.data != nullptr) {
449 free(new_jpeg_image.data);
450 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400451
Dichen Zhang6947d532022-10-22 02:16:21 +0000452 return NO_ERROR;
453}
454
Dichen Zhang636f5242022-12-07 20:25:44 +0000455/* Encode API-3 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000456status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400457 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500458 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000459 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000460 if (uncompressed_p010_image == nullptr
461 || compressed_jpeg_image == nullptr
462 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000463 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000464 }
465
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400466 JpegDecoder jpeg_decoder;
467 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
468 return ERROR_JPEGR_DECODE_ERROR;
469 }
470 jpegr_uncompressed_struct uncompressed_yuv_420_image;
471 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
472 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
473 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500474 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400475
Dichen Zhang63d92512023-01-04 12:01:16 -0800476 // Update exif.
Dichen Zhangd18bc302022-12-16 20:55:24 +0000477 jpegr_exif_struct exif;
478 exif.data = nullptr;
479 exif.length = 0;
Dichen Zhang63d92512023-01-04 12:01:16 -0800480 jpegr_compressed_struct new_jpeg_image;
481 new_jpeg_image.data = nullptr;
482 new_jpeg_image.length = 0;
Dichen Zhangd18bc302022-12-16 20:55:24 +0000483 if (jpeg_decoder.getEXIFPos() != 0) {
Dichen Zhang63d92512023-01-04 12:01:16 -0800484 copyJpegWithoutExif(&new_jpeg_image,
485 compressed_jpeg_image,
486 jpeg_decoder.getEXIFPos(),
487 jpeg_decoder.getEXIFSize());
Dichen Zhangd18bc302022-12-16 20:55:24 +0000488 exif.data = jpeg_decoder.getEXIFPtr();
489 exif.length = jpeg_decoder.getEXIFSize();
490 }
491
492 jpegr_exif_struct new_exif;
493 if (exif.data == nullptr) {
494 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
495 } else {
496 new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
497 }
498 new_exif.data = new uint8_t[new_exif.length];
499 std::unique_ptr<uint8_t[]> new_exif_data;
500 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
501 JPEGR_CHECK(updateExif(&exif, &new_exif));
502
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400503 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
504 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
505 return ERROR_JPEGR_RESOLUTION_MISMATCH;
506 }
507
Nick Deakin6bd90432022-11-20 16:26:37 -0500508 jpegr_metadata metadata;
509 metadata.version = kJpegrVersion;
510 metadata.transferFunction = hdr_tf;
511 if (hdr_tf == JPEGR_TF_PQ) {
512 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
513 }
514
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400515 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000516 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500517 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400518 std::unique_ptr<uint8_t[]> map_data;
519 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
520
521 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000522 compressed_map.maxLength = map.width * map.height;
523 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400524 compressed_map.data = compressed_map_data.get();
525 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
526
Dichen Zhangd18bc302022-12-16 20:55:24 +0000527 JPEGR_CHECK(appendRecoveryMap(
Dichen Zhang63d92512023-01-04 12:01:16 -0800528 new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
529 &compressed_map, &new_exif, &metadata, dest));
530
531 if (new_jpeg_image.data != nullptr) {
532 free(new_jpeg_image.data);
533 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400534
Dichen Zhang6947d532022-10-22 02:16:21 +0000535 return NO_ERROR;
536}
537
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000538status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
539 jr_info_ptr jpegr_info) {
540 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
541 return ERROR_JPEGR_INVALID_NULL_PTR;
542 }
543
544 jpegr_compressed_struct primary_image, recovery_map;
545 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
546 &primary_image, &recovery_map));
547
548 JpegDecoder jpeg_decoder;
549 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
550 &jpegr_info->width, &jpegr_info->height,
551 jpegr_info->iccData, jpegr_info->exifData)) {
552 return ERROR_JPEGR_DECODE_ERROR;
553 }
554
555 return NO_ERROR;
556}
557
Dichen Zhang636f5242022-12-07 20:25:44 +0000558/* Decode API */
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400559status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000560 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000561 jr_exif_ptr exif,
562 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000563 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000564 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000565 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000566 // TODO: fill EXIF data
567 (void) exif;
568
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000569 if (request_sdr) {
570 JpegDecoder jpeg_decoder;
571 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
572 true)) {
573 return ERROR_JPEGR_DECODE_ERROR;
574 }
575 jpegr_uncompressed_struct uncompressed_rgba_image;
576 uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
577 uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
578 uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
579 memcpy(dest->data, uncompressed_rgba_image.data,
580 uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
581 dest->width = uncompressed_rgba_image.width;
582 dest->height = uncompressed_rgba_image.height;
583 return NO_ERROR;
584 }
585
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400586 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500587 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000588 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400589
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400590 JpegDecoder jpeg_decoder;
591 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
592 return ERROR_JPEGR_DECODE_ERROR;
593 }
594
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000595 JpegDecoder recovery_map_decoder;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000596 if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000597 return ERROR_JPEGR_DECODE_ERROR;
598 }
599
600 jpegr_uncompressed_struct map;
601 map.data = recovery_map_decoder.getDecompressedImagePtr();
602 map.width = recovery_map_decoder.getDecompressedImageWidth();
603 map.height = recovery_map_decoder.getDecompressedImageHeight();
604
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400605 jpegr_uncompressed_struct uncompressed_yuv_420_image;
606 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
607 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
608 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
609
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000610 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000611 jpeg_decoder.getXMPSize(), &metadata)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000612 return ERROR_JPEGR_DECODE_ERROR;
613 }
614
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000615 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000616 return NO_ERROR;
617}
618
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400619status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
620 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700621 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000622 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700623 }
624
Nick Deakin6bd90432022-11-20 16:26:37 -0500625 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400626 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000627 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
628 uncompressed_recovery_map->width,
629 uncompressed_recovery_map->height,
630 kMapCompressQuality,
631 nullptr,
632 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400633 true /* isSingleChannel */)) {
634 return ERROR_JPEGR_ENCODE_ERROR;
635 }
636
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000637 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400638 return ERROR_JPEGR_BUFFER_TOO_SMALL;
639 }
640
641 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
642 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500643 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400644
Dichen Zhang6947d532022-10-22 02:16:21 +0000645 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700646}
647
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800648const int kJobSzInRows = 16;
649static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
650 "align job size to kMapDimensionScaleFactor");
651
652class JobQueue {
653 public:
654 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
655 void enqueueJob(size_t rowStart, size_t rowEnd);
656 void markQueueForEnd();
657 void reset();
658
659 private:
660 bool mQueuedAllJobs = false;
661 std::deque<std::tuple<size_t, size_t>> mJobs;
662 std::mutex mMutex;
663 std::condition_variable mCv;
664};
665
666bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
667 std::unique_lock<std::mutex> lock{mMutex};
668 while (true) {
669 if (mJobs.empty()) {
670 if (mQueuedAllJobs) {
671 return false;
672 } else {
673 mCv.wait(lock);
674 }
675 } else {
676 auto it = mJobs.begin();
677 rowStart = std::get<0>(*it);
678 rowEnd = std::get<1>(*it);
679 mJobs.erase(it);
680 return true;
681 }
682 }
683 return false;
684}
685
686void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
687 std::unique_lock<std::mutex> lock{mMutex};
688 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
689 lock.unlock();
690 mCv.notify_one();
691}
692
693void JobQueue::markQueueForEnd() {
694 std::unique_lock<std::mutex> lock{mMutex};
695 mQueuedAllJobs = true;
696}
697
698void JobQueue::reset() {
699 std::unique_lock<std::mutex> lock{mMutex};
700 mJobs.clear();
701 mQueuedAllJobs = false;
702}
703
Dichen Zhang6947d532022-10-22 02:16:21 +0000704status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
705 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500706 jr_metadata_ptr metadata,
707 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700708 if (uncompressed_yuv_420_image == nullptr
709 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500710 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700711 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000712 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700713 }
714
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400715 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
716 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
717 return ERROR_JPEGR_RESOLUTION_MISMATCH;
718 }
719
Nick Deakin6bd90432022-11-20 16:26:37 -0500720 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
721 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
722 return ERROR_JPEGR_INVALID_COLORGAMUT;
723 }
724
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400725 size_t image_width = uncompressed_yuv_420_image->width;
726 size_t image_height = uncompressed_yuv_420_image->height;
727 size_t map_width = image_width / kMapDimensionScaleFactor;
728 size_t map_height = image_height / kMapDimensionScaleFactor;
729
730 dest->width = map_width;
731 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500732 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400733 dest->data = new uint8_t[map_width * map_height];
734 std::unique_ptr<uint8_t[]> map_data;
735 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
736
Nick Deakin6bd90432022-11-20 16:26:37 -0500737 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500738 float hdr_white_nits = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500739 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000740 case JPEGR_TF_LINEAR:
741 hdrInvOetf = identityConversion;
742 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500743 case JPEGR_TF_HLG:
744 hdrInvOetf = hlgInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500745 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500746 break;
747 case JPEGR_TF_PQ:
748 hdrInvOetf = pqInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500749 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500750 break;
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000751 case JPEGR_TF_UNSPECIFIED:
752 // Should be impossible to hit after input validation.
753 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500754 }
755
756 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
757 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
758
759 ColorCalculationFn luminanceFn = nullptr;
760 switch (uncompressed_yuv_420_image->colorGamut) {
761 case JPEGR_COLORGAMUT_BT709:
762 luminanceFn = srgbLuminance;
763 break;
764 case JPEGR_COLORGAMUT_P3:
765 luminanceFn = p3Luminance;
766 break;
767 case JPEGR_COLORGAMUT_BT2100:
768 luminanceFn = bt2100Luminance;
769 break;
770 case JPEGR_COLORGAMUT_UNSPECIFIED:
771 // Should be impossible to hit after input validation.
772 return ERROR_JPEGR_INVALID_COLORGAMUT;
773 }
774
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800775 std::mutex mutex;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500776 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500777 double hdr_y_nits_avg = 0.0f;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800778 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
779 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
780 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500781
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800782 std::function<void()> computeMetadata = [uncompressed_p010_image, hdrInvOetf,
783 hdrGamutConversionFn, luminanceFn, hdr_white_nits,
784 threads, &mutex, &hdr_y_nits_avg,
785 &hdr_y_nits_max, &jobQueue]() -> void {
786 size_t rowStart, rowEnd;
787 float hdr_y_nits_max_th = 0.0f;
788 double hdr_y_nits_avg_th = 0.0f;
789 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
790 for (size_t y = rowStart; y < rowEnd; ++y) {
791 for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
792 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
793 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
794 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
795 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
796 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
797
798 hdr_y_nits_avg_th += hdr_y_nits;
799 if (hdr_y_nits > hdr_y_nits_max_th) {
800 hdr_y_nits_max_th = hdr_y_nits;
801 }
802 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400803 }
804 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800805 std::unique_lock<std::mutex> lock{mutex};
806 hdr_y_nits_avg += hdr_y_nits_avg_th;
807 hdr_y_nits_max = std::max(hdr_y_nits_max, hdr_y_nits_max_th);
808 };
809
810 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
811 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
812 luminanceFn, hdr_white_nits, &jobQueue]() -> void {
813 size_t rowStart, rowEnd;
814 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
815 for (size_t y = rowStart; y < rowEnd; ++y) {
816 for (size_t x = 0; x < dest->width; ++x) {
817 Color sdr_yuv_gamma =
818 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
819 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
820 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
821 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
822
823 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
824 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
825 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
826 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
827 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
828
829 size_t pixel_idx = x + y * dest->width;
830 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
831 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
832 }
833 }
834 }
835 };
836
837 std::vector<std::thread> workers;
838 for (int th = 0; th < threads - 1; th++) {
839 workers.push_back(std::thread(computeMetadata));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400840 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800841
842 // compute metadata
843 for (size_t rowStart = 0; rowStart < image_height;) {
844 size_t rowEnd = std::min(rowStart + rowStep, image_height);
845 jobQueue.enqueueJob(rowStart, rowEnd);
846 rowStart = rowEnd;
847 }
848 jobQueue.markQueueForEnd();
849 computeMetadata();
850 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
851 workers.clear();
Nick Deakin6bd90432022-11-20 16:26:37 -0500852 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400853
Nick Deakin6bd90432022-11-20 16:26:37 -0500854 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
855 if (metadata->transferFunction == JPEGR_TF_PQ) {
856 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
857 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
858 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400859
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800860 // generate map
861 jobQueue.reset();
862 for (int th = 0; th < threads - 1; th++) {
863 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400864 }
865
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800866 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
867 for (size_t rowStart = 0; rowStart < map_height;) {
868 size_t rowEnd = std::min(rowStart + rowStep, map_height);
869 jobQueue.enqueueJob(rowStart, rowEnd);
870 rowStart = rowEnd;
871 }
872 jobQueue.markQueueForEnd();
873 generateMap();
874 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
875
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400876 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000877 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700878}
879
Dichen Zhang6947d532022-10-22 02:16:21 +0000880status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
881 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500882 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000883 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700884 if (uncompressed_yuv_420_image == nullptr
885 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500886 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700887 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000888 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700889 }
890
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800891 dest->width = uncompressed_yuv_420_image->width;
892 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530893 ShepardsIDW idwTable(kMapDimensionScaleFactor);
894
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800895 JobQueue jobQueue;
896 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
897 metadata, dest, &jobQueue, &idwTable]() -> void {
898 const float hdr_ratio = metadata->rangeScalingFactor;
899 size_t width = uncompressed_yuv_420_image->width;
900 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400901
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800902 ColorTransformFn hdrOetf = nullptr;
903 switch (metadata->transferFunction) {
904 case JPEGR_TF_LINEAR:
905 hdrOetf = identityConversion;
906 break;
907 case JPEGR_TF_HLG:
908 hdrOetf = hlgOetf;
909 break;
910 case JPEGR_TF_PQ:
911 hdrOetf = pqOetf;
912 break;
913 case JPEGR_TF_UNSPECIFIED:
914 // Should be impossible to hit after input validation.
915 hdrOetf = identityConversion;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400916 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800917
918 size_t rowStart, rowEnd;
919 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
920 for (size_t y = rowStart; y < rowEnd; ++y) {
921 for (size_t x = 0; x < width; ++x) {
922 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
923 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
924 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
925
926 float recovery;
927 // TODO: determine map scaling factor based on actual map dims
928 size_t map_scale_factor = kMapDimensionScaleFactor;
929 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
930 // Currently map_scale_factor is of type size_t, but it could be changed to a float
931 // later.
932 if (map_scale_factor != floorf(map_scale_factor)) {
933 recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
934 } else {
935 recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y,
936 idwTable);
937 }
938 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, hdr_ratio);
939
940 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
941 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
942
943 size_t pixel_idx = x + y * width;
944 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
945 }
946 }
947 }
948 };
949
950 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
951 std::vector<std::thread> workers;
952 for (int th = 0; th < threads - 1; th++) {
953 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400954 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800955 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
956 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
957 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
958 jobQueue.enqueueJob(rowStart, rowEnd);
959 rowStart = rowEnd;
960 }
961 jobQueue.markQueueForEnd();
962 applyRecMap();
963 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000964 return NO_ERROR;
965}
966
967status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
968 jr_compressed_ptr primary_image,
969 jr_compressed_ptr recovery_map) {
970 if (compressed_jpegr_image == nullptr) {
971 return ERROR_JPEGR_INVALID_NULL_PTR;
972 }
973
974 MessageHandler msg_handler;
975 std::shared_ptr<DataSegment> seg =
976 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
977 static_cast<const uint8_t*>(compressed_jpegr_image->data),
978 DataSegment::BufferDispositionPolicy::kDontDelete);
979 DataSegmentDataSource data_source(seg);
980 JpegInfoBuilder jpeg_info_builder;
981 jpeg_info_builder.SetImageLimit(2);
982 JpegScanner jpeg_scanner(&msg_handler);
983 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
984 data_source.Reset();
985
986 if (jpeg_scanner.HasError()) {
987 return ERROR_JPEGR_INVALID_INPUT_TYPE;
988 }
989
990 const auto& jpeg_info = jpeg_info_builder.GetInfo();
991 const auto& image_ranges = jpeg_info.GetImageRanges();
992 if (image_ranges.empty()) {
993 return ERROR_JPEGR_INVALID_INPUT_TYPE;
994 }
995
996 if (image_ranges.size() != 2) {
997 // Must be 2 JPEG Images
998 return ERROR_JPEGR_INVALID_INPUT_TYPE;
999 }
1000
1001 if (primary_image != nullptr) {
1002 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
1003 image_ranges[0].GetBegin();
1004 primary_image->length = image_ranges[0].GetLength();
1005 }
1006
1007 if (recovery_map != nullptr) {
1008 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
1009 image_ranges[1].GetBegin();
1010 recovery_map->length = image_ranges[1].GetLength();
1011 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001012
Dichen Zhang6947d532022-10-22 02:16:21 +00001013 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -07001014}
1015
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001016
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001017status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001018 jr_compressed_ptr dest) {
1019 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001020 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001021 }
1022
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001023 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -07001024}
1025
Dichen Zhangd18bc302022-12-16 20:55:24 +00001026// JPEG/R structure:
1027// SOI (ff d8)
1028// APP1 (ff e1)
1029// 2 bytes of length (2 + length of exif package)
1030// EXIF package (this includes the first two bytes representing the package length)
1031// APP1 (ff e1)
1032// 2 bytes of length (2 + 29 + length of xmp package)
1033// name space ("http://ns.adobe.com/xap/1.0/\0")
1034// xmp
1035// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
1036// secondary image (the recovery map)
1037//
1038// Metadata versions we are using:
1039// ECMA TR-98 for JFIF marker
1040// Exif 2.2 spec for EXIF marker
1041// Adobe XMP spec part 3 for XMP marker
1042// ICC v4.3 spec for ICC
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001043status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
1044 jr_compressed_ptr compressed_recovery_map,
Dichen Zhangd18bc302022-12-16 20:55:24 +00001045 jr_exif_ptr exif,
Nick Deakin6bd90432022-11-20 16:26:37 -05001046 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001047 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +00001048 if (compressed_jpeg_image == nullptr
1049 || compressed_recovery_map == nullptr
Dichen Zhangd18bc302022-12-16 20:55:24 +00001050 || exif == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -05001051 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +00001052 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001053 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001054 }
1055
Dichen Zhanga8766262022-11-07 23:48:24 +00001056 int pos = 0;
1057
Dichen Zhangd18bc302022-12-16 20:55:24 +00001058 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001059 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1060 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001061
1062 // Write EXIF
1063 {
1064 const int length = 2 + exif->length;
1065 const uint8_t lengthH = ((length >> 8) & 0xff);
1066 const uint8_t lengthL = (length & 0xff);
1067 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1068 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1069 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1070 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1071 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1072 }
1073
1074 // Prepare and write XMP
1075 {
1076 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
1077 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
1078 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
1079 // 2 bytes: representing the length of the package
1080 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
1081 // x bytes: length of xmp packet
Dichen Zhang25df9c82023-01-03 17:04:10 -08001082 const int length = 2 + nameSpaceLength + xmp.size();
Dichen Zhangd18bc302022-12-16 20:55:24 +00001083 const uint8_t lengthH = ((length >> 8) & 0xff);
1084 const uint8_t lengthL = (length & 0xff);
1085 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1086 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1087 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1088 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1089 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1090 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
1091 }
1092
1093 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +00001094 JPEGR_CHECK(Write(dest,
1095 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001096
1097 // Write secondary image
Dichen Zhanga8766262022-11-07 23:48:24 +00001098 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001099
1100 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001101 dest->length = pos;
1102
Dichen Zhangd18bc302022-12-16 20:55:24 +00001103 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001104 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001105}
1106
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001107status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
Dichen Zhang636f5242022-12-07 20:25:44 +00001108 jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001109 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001110 return ERROR_JPEGR_INVALID_NULL_PTR;
1111 }
1112
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001113 dest->width = src->width;
1114 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +00001115
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001116 size_t pixel_count = src->width * src->height;
1117 for (size_t y = 0; y < src->height; ++y) {
1118 for (size_t x = 0; x < src->width; ++x) {
1119 size_t pixel_y_idx = x + y * src->width;
1120 size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
1121
1122 uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
1123 >> 6;
1124 uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
1125 >> 6;
1126 uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
1127 >> 6;
1128
1129 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
1130 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
1131 uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
1132
1133 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1134 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1135 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1136 }
1137 }
1138
1139 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001140
1141 return NO_ERROR;
1142}
1143
Dichen Zhang85b37562022-10-11 11:08:28 -07001144} // namespace android::recoverymap