blob: 6b46d4001ce4db2273b3b1749ac28823bf9a71b4 [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>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000033
34using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000035using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070036
37namespace android::recoverymap {
38
Nick Deakinf6bca5a2022-11-04 10:43:43 -040039#define JPEGR_CHECK(x) \
40 { \
41 status_t status = (x); \
42 if ((status) != NO_ERROR) { \
43 return status; \
44 } \
45 }
46
Nick Deakin6bd90432022-11-20 16:26:37 -050047// The current JPEGR version that we encode to
48static const uint32_t kJpegrVersion = 1;
49
Nick Deakinf6bca5a2022-11-04 10:43:43 -040050// Map is quarter res / sixteenth size
51static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000052// JPEG compress quality (0 ~ 100) for recovery map
53static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040054
Nick Deakin6bd90432022-11-20 16:26:37 -050055// TODO: fill in st2086 metadata
56static const st2086_metadata kSt2086Metadata = {
57 {0.0f, 0.0f},
58 {0.0f, 0.0f},
59 {0.0f, 0.0f},
60 {0.0f, 0.0f},
61 0,
62 1.0f,
63};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040064
Dichen Zhang72fd2b12022-11-01 06:11:50 +000065/*
Dichen Zhanga8766262022-11-07 23:48:24 +000066 * Helper function used for writing data to destination.
67 *
68 * @param destination destination of the data to be written.
69 * @param source source of data being written.
70 * @param length length of the data to be written.
71 * @param position cursor in desitination where the data is to be written.
72 * @return status of succeed or error code.
73 */
74status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000075 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000076 return ERROR_JPEGR_BUFFER_TOO_SMALL;
77 }
78
79 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
80 position += length;
81 return NO_ERROR;
82}
83
Dichen Zhangd18bc302022-12-16 20:55:24 +000084status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
85 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
86 position += length;
87 return NO_ERROR;
88}
89
90// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
91// where the length is represented by this value.
92const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
93// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
94// represented by this value.
95const size_t EXIF_J_R_ENTRY_LENGTH = 12;
96
97/*
98 * Helper function
99 * Add J R entry to existing exif, or create a new one with J R entry if it's null.
100 * EXIF syntax / change:
101 * ori:
102 * FF E1 - APP1
103 * 01 FC - size of APP1 (to be calculated)
104 * -----------------------------------------------------
105 * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
106 * 49 49 2A 00 - TIFF Header
107 * 08 00 00 00 - offset to the IFD (image file directory)
108 * 06 00 - 6 entries
109 * 00 01 - Width Tag
110 * 03 00 - 'Short' type
111 * 01 00 00 00 - one entry
112 * 00 05 00 00 - image with 0x500
113 *--------------------------------------------------------------------------
114 * new:
115 * FF E1 - APP1
116 * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
117 *-----------------------------------------------------
118 * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
119 * 49 49 2A 00 - TIFF Header
120 * 08 00 00 00 - offset to the IFD (image file directory)
121 * 07 00 - +1 entry
122 * 4A 52 Custom ('J''R') Tag
123 * 07 00 - Unknown type
124 * 01 00 00 00 - one element
125 * 00 00 00 00 - empty data
126 * 00 01 - Width Tag
127 * 03 00 - 'Short' type
128 * 01 00 00 00 - one entry
129 * 00 05 00 00 - image with 0x500
130 */
131status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
132 if (exif == nullptr || exif->data == nullptr) {
133 uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
134 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
135 0x49, 0x49, 0x2A, 0x00,
136 0x08, 0x00, 0x00, 0x00,
137 0x01, 0x00,
138 0x4A, 0x52,
139 0x07, 0x00,
140 0x01, 0x00, 0x00, 0x00,
141 0x00, 0x00, 0x00, 0x00};
142 int pos = 0;
143 Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
144 return NO_ERROR;
145 }
146
147 int num_entry = 0;
148 uint8_t num_entry_low = 0;
149 uint8_t num_entry_high = 0;
150 bool use_big_endian = false;
151 if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
152 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
153 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
154 } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
155 use_big_endian = true;
156 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
157 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
158 } else {
159 return ERROR_JPEGR_METADATA_ERROR;
160 }
161 num_entry = (num_entry_high << 8) | num_entry_low;
162 num_entry += 1;
163 num_entry_low = num_entry & 0xff;
164 num_entry_high = (num_entry << 8) & 0xff;
165
166 int pos = 0;
167 Write(dest, (uint8_t*)exif->data, 14, pos);
168
169 if (use_big_endian) {
170 Write(dest, &num_entry_high, 1, pos);
171 Write(dest, &num_entry_low, 1, pos);
172 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
173 0x4A, 0x52,
174 0x07, 0x00,
175 0x01, 0x00, 0x00, 0x00,
176 0x00, 0x00, 0x00, 0x00};
177 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
178 } else {
179 Write(dest, &num_entry_low, 1, pos);
180 Write(dest, &num_entry_high, 1, pos);
181 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
182 0x4A, 0x52,
183 0x00, 0x07,
184 0x00, 0x00, 0x00, 0x01,
185 0x00, 0x00, 0x00, 0x00};
186 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
187 }
188
189 Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
190
191 return NO_ERROR;
192}
193
Dichen Zhang63d92512023-01-04 12:01:16 -0800194/*
195 * Helper function copies the JPEG image from without EXIF.
196 *
197 * @param dest destination of the data to be written.
198 * @param source source of data being written.
199 * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
200 * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>).
201 * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
202 */
203void copyJpegWithoutExif(jr_compressed_ptr dest,
204 jr_compressed_ptr source,
205 size_t exif_pos,
206 size_t exif_size) {
207 memcpy(dest, source, sizeof(jpegr_compressed_struct));
208
209 const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign
210 dest->length = source->length - exif_size - exif_offset;
211 dest->data = malloc(dest->length);
212
213 memcpy(dest->data, source->data, exif_pos - exif_offset);
214 memcpy((uint8_t*)dest->data + exif_pos - exif_offset,
215 (uint8_t*)source->data + exif_pos + exif_size,
216 source->length - exif_pos - exif_size);
217}
218
Dichen Zhang636f5242022-12-07 20:25:44 +0000219/* Encode API-0 */
220status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
221 jpegr_transfer_function hdr_tf,
222 jr_compressed_ptr dest,
223 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000224 jr_exif_ptr exif) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000225 if (uncompressed_p010_image == nullptr || dest == nullptr) {
226 return ERROR_JPEGR_INVALID_NULL_PTR;
227 }
228
229 if (quality < 0 || quality > 100) {
230 return ERROR_JPEGR_INVALID_INPUT_TYPE;
231 }
232
233 jpegr_metadata metadata;
234 metadata.version = kJpegrVersion;
235 metadata.transferFunction = hdr_tf;
236 if (hdr_tf == JPEGR_TF_PQ) {
237 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
238 }
239
240 jpegr_uncompressed_struct uncompressed_yuv_420_image;
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800241 unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
242 uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
243 uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
Dichen Zhang636f5242022-12-07 20:25:44 +0000244 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
245
246 jpegr_uncompressed_struct map;
247 JPEGR_CHECK(generateRecoveryMap(
248 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
249 std::unique_ptr<uint8_t[]> map_data;
250 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
251
252 jpegr_compressed_struct compressed_map;
253 compressed_map.maxLength = map.width * map.height;
254 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
255 compressed_map.data = compressed_map_data.get();
256 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
257
258 JpegEncoder jpeg_encoder;
259 // TODO: determine ICC data based on color gamut information
260 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
261 uncompressed_yuv_420_image.width,
262 uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
263 return ERROR_JPEGR_ENCODE_ERROR;
264 }
265 jpegr_compressed_struct jpeg;
266 jpeg.data = jpeg_encoder.getCompressedImagePtr();
267 jpeg.length = jpeg_encoder.getCompressedImageSize();
268
Dichen Zhangd18bc302022-12-16 20:55:24 +0000269 jpegr_exif_struct new_exif;
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800270 if (exif == nullptr || exif->data == nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +0000271 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
272 } else {
273 new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
274 }
275 new_exif.data = new uint8_t[new_exif.length];
276 std::unique_ptr<uint8_t[]> new_exif_data;
277 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
278 JPEGR_CHECK(updateExif(exif, &new_exif));
279
280 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000281
282 return NO_ERROR;
283}
284
285/* Encode API-1 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000286status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
287 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500288 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400289 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000290 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000291 jr_exif_ptr exif) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000292 if (uncompressed_p010_image == nullptr
293 || uncompressed_yuv_420_image == nullptr
294 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000295 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000296 }
297
Dichen Zhangffa34012022-11-03 23:21:13 +0000298 if (quality < 0 || quality > 100) {
299 return ERROR_JPEGR_INVALID_INPUT_TYPE;
300 }
301
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400302 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
303 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
304 return ERROR_JPEGR_RESOLUTION_MISMATCH;
305 }
306
Nick Deakin6bd90432022-11-20 16:26:37 -0500307 jpegr_metadata metadata;
308 metadata.version = kJpegrVersion;
309 metadata.transferFunction = hdr_tf;
310 if (hdr_tf == JPEGR_TF_PQ) {
311 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
312 }
313
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400314 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000315 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500316 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400317 std::unique_ptr<uint8_t[]> map_data;
318 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
319
320 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000321 compressed_map.maxLength = map.width * map.height;
322 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400323 compressed_map.data = compressed_map_data.get();
324 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
325
326 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500327 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400328 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
329 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000330 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400331 return ERROR_JPEGR_ENCODE_ERROR;
332 }
333 jpegr_compressed_struct jpeg;
334 jpeg.data = jpeg_encoder.getCompressedImagePtr();
335 jpeg.length = jpeg_encoder.getCompressedImageSize();
336
Dichen Zhangd18bc302022-12-16 20:55:24 +0000337 jpegr_exif_struct new_exif;
338 if (exif == nullptr || exif->data == nullptr) {
339 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
340 } else {
341 new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
342 }
343
344 new_exif.data = new uint8_t[new_exif.length];
345 std::unique_ptr<uint8_t[]> new_exif_data;
346 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
347 JPEGR_CHECK(updateExif(exif, &new_exif));
348
349 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400350
Dichen Zhang6947d532022-10-22 02:16:21 +0000351 return NO_ERROR;
352}
353
Dichen Zhang636f5242022-12-07 20:25:44 +0000354/* Encode API-2 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000355status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
356 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400357 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500358 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000359 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000360 if (uncompressed_p010_image == nullptr
361 || uncompressed_yuv_420_image == nullptr
362 || compressed_jpeg_image == nullptr
363 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000364 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000365 }
366
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400367 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
368 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
369 return ERROR_JPEGR_RESOLUTION_MISMATCH;
370 }
371
Nick Deakin6bd90432022-11-20 16:26:37 -0500372 jpegr_metadata metadata;
373 metadata.version = kJpegrVersion;
374 metadata.transferFunction = hdr_tf;
375 if (hdr_tf == JPEGR_TF_PQ) {
376 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
377 }
378
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400379 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000380 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500381 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400382 std::unique_ptr<uint8_t[]> map_data;
383 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
384
385 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000386 compressed_map.maxLength = map.width * map.height;
387 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400388 compressed_map.data = compressed_map_data.get();
389 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
390
Dichen Zhangd18bc302022-12-16 20:55:24 +0000391 // Extract EXIF from JPEG without decoding.
392 JpegDecoder jpeg_decoder;
393 if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
394 return ERROR_JPEGR_DECODE_ERROR;
395 }
396
Dichen Zhang63d92512023-01-04 12:01:16 -0800397 // Update exif.
Dichen Zhangd18bc302022-12-16 20:55:24 +0000398 jpegr_exif_struct exif;
399 exif.data = nullptr;
400 exif.length = 0;
Dichen Zhang63d92512023-01-04 12:01:16 -0800401 jpegr_compressed_struct new_jpeg_image;
402 new_jpeg_image.data = nullptr;
403 new_jpeg_image.length = 0;
Dichen Zhangd18bc302022-12-16 20:55:24 +0000404 if (jpeg_decoder.getEXIFPos() != 0) {
Dichen Zhang63d92512023-01-04 12:01:16 -0800405 copyJpegWithoutExif(&new_jpeg_image,
406 compressed_jpeg_image,
407 jpeg_decoder.getEXIFPos(),
408 jpeg_decoder.getEXIFSize());
Dichen Zhangd18bc302022-12-16 20:55:24 +0000409 exif.data = jpeg_decoder.getEXIFPtr();
410 exif.length = jpeg_decoder.getEXIFSize();
411 }
412
413 jpegr_exif_struct new_exif;
414 if (exif.data == nullptr) {
415 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
416 } else {
417 new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
418 }
419
420 new_exif.data = new uint8_t[new_exif.length];
421 std::unique_ptr<uint8_t[]> new_exif_data;
422 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
423 JPEGR_CHECK(updateExif(&exif, &new_exif));
424
425 JPEGR_CHECK(appendRecoveryMap(
Dichen Zhang63d92512023-01-04 12:01:16 -0800426 new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
427 &compressed_map, &new_exif, &metadata, dest));
428
429 if (new_jpeg_image.data != nullptr) {
430 free(new_jpeg_image.data);
431 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400432
Dichen Zhang6947d532022-10-22 02:16:21 +0000433 return NO_ERROR;
434}
435
Dichen Zhang636f5242022-12-07 20:25:44 +0000436/* Encode API-3 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000437status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400438 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500439 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000440 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000441 if (uncompressed_p010_image == nullptr
442 || compressed_jpeg_image == nullptr
443 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000444 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000445 }
446
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400447 JpegDecoder jpeg_decoder;
448 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
449 return ERROR_JPEGR_DECODE_ERROR;
450 }
451 jpegr_uncompressed_struct uncompressed_yuv_420_image;
452 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
453 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
454 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500455 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400456
Dichen Zhang63d92512023-01-04 12:01:16 -0800457 // Update exif.
Dichen Zhangd18bc302022-12-16 20:55:24 +0000458 jpegr_exif_struct exif;
459 exif.data = nullptr;
460 exif.length = 0;
Dichen Zhang63d92512023-01-04 12:01:16 -0800461 jpegr_compressed_struct new_jpeg_image;
462 new_jpeg_image.data = nullptr;
463 new_jpeg_image.length = 0;
Dichen Zhangd18bc302022-12-16 20:55:24 +0000464 if (jpeg_decoder.getEXIFPos() != 0) {
Dichen Zhang63d92512023-01-04 12:01:16 -0800465 copyJpegWithoutExif(&new_jpeg_image,
466 compressed_jpeg_image,
467 jpeg_decoder.getEXIFPos(),
468 jpeg_decoder.getEXIFSize());
Dichen Zhangd18bc302022-12-16 20:55:24 +0000469 exif.data = jpeg_decoder.getEXIFPtr();
470 exif.length = jpeg_decoder.getEXIFSize();
471 }
472
473 jpegr_exif_struct new_exif;
474 if (exif.data == nullptr) {
475 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
476 } else {
477 new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
478 }
479 new_exif.data = new uint8_t[new_exif.length];
480 std::unique_ptr<uint8_t[]> new_exif_data;
481 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
482 JPEGR_CHECK(updateExif(&exif, &new_exif));
483
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400484 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
485 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
486 return ERROR_JPEGR_RESOLUTION_MISMATCH;
487 }
488
Nick Deakin6bd90432022-11-20 16:26:37 -0500489 jpegr_metadata metadata;
490 metadata.version = kJpegrVersion;
491 metadata.transferFunction = hdr_tf;
492 if (hdr_tf == JPEGR_TF_PQ) {
493 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
494 }
495
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400496 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000497 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500498 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400499 std::unique_ptr<uint8_t[]> map_data;
500 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
501
502 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000503 compressed_map.maxLength = map.width * map.height;
504 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400505 compressed_map.data = compressed_map_data.get();
506 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
507
Dichen Zhangd18bc302022-12-16 20:55:24 +0000508 JPEGR_CHECK(appendRecoveryMap(
Dichen Zhang63d92512023-01-04 12:01:16 -0800509 new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
510 &compressed_map, &new_exif, &metadata, dest));
511
512 if (new_jpeg_image.data != nullptr) {
513 free(new_jpeg_image.data);
514 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400515
Dichen Zhang6947d532022-10-22 02:16:21 +0000516 return NO_ERROR;
517}
518
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000519status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
520 jr_info_ptr jpegr_info) {
521 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
522 return ERROR_JPEGR_INVALID_NULL_PTR;
523 }
524
525 jpegr_compressed_struct primary_image, recovery_map;
526 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
527 &primary_image, &recovery_map));
528
529 JpegDecoder jpeg_decoder;
530 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
531 &jpegr_info->width, &jpegr_info->height,
532 jpegr_info->iccData, jpegr_info->exifData)) {
533 return ERROR_JPEGR_DECODE_ERROR;
534 }
535
536 return NO_ERROR;
537}
538
Dichen Zhang636f5242022-12-07 20:25:44 +0000539/* Decode API */
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400540status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000541 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000542 jr_exif_ptr exif,
543 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000544 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000545 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000546 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000547 // TODO: fill EXIF data
548 (void) exif;
549
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000550 if (request_sdr) {
551 JpegDecoder jpeg_decoder;
552 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
553 true)) {
554 return ERROR_JPEGR_DECODE_ERROR;
555 }
556 jpegr_uncompressed_struct uncompressed_rgba_image;
557 uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
558 uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
559 uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
560 memcpy(dest->data, uncompressed_rgba_image.data,
561 uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
562 dest->width = uncompressed_rgba_image.width;
563 dest->height = uncompressed_rgba_image.height;
564 return NO_ERROR;
565 }
566
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400567 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500568 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000569 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400570
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400571 JpegDecoder jpeg_decoder;
572 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
573 return ERROR_JPEGR_DECODE_ERROR;
574 }
575
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000576 JpegDecoder recovery_map_decoder;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000577 if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000578 return ERROR_JPEGR_DECODE_ERROR;
579 }
580
581 jpegr_uncompressed_struct map;
582 map.data = recovery_map_decoder.getDecompressedImagePtr();
583 map.width = recovery_map_decoder.getDecompressedImageWidth();
584 map.height = recovery_map_decoder.getDecompressedImageHeight();
585
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400586 jpegr_uncompressed_struct uncompressed_yuv_420_image;
587 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
588 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
589 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
590
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000591 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000592 jpeg_decoder.getXMPSize(), &metadata)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000593 return ERROR_JPEGR_DECODE_ERROR;
594 }
595
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000596 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000597 return NO_ERROR;
598}
599
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400600status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
601 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700602 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000603 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700604 }
605
Nick Deakin6bd90432022-11-20 16:26:37 -0500606 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400607 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000608 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
609 uncompressed_recovery_map->width,
610 uncompressed_recovery_map->height,
611 kMapCompressQuality,
612 nullptr,
613 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400614 true /* isSingleChannel */)) {
615 return ERROR_JPEGR_ENCODE_ERROR;
616 }
617
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000618 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400619 return ERROR_JPEGR_BUFFER_TOO_SMALL;
620 }
621
622 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
623 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500624 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400625
Dichen Zhang6947d532022-10-22 02:16:21 +0000626 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700627}
628
Dichen Zhang6947d532022-10-22 02:16:21 +0000629status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
630 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500631 jr_metadata_ptr metadata,
632 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700633 if (uncompressed_yuv_420_image == nullptr
634 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500635 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700636 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000637 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700638 }
639
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400640 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
641 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
642 return ERROR_JPEGR_RESOLUTION_MISMATCH;
643 }
644
Nick Deakin6bd90432022-11-20 16:26:37 -0500645 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
646 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
647 return ERROR_JPEGR_INVALID_COLORGAMUT;
648 }
649
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400650 size_t image_width = uncompressed_yuv_420_image->width;
651 size_t image_height = uncompressed_yuv_420_image->height;
652 size_t map_width = image_width / kMapDimensionScaleFactor;
653 size_t map_height = image_height / kMapDimensionScaleFactor;
654
655 dest->width = map_width;
656 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500657 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400658 dest->data = new uint8_t[map_width * map_height];
659 std::unique_ptr<uint8_t[]> map_data;
660 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
661
Nick Deakin6bd90432022-11-20 16:26:37 -0500662 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500663 float hdr_white_nits = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500664 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000665 case JPEGR_TF_LINEAR:
666 hdrInvOetf = identityConversion;
667 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500668 case JPEGR_TF_HLG:
669 hdrInvOetf = hlgInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500670 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500671 break;
672 case JPEGR_TF_PQ:
673 hdrInvOetf = pqInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500674 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500675 break;
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000676 case JPEGR_TF_UNSPECIFIED:
677 // Should be impossible to hit after input validation.
678 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500679 }
680
681 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
682 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
683
684 ColorCalculationFn luminanceFn = nullptr;
685 switch (uncompressed_yuv_420_image->colorGamut) {
686 case JPEGR_COLORGAMUT_BT709:
687 luminanceFn = srgbLuminance;
688 break;
689 case JPEGR_COLORGAMUT_P3:
690 luminanceFn = p3Luminance;
691 break;
692 case JPEGR_COLORGAMUT_BT2100:
693 luminanceFn = bt2100Luminance;
694 break;
695 case JPEGR_COLORGAMUT_UNSPECIFIED:
696 // Should be impossible to hit after input validation.
697 return ERROR_JPEGR_INVALID_COLORGAMUT;
698 }
699
Nick Deakin594a4ca2022-11-16 20:57:42 -0500700 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500701 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400702 for (size_t y = 0; y < image_height; ++y) {
703 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500704 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
705 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500706 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
707 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500708 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500709
Nick Deakin6bd90432022-11-20 16:26:37 -0500710 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500711 if (hdr_y_nits > hdr_y_nits_max) {
712 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400713 }
714 }
715 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500716 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400717
Nick Deakin6bd90432022-11-20 16:26:37 -0500718 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
719 if (metadata->transferFunction == JPEGR_TF_PQ) {
720 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
721 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
722 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400723
724 for (size_t y = 0; y < map_height; ++y) {
725 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500726 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
727 kMapDimensionScaleFactor, x, y);
728 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
729 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin65f492a2022-11-29 22:47:40 -0500730 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400731
Nick Deakin594a4ca2022-11-16 20:57:42 -0500732 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
733 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500734 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
735 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500736 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400737
738 size_t pixel_idx = x + y * map_width;
739 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500740 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400741 }
742 }
743
744 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000745 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700746}
747
Dichen Zhang6947d532022-10-22 02:16:21 +0000748status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
749 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500750 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000751 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700752 if (uncompressed_yuv_420_image == nullptr
753 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500754 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700755 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000756 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700757 }
758
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400759 size_t width = uncompressed_yuv_420_image->width;
760 size_t height = uncompressed_yuv_420_image->height;
761
762 dest->width = width;
763 dest->height = height;
764 size_t pixel_count = width * height;
765
Nick Deakin6bd90432022-11-20 16:26:37 -0500766 ColorTransformFn hdrOetf = nullptr;
767 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000768 case JPEGR_TF_LINEAR:
769 hdrOetf = identityConversion;
770 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500771 case JPEGR_TF_HLG:
772 hdrOetf = hlgOetf;
773 break;
774 case JPEGR_TF_PQ:
775 hdrOetf = pqOetf;
776 break;
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000777 case JPEGR_TF_UNSPECIFIED:
778 // Should be impossible to hit after input validation.
779 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500780 }
781
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400782 for (size_t y = 0; y < height; ++y) {
783 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500784 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
785 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
786 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400787
Nick Deakin6bd90432022-11-20 16:26:37 -0500788 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400789 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500790 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400791
Nick Deakin38125332022-12-12 15:48:24 -0500792 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
Nick Deakin6bd90432022-11-20 16:26:37 -0500793 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400794
Nick Deakin6bd90432022-11-20 16:26:37 -0500795 size_t pixel_idx = x + y * width;
796 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400797 }
798 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000799 return NO_ERROR;
800}
801
802status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
803 jr_compressed_ptr primary_image,
804 jr_compressed_ptr recovery_map) {
805 if (compressed_jpegr_image == nullptr) {
806 return ERROR_JPEGR_INVALID_NULL_PTR;
807 }
808
809 MessageHandler msg_handler;
810 std::shared_ptr<DataSegment> seg =
811 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
812 static_cast<const uint8_t*>(compressed_jpegr_image->data),
813 DataSegment::BufferDispositionPolicy::kDontDelete);
814 DataSegmentDataSource data_source(seg);
815 JpegInfoBuilder jpeg_info_builder;
816 jpeg_info_builder.SetImageLimit(2);
817 JpegScanner jpeg_scanner(&msg_handler);
818 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
819 data_source.Reset();
820
821 if (jpeg_scanner.HasError()) {
822 return ERROR_JPEGR_INVALID_INPUT_TYPE;
823 }
824
825 const auto& jpeg_info = jpeg_info_builder.GetInfo();
826 const auto& image_ranges = jpeg_info.GetImageRanges();
827 if (image_ranges.empty()) {
828 return ERROR_JPEGR_INVALID_INPUT_TYPE;
829 }
830
831 if (image_ranges.size() != 2) {
832 // Must be 2 JPEG Images
833 return ERROR_JPEGR_INVALID_INPUT_TYPE;
834 }
835
836 if (primary_image != nullptr) {
837 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
838 image_ranges[0].GetBegin();
839 primary_image->length = image_ranges[0].GetLength();
840 }
841
842 if (recovery_map != nullptr) {
843 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
844 image_ranges[1].GetBegin();
845 recovery_map->length = image_ranges[1].GetLength();
846 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400847
Dichen Zhang6947d532022-10-22 02:16:21 +0000848 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700849}
850
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000851
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400852status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000853 jr_compressed_ptr dest) {
854 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000855 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700856 }
857
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000858 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700859}
860
Dichen Zhangd18bc302022-12-16 20:55:24 +0000861// JPEG/R structure:
862// SOI (ff d8)
863// APP1 (ff e1)
864// 2 bytes of length (2 + length of exif package)
865// EXIF package (this includes the first two bytes representing the package length)
866// APP1 (ff e1)
867// 2 bytes of length (2 + 29 + length of xmp package)
868// name space ("http://ns.adobe.com/xap/1.0/\0")
869// xmp
870// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
871// secondary image (the recovery map)
872//
873// Metadata versions we are using:
874// ECMA TR-98 for JFIF marker
875// Exif 2.2 spec for EXIF marker
876// Adobe XMP spec part 3 for XMP marker
877// ICC v4.3 spec for ICC
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400878status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
879 jr_compressed_ptr compressed_recovery_map,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000880 jr_exif_ptr exif,
Nick Deakin6bd90432022-11-20 16:26:37 -0500881 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400882 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000883 if (compressed_jpeg_image == nullptr
884 || compressed_recovery_map == nullptr
Dichen Zhangd18bc302022-12-16 20:55:24 +0000885 || exif == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500886 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000887 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000888 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700889 }
890
Dichen Zhanga8766262022-11-07 23:48:24 +0000891 int pos = 0;
892
Dichen Zhangd18bc302022-12-16 20:55:24 +0000893 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +0000894 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
895 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000896
897 // Write EXIF
898 {
899 const int length = 2 + exif->length;
900 const uint8_t lengthH = ((length >> 8) & 0xff);
901 const uint8_t lengthL = (length & 0xff);
902 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
903 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
904 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
905 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
906 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
907 }
908
909 // Prepare and write XMP
910 {
911 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
912 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
913 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
914 // 2 bytes: representing the length of the package
915 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
916 // x bytes: length of xmp packet
Dichen Zhang25df9c82023-01-03 17:04:10 -0800917 const int length = 2 + nameSpaceLength + xmp.size();
Dichen Zhangd18bc302022-12-16 20:55:24 +0000918 const uint8_t lengthH = ((length >> 8) & 0xff);
919 const uint8_t lengthL = (length & 0xff);
920 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
921 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
922 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
923 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
924 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
925 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
926 }
927
928 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000929 JPEGR_CHECK(Write(dest,
930 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000931
932 // Write secondary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000933 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000934
935 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +0000936 dest->length = pos;
937
Dichen Zhangd18bc302022-12-16 20:55:24 +0000938 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +0000939 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700940}
941
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800942status_t RecoveryMap::toneMap(jr_uncompressed_ptr src,
Dichen Zhang636f5242022-12-07 20:25:44 +0000943 jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800944 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000945 return ERROR_JPEGR_INVALID_NULL_PTR;
946 }
947
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800948 dest->width = src->width;
949 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +0000950
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800951 size_t pixel_count = src->width * src->height;
952 for (size_t y = 0; y < src->height; ++y) {
953 for (size_t x = 0; x < src->width; ++x) {
954 size_t pixel_y_idx = x + y * src->width;
955 size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
956
957 uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
958 >> 6;
959 uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
960 >> 6;
961 uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
962 >> 6;
963
964 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
965 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
966 uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
967
968 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
969 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
970 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
971 }
972 }
973
974 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +0000975
976 return NO_ERROR;
977}
978
Dichen Zhang85b37562022-10-11 11:08:28 -0700979} // namespace android::recoverymap