blob: 3fc5e4aba022d22d8f2ed819adb3c6e56c475763 [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>
28#include <utils/Log.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040029
30#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000031#include <sstream>
32#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000033#include <cmath>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000034
35using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000036using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070037
38namespace android::recoverymap {
39
Nick Deakinf6bca5a2022-11-04 10:43:43 -040040#define JPEGR_CHECK(x) \
41 { \
42 status_t status = (x); \
43 if ((status) != NO_ERROR) { \
44 return status; \
45 } \
46 }
47
Nick Deakin6bd90432022-11-20 16:26:37 -050048// The current JPEGR version that we encode to
49static const uint32_t kJpegrVersion = 1;
50
Nick Deakinf6bca5a2022-11-04 10:43:43 -040051// Map is quarter res / sixteenth size
52static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000053// JPEG compress quality (0 ~ 100) for recovery map
54static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040055
Nick Deakin6bd90432022-11-20 16:26:37 -050056// TODO: fill in st2086 metadata
57static const st2086_metadata kSt2086Metadata = {
58 {0.0f, 0.0f},
59 {0.0f, 0.0f},
60 {0.0f, 0.0f},
61 {0.0f, 0.0f},
62 0,
63 1.0f,
64};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040065
Dichen Zhang72fd2b12022-11-01 06:11:50 +000066/*
Dichen Zhanga8766262022-11-07 23:48:24 +000067 * Helper function used for writing data to destination.
68 *
69 * @param destination destination of the data to be written.
70 * @param source source of data being written.
71 * @param length length of the data to be written.
72 * @param position cursor in desitination where the data is to be written.
73 * @return status of succeed or error code.
74 */
75status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000076 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000077 return ERROR_JPEGR_BUFFER_TOO_SMALL;
78 }
79
80 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
81 position += length;
82 return NO_ERROR;
83}
84
Dichen Zhangd18bc302022-12-16 20:55:24 +000085status_t Write(jr_exif_ptr destination, const void* source, size_t length, int &position) {
86 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
87 position += length;
88 return NO_ERROR;
89}
90
91// If the EXIF package doesn't exist in the input JPEG, we'll create one with one entry
92// where the length is represented by this value.
93const size_t PSEUDO_EXIF_PACKAGE_LENGTH = 28;
94// If the EXIF package exists in the input JPEG, we'll add an "JR" entry where the length is
95// represented by this value.
96const size_t EXIF_J_R_ENTRY_LENGTH = 12;
97
98/*
99 * Helper function
100 * Add J R entry to existing exif, or create a new one with J R entry if it's null.
101 * EXIF syntax / change:
102 * ori:
103 * FF E1 - APP1
104 * 01 FC - size of APP1 (to be calculated)
105 * -----------------------------------------------------
106 * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
107 * 49 49 2A 00 - TIFF Header
108 * 08 00 00 00 - offset to the IFD (image file directory)
109 * 06 00 - 6 entries
110 * 00 01 - Width Tag
111 * 03 00 - 'Short' type
112 * 01 00 00 00 - one entry
113 * 00 05 00 00 - image with 0x500
114 *--------------------------------------------------------------------------
115 * new:
116 * FF E1 - APP1
117 * 02 08 - new size, equals to old size + EXIF_J_R_ENTRY_LENGTH (12)
118 *-----------------------------------------------------
119 * 45 78 69 66 00 00 - Exif\0\0 "Exif header"
120 * 49 49 2A 00 - TIFF Header
121 * 08 00 00 00 - offset to the IFD (image file directory)
122 * 07 00 - +1 entry
123 * 4A 52 Custom ('J''R') Tag
124 * 07 00 - Unknown type
125 * 01 00 00 00 - one element
126 * 00 00 00 00 - empty data
127 * 00 01 - Width Tag
128 * 03 00 - 'Short' type
129 * 01 00 00 00 - one entry
130 * 00 05 00 00 - image with 0x500
131 */
132status_t updateExif(jr_exif_ptr exif, jr_exif_ptr dest) {
133 if (exif == nullptr || exif->data == nullptr) {
134 uint8_t data[PSEUDO_EXIF_PACKAGE_LENGTH] = {
135 0x45, 0x78, 0x69, 0x66, 0x00, 0x00,
136 0x49, 0x49, 0x2A, 0x00,
137 0x08, 0x00, 0x00, 0x00,
138 0x01, 0x00,
139 0x4A, 0x52,
140 0x07, 0x00,
141 0x01, 0x00, 0x00, 0x00,
142 0x00, 0x00, 0x00, 0x00};
143 int pos = 0;
144 Write(dest, data, PSEUDO_EXIF_PACKAGE_LENGTH, pos);
145 return NO_ERROR;
146 }
147
148 int num_entry = 0;
149 uint8_t num_entry_low = 0;
150 uint8_t num_entry_high = 0;
151 bool use_big_endian = false;
152 if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4949) {
153 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[14];
154 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[15];
155 } else if (reinterpret_cast<uint16_t*>(exif->data)[3] == 0x4d4d) {
156 use_big_endian = true;
157 num_entry_high = reinterpret_cast<uint8_t*>(exif->data)[14];
158 num_entry_low = reinterpret_cast<uint8_t*>(exif->data)[15];
159 } else {
160 return ERROR_JPEGR_METADATA_ERROR;
161 }
162 num_entry = (num_entry_high << 8) | num_entry_low;
163 num_entry += 1;
164 num_entry_low = num_entry & 0xff;
165 num_entry_high = (num_entry << 8) & 0xff;
166
167 int pos = 0;
168 Write(dest, (uint8_t*)exif->data, 14, pos);
169
170 if (use_big_endian) {
171 Write(dest, &num_entry_high, 1, pos);
172 Write(dest, &num_entry_low, 1, pos);
173 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
174 0x4A, 0x52,
175 0x07, 0x00,
176 0x01, 0x00, 0x00, 0x00,
177 0x00, 0x00, 0x00, 0x00};
178 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
179 } else {
180 Write(dest, &num_entry_low, 1, pos);
181 Write(dest, &num_entry_high, 1, pos);
182 uint8_t data[EXIF_J_R_ENTRY_LENGTH] = {
183 0x4A, 0x52,
184 0x00, 0x07,
185 0x00, 0x00, 0x00, 0x01,
186 0x00, 0x00, 0x00, 0x00};
187 Write(dest, data, EXIF_J_R_ENTRY_LENGTH, pos);
188 }
189
190 Write(dest, (uint8_t*)exif->data + 16, exif->length - 16, pos);
191
192 return NO_ERROR;
193}
194
Dichen Zhang63d92512023-01-04 12:01:16 -0800195/*
196 * Helper function copies the JPEG image from without EXIF.
197 *
198 * @param dest destination of the data to be written.
199 * @param source source of data being written.
200 * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
201 * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>).
202 * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
203 */
204void copyJpegWithoutExif(jr_compressed_ptr dest,
205 jr_compressed_ptr source,
206 size_t exif_pos,
207 size_t exif_size) {
208 memcpy(dest, source, sizeof(jpegr_compressed_struct));
209
210 const size_t exif_offset = 4; //exif_pos has 4 bypes offset to the FF sign
211 dest->length = source->length - exif_size - exif_offset;
212 dest->data = malloc(dest->length);
213
214 memcpy(dest->data, source->data, exif_pos - exif_offset);
215 memcpy((uint8_t*)dest->data + exif_pos - exif_offset,
216 (uint8_t*)source->data + exif_pos + exif_size,
217 source->length - exif_pos - exif_size);
218}
219
Dichen Zhang636f5242022-12-07 20:25:44 +0000220/* Encode API-0 */
221status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
222 jpegr_transfer_function hdr_tf,
223 jr_compressed_ptr dest,
224 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000225 jr_exif_ptr exif) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000226 if (uncompressed_p010_image == nullptr || dest == nullptr) {
227 return ERROR_JPEGR_INVALID_NULL_PTR;
228 }
229
230 if (quality < 0 || quality > 100) {
231 return ERROR_JPEGR_INVALID_INPUT_TYPE;
232 }
233
234 jpegr_metadata metadata;
235 metadata.version = kJpegrVersion;
236 metadata.transferFunction = hdr_tf;
237 if (hdr_tf == JPEGR_TF_PQ) {
238 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
239 }
240
241 jpegr_uncompressed_struct uncompressed_yuv_420_image;
242 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
243
244 jpegr_uncompressed_struct map;
245 JPEGR_CHECK(generateRecoveryMap(
246 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
247 std::unique_ptr<uint8_t[]> map_data;
248 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
249
250 jpegr_compressed_struct compressed_map;
251 compressed_map.maxLength = map.width * map.height;
252 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
253 compressed_map.data = compressed_map_data.get();
254 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
255
256 JpegEncoder jpeg_encoder;
257 // TODO: determine ICC data based on color gamut information
258 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
259 uncompressed_yuv_420_image.width,
260 uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
261 return ERROR_JPEGR_ENCODE_ERROR;
262 }
263 jpegr_compressed_struct jpeg;
264 jpeg.data = jpeg_encoder.getCompressedImagePtr();
265 jpeg.length = jpeg_encoder.getCompressedImageSize();
266
Dichen Zhangd18bc302022-12-16 20:55:24 +0000267 jpegr_exif_struct new_exif;
268 if (exif->data == nullptr) {
269 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
270 } else {
271 new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
272 }
273 new_exif.data = new uint8_t[new_exif.length];
274 std::unique_ptr<uint8_t[]> new_exif_data;
275 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
276 JPEGR_CHECK(updateExif(exif, &new_exif));
277
278 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000279
280 return NO_ERROR;
281}
282
283/* Encode API-1 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000284status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
285 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500286 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400287 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000288 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000289 jr_exif_ptr exif) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000290 if (uncompressed_p010_image == nullptr
291 || uncompressed_yuv_420_image == nullptr
292 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000293 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000294 }
295
Dichen Zhangffa34012022-11-03 23:21:13 +0000296 if (quality < 0 || quality > 100) {
297 return ERROR_JPEGR_INVALID_INPUT_TYPE;
298 }
299
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400300 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
301 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
302 return ERROR_JPEGR_RESOLUTION_MISMATCH;
303 }
304
Nick Deakin6bd90432022-11-20 16:26:37 -0500305 jpegr_metadata metadata;
306 metadata.version = kJpegrVersion;
307 metadata.transferFunction = hdr_tf;
308 if (hdr_tf == JPEGR_TF_PQ) {
309 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
310 }
311
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400312 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000313 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500314 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400315 std::unique_ptr<uint8_t[]> map_data;
316 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
317
318 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000319 compressed_map.maxLength = map.width * map.height;
320 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400321 compressed_map.data = compressed_map_data.get();
322 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
323
324 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500325 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400326 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
327 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000328 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400329 return ERROR_JPEGR_ENCODE_ERROR;
330 }
331 jpegr_compressed_struct jpeg;
332 jpeg.data = jpeg_encoder.getCompressedImagePtr();
333 jpeg.length = jpeg_encoder.getCompressedImageSize();
334
Dichen Zhangd18bc302022-12-16 20:55:24 +0000335 jpegr_exif_struct new_exif;
336 if (exif == nullptr || exif->data == nullptr) {
337 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
338 } else {
339 new_exif.length = exif->length + EXIF_J_R_ENTRY_LENGTH;
340 }
341
342 new_exif.data = new uint8_t[new_exif.length];
343 std::unique_ptr<uint8_t[]> new_exif_data;
344 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
345 JPEGR_CHECK(updateExif(exif, &new_exif));
346
347 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &new_exif, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400348
Dichen Zhang6947d532022-10-22 02:16:21 +0000349 return NO_ERROR;
350}
351
Dichen Zhang636f5242022-12-07 20:25:44 +0000352/* Encode API-2 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000353status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
354 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400355 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500356 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000357 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000358 if (uncompressed_p010_image == nullptr
359 || uncompressed_yuv_420_image == nullptr
360 || compressed_jpeg_image == nullptr
361 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000362 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000363 }
364
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400365 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
366 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
367 return ERROR_JPEGR_RESOLUTION_MISMATCH;
368 }
369
Nick Deakin6bd90432022-11-20 16:26:37 -0500370 jpegr_metadata metadata;
371 metadata.version = kJpegrVersion;
372 metadata.transferFunction = hdr_tf;
373 if (hdr_tf == JPEGR_TF_PQ) {
374 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
375 }
376
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400377 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000378 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500379 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400380 std::unique_ptr<uint8_t[]> map_data;
381 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
382
383 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000384 compressed_map.maxLength = map.width * map.height;
385 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400386 compressed_map.data = compressed_map_data.get();
387 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
388
Dichen Zhangd18bc302022-12-16 20:55:24 +0000389 // Extract EXIF from JPEG without decoding.
390 JpegDecoder jpeg_decoder;
391 if (!jpeg_decoder.extractEXIF(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
392 return ERROR_JPEGR_DECODE_ERROR;
393 }
394
Dichen Zhang63d92512023-01-04 12:01:16 -0800395 // Update exif.
Dichen Zhangd18bc302022-12-16 20:55:24 +0000396 jpegr_exif_struct exif;
397 exif.data = nullptr;
398 exif.length = 0;
Dichen Zhang63d92512023-01-04 12:01:16 -0800399 jpegr_compressed_struct new_jpeg_image;
400 new_jpeg_image.data = nullptr;
401 new_jpeg_image.length = 0;
Dichen Zhangd18bc302022-12-16 20:55:24 +0000402 if (jpeg_decoder.getEXIFPos() != 0) {
Dichen Zhang63d92512023-01-04 12:01:16 -0800403 copyJpegWithoutExif(&new_jpeg_image,
404 compressed_jpeg_image,
405 jpeg_decoder.getEXIFPos(),
406 jpeg_decoder.getEXIFSize());
Dichen Zhangd18bc302022-12-16 20:55:24 +0000407 exif.data = jpeg_decoder.getEXIFPtr();
408 exif.length = jpeg_decoder.getEXIFSize();
409 }
410
411 jpegr_exif_struct new_exif;
412 if (exif.data == nullptr) {
413 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
414 } else {
415 new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
416 }
417
418 new_exif.data = new uint8_t[new_exif.length];
419 std::unique_ptr<uint8_t[]> new_exif_data;
420 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
421 JPEGR_CHECK(updateExif(&exif, &new_exif));
422
423 JPEGR_CHECK(appendRecoveryMap(
Dichen Zhang63d92512023-01-04 12:01:16 -0800424 new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
425 &compressed_map, &new_exif, &metadata, dest));
426
427 if (new_jpeg_image.data != nullptr) {
428 free(new_jpeg_image.data);
429 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400430
Dichen Zhang6947d532022-10-22 02:16:21 +0000431 return NO_ERROR;
432}
433
Dichen Zhang636f5242022-12-07 20:25:44 +0000434/* Encode API-3 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000435status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400436 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500437 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000438 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000439 if (uncompressed_p010_image == nullptr
440 || compressed_jpeg_image == nullptr
441 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000442 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000443 }
444
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400445 JpegDecoder jpeg_decoder;
446 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
447 return ERROR_JPEGR_DECODE_ERROR;
448 }
449 jpegr_uncompressed_struct uncompressed_yuv_420_image;
450 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
451 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
452 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500453 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400454
Dichen Zhang63d92512023-01-04 12:01:16 -0800455 // Update exif.
Dichen Zhangd18bc302022-12-16 20:55:24 +0000456 jpegr_exif_struct exif;
457 exif.data = nullptr;
458 exif.length = 0;
Dichen Zhang63d92512023-01-04 12:01:16 -0800459 jpegr_compressed_struct new_jpeg_image;
460 new_jpeg_image.data = nullptr;
461 new_jpeg_image.length = 0;
Dichen Zhangd18bc302022-12-16 20:55:24 +0000462 if (jpeg_decoder.getEXIFPos() != 0) {
Dichen Zhang63d92512023-01-04 12:01:16 -0800463 copyJpegWithoutExif(&new_jpeg_image,
464 compressed_jpeg_image,
465 jpeg_decoder.getEXIFPos(),
466 jpeg_decoder.getEXIFSize());
Dichen Zhangd18bc302022-12-16 20:55:24 +0000467 exif.data = jpeg_decoder.getEXIFPtr();
468 exif.length = jpeg_decoder.getEXIFSize();
469 }
470
471 jpegr_exif_struct new_exif;
472 if (exif.data == nullptr) {
473 new_exif.length = PSEUDO_EXIF_PACKAGE_LENGTH;
474 } else {
475 new_exif.length = exif.length + EXIF_J_R_ENTRY_LENGTH;
476 }
477 new_exif.data = new uint8_t[new_exif.length];
478 std::unique_ptr<uint8_t[]> new_exif_data;
479 new_exif_data.reset(reinterpret_cast<uint8_t*>(new_exif.data));
480 JPEGR_CHECK(updateExif(&exif, &new_exif));
481
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400482 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
483 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
484 return ERROR_JPEGR_RESOLUTION_MISMATCH;
485 }
486
Nick Deakin6bd90432022-11-20 16:26:37 -0500487 jpegr_metadata metadata;
488 metadata.version = kJpegrVersion;
489 metadata.transferFunction = hdr_tf;
490 if (hdr_tf == JPEGR_TF_PQ) {
491 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
492 }
493
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400494 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000495 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500496 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400497 std::unique_ptr<uint8_t[]> map_data;
498 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
499
500 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000501 compressed_map.maxLength = map.width * map.height;
502 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400503 compressed_map.data = compressed_map_data.get();
504 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
505
Dichen Zhangd18bc302022-12-16 20:55:24 +0000506 JPEGR_CHECK(appendRecoveryMap(
Dichen Zhang63d92512023-01-04 12:01:16 -0800507 new_jpeg_image.data == nullptr ? compressed_jpeg_image : &new_jpeg_image,
508 &compressed_map, &new_exif, &metadata, dest));
509
510 if (new_jpeg_image.data != nullptr) {
511 free(new_jpeg_image.data);
512 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400513
Dichen Zhang6947d532022-10-22 02:16:21 +0000514 return NO_ERROR;
515}
516
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000517status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
518 jr_info_ptr jpegr_info) {
519 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
520 return ERROR_JPEGR_INVALID_NULL_PTR;
521 }
522
523 jpegr_compressed_struct primary_image, recovery_map;
524 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
525 &primary_image, &recovery_map));
526
527 JpegDecoder jpeg_decoder;
528 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
529 &jpegr_info->width, &jpegr_info->height,
530 jpegr_info->iccData, jpegr_info->exifData)) {
531 return ERROR_JPEGR_DECODE_ERROR;
532 }
533
534 return NO_ERROR;
535}
536
Dichen Zhang636f5242022-12-07 20:25:44 +0000537/* Decode API */
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400538status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000539 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000540 jr_exif_ptr exif,
541 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000542 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000543 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000544 }
545
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000546 // TODO: fill EXIF data
547 (void) exif;
548
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400549 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500550 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000551 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400552
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400553
554 JpegDecoder jpeg_decoder;
555 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
556 return ERROR_JPEGR_DECODE_ERROR;
557 }
558
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000559 JpegDecoder recovery_map_decoder;
560 if (!recovery_map_decoder.decompressImage(compressed_map.data,
561 compressed_map.length)) {
562 return ERROR_JPEGR_DECODE_ERROR;
563 }
564
565 jpegr_uncompressed_struct map;
566 map.data = recovery_map_decoder.getDecompressedImagePtr();
567 map.width = recovery_map_decoder.getDecompressedImageWidth();
568 map.height = recovery_map_decoder.getDecompressedImageHeight();
569
570
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400571 jpegr_uncompressed_struct uncompressed_yuv_420_image;
572 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
573 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
574 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
575
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000576 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
577 jpeg_decoder.getXMPSize(), &metadata)) {
578 return ERROR_JPEGR_DECODE_ERROR;
579 }
580
581 if (request_sdr) {
582 memcpy(dest->data, uncompressed_yuv_420_image.data,
583 uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
584 dest->width = uncompressed_yuv_420_image.width;
585 dest->height = uncompressed_yuv_420_image.height;
586 } else {
587 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
588 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400589
Dichen Zhang6947d532022-10-22 02:16:21 +0000590 return NO_ERROR;
591}
592
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400593status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
594 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700595 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000596 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700597 }
598
Nick Deakin6bd90432022-11-20 16:26:37 -0500599 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400600 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000601 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
602 uncompressed_recovery_map->width,
603 uncompressed_recovery_map->height,
604 kMapCompressQuality,
605 nullptr,
606 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400607 true /* isSingleChannel */)) {
608 return ERROR_JPEGR_ENCODE_ERROR;
609 }
610
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000611 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400612 return ERROR_JPEGR_BUFFER_TOO_SMALL;
613 }
614
615 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
616 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500617 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400618
Dichen Zhang6947d532022-10-22 02:16:21 +0000619 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700620}
621
Dichen Zhang6947d532022-10-22 02:16:21 +0000622status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
623 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500624 jr_metadata_ptr metadata,
625 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700626 if (uncompressed_yuv_420_image == nullptr
627 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500628 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700629 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000630 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700631 }
632
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400633 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
634 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
635 return ERROR_JPEGR_RESOLUTION_MISMATCH;
636 }
637
Nick Deakin6bd90432022-11-20 16:26:37 -0500638 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
639 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
640 return ERROR_JPEGR_INVALID_COLORGAMUT;
641 }
642
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400643 size_t image_width = uncompressed_yuv_420_image->width;
644 size_t image_height = uncompressed_yuv_420_image->height;
645 size_t map_width = image_width / kMapDimensionScaleFactor;
646 size_t map_height = image_height / kMapDimensionScaleFactor;
647
648 dest->width = map_width;
649 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500650 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400651 dest->data = new uint8_t[map_width * map_height];
652 std::unique_ptr<uint8_t[]> map_data;
653 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
654
Nick Deakin6bd90432022-11-20 16:26:37 -0500655 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500656 float hdr_white_nits = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500657 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000658 case JPEGR_TF_LINEAR:
659 hdrInvOetf = identityConversion;
660 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500661 case JPEGR_TF_HLG:
662 hdrInvOetf = hlgInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500663 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500664 break;
665 case JPEGR_TF_PQ:
666 hdrInvOetf = pqInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500667 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500668 break;
669 }
670
671 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
672 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
673
674 ColorCalculationFn luminanceFn = nullptr;
675 switch (uncompressed_yuv_420_image->colorGamut) {
676 case JPEGR_COLORGAMUT_BT709:
677 luminanceFn = srgbLuminance;
678 break;
679 case JPEGR_COLORGAMUT_P3:
680 luminanceFn = p3Luminance;
681 break;
682 case JPEGR_COLORGAMUT_BT2100:
683 luminanceFn = bt2100Luminance;
684 break;
685 case JPEGR_COLORGAMUT_UNSPECIFIED:
686 // Should be impossible to hit after input validation.
687 return ERROR_JPEGR_INVALID_COLORGAMUT;
688 }
689
Nick Deakin594a4ca2022-11-16 20:57:42 -0500690 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500691 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400692 for (size_t y = 0; y < image_height; ++y) {
693 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500694 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
695 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500696 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
697 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500698 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500699
Nick Deakin6bd90432022-11-20 16:26:37 -0500700 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500701 if (hdr_y_nits > hdr_y_nits_max) {
702 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400703 }
704 }
705 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500706 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400707
Nick Deakin6bd90432022-11-20 16:26:37 -0500708 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
709 if (metadata->transferFunction == JPEGR_TF_PQ) {
710 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
711 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
712 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400713
714 for (size_t y = 0; y < map_height; ++y) {
715 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500716 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
717 kMapDimensionScaleFactor, x, y);
718 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
719 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin65f492a2022-11-29 22:47:40 -0500720 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400721
Nick Deakin594a4ca2022-11-16 20:57:42 -0500722 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
723 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500724 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
725 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500726 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400727
728 size_t pixel_idx = x + y * map_width;
729 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500730 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400731 }
732 }
733
734 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000735 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700736}
737
Dichen Zhang6947d532022-10-22 02:16:21 +0000738status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
739 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500740 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000741 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700742 if (uncompressed_yuv_420_image == nullptr
743 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500744 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700745 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000746 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700747 }
748
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400749 size_t width = uncompressed_yuv_420_image->width;
750 size_t height = uncompressed_yuv_420_image->height;
751
752 dest->width = width;
753 dest->height = height;
754 size_t pixel_count = width * height;
755
Nick Deakin6bd90432022-11-20 16:26:37 -0500756 ColorTransformFn hdrOetf = nullptr;
757 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000758 case JPEGR_TF_LINEAR:
759 hdrOetf = identityConversion;
760 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500761 case JPEGR_TF_HLG:
762 hdrOetf = hlgOetf;
763 break;
764 case JPEGR_TF_PQ:
765 hdrOetf = pqOetf;
766 break;
767 }
768
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400769 for (size_t y = 0; y < height; ++y) {
770 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500771 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
772 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
773 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400774
Nick Deakin6bd90432022-11-20 16:26:37 -0500775 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400776 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500777 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400778
Nick Deakin38125332022-12-12 15:48:24 -0500779 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
Nick Deakin6bd90432022-11-20 16:26:37 -0500780 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400781
Nick Deakin6bd90432022-11-20 16:26:37 -0500782 size_t pixel_idx = x + y * width;
783 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400784 }
785 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000786 return NO_ERROR;
787}
788
789status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
790 jr_compressed_ptr primary_image,
791 jr_compressed_ptr recovery_map) {
792 if (compressed_jpegr_image == nullptr) {
793 return ERROR_JPEGR_INVALID_NULL_PTR;
794 }
795
796 MessageHandler msg_handler;
797 std::shared_ptr<DataSegment> seg =
798 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
799 static_cast<const uint8_t*>(compressed_jpegr_image->data),
800 DataSegment::BufferDispositionPolicy::kDontDelete);
801 DataSegmentDataSource data_source(seg);
802 JpegInfoBuilder jpeg_info_builder;
803 jpeg_info_builder.SetImageLimit(2);
804 JpegScanner jpeg_scanner(&msg_handler);
805 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
806 data_source.Reset();
807
808 if (jpeg_scanner.HasError()) {
809 return ERROR_JPEGR_INVALID_INPUT_TYPE;
810 }
811
812 const auto& jpeg_info = jpeg_info_builder.GetInfo();
813 const auto& image_ranges = jpeg_info.GetImageRanges();
814 if (image_ranges.empty()) {
815 return ERROR_JPEGR_INVALID_INPUT_TYPE;
816 }
817
818 if (image_ranges.size() != 2) {
819 // Must be 2 JPEG Images
820 return ERROR_JPEGR_INVALID_INPUT_TYPE;
821 }
822
823 if (primary_image != nullptr) {
824 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
825 image_ranges[0].GetBegin();
826 primary_image->length = image_ranges[0].GetLength();
827 }
828
829 if (recovery_map != nullptr) {
830 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
831 image_ranges[1].GetBegin();
832 recovery_map->length = image_ranges[1].GetLength();
833 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400834
Dichen Zhang6947d532022-10-22 02:16:21 +0000835 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700836}
837
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000838
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400839status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000840 jr_compressed_ptr dest) {
841 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000842 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700843 }
844
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000845 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700846}
847
Dichen Zhangd18bc302022-12-16 20:55:24 +0000848// JPEG/R structure:
849// SOI (ff d8)
850// APP1 (ff e1)
851// 2 bytes of length (2 + length of exif package)
852// EXIF package (this includes the first two bytes representing the package length)
853// APP1 (ff e1)
854// 2 bytes of length (2 + 29 + length of xmp package)
855// name space ("http://ns.adobe.com/xap/1.0/\0")
856// xmp
857// primary image (without the first two bytes (SOI) and without EXIF, may have other packages)
858// secondary image (the recovery map)
859//
860// Metadata versions we are using:
861// ECMA TR-98 for JFIF marker
862// Exif 2.2 spec for EXIF marker
863// Adobe XMP spec part 3 for XMP marker
864// ICC v4.3 spec for ICC
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400865status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
866 jr_compressed_ptr compressed_recovery_map,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000867 jr_exif_ptr exif,
Nick Deakin6bd90432022-11-20 16:26:37 -0500868 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400869 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000870 if (compressed_jpeg_image == nullptr
871 || compressed_recovery_map == nullptr
Dichen Zhangd18bc302022-12-16 20:55:24 +0000872 || exif == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500873 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000874 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000875 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700876 }
877
Dichen Zhanga8766262022-11-07 23:48:24 +0000878 int pos = 0;
879
Dichen Zhangd18bc302022-12-16 20:55:24 +0000880 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +0000881 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
882 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000883
884 // Write EXIF
885 {
886 const int length = 2 + exif->length;
887 const uint8_t lengthH = ((length >> 8) & 0xff);
888 const uint8_t lengthL = (length & 0xff);
889 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
890 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
891 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
892 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
893 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
894 }
895
896 // Prepare and write XMP
897 {
898 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
899 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
900 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
901 // 2 bytes: representing the length of the package
902 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
903 // x bytes: length of xmp packet
904 const int length = 3 + nameSpaceLength + xmp.size();
905 const uint8_t lengthH = ((length >> 8) & 0xff);
906 const uint8_t lengthL = (length & 0xff);
907 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
908 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
909 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
910 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
911 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
912 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
913 }
914
915 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000916 JPEGR_CHECK(Write(dest,
917 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000918
919 // Write secondary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000920 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000921
922 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +0000923 dest->length = pos;
924
Dichen Zhangd18bc302022-12-16 20:55:24 +0000925 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +0000926 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700927}
928
Dichen Zhang636f5242022-12-07 20:25:44 +0000929status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
930 jr_uncompressed_ptr dest) {
931 if (uncompressed_p010_image == nullptr || dest == nullptr) {
932 return ERROR_JPEGR_INVALID_NULL_PTR;
933 }
934
935 dest->width = uncompressed_p010_image->width;
936 dest->height = uncompressed_p010_image->height;
937 unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
938 dest->data = dest_data.get();
939
940 // TODO: Tone map algorighm here.
941
942 return NO_ERROR;
943}
944
Dichen Zhang85b37562022-10-11 11:08:28 -0700945} // namespace android::recoverymap