blob: 64021b7ceddcfeecd694e2c13721da920b142690 [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>
21
Dichen Zhanga8766262022-11-07 23:48:24 +000022#include <image_io/jpeg/jpeg_marker.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040023#include <image_io/xml/xml_writer.h>
24
25#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000026#include <sstream>
27#include <string>
28
29using namespace std;
Dichen Zhang85b37562022-10-11 11:08:28 -070030
31namespace android::recoverymap {
32
Nick Deakinf6bca5a2022-11-04 10:43:43 -040033#define JPEGR_CHECK(x) \
34 { \
35 status_t status = (x); \
36 if ((status) != NO_ERROR) { \
37 return status; \
38 } \
39 }
40
Nick Deakin6bd90432022-11-20 16:26:37 -050041// The current JPEGR version that we encode to
42static const uint32_t kJpegrVersion = 1;
43
Nick Deakinf6bca5a2022-11-04 10:43:43 -040044// Map is quarter res / sixteenth size
45static const size_t kMapDimensionScaleFactor = 4;
46
Nick Deakin6bd90432022-11-20 16:26:37 -050047// TODO: fill in st2086 metadata
48static const st2086_metadata kSt2086Metadata = {
49 {0.0f, 0.0f},
50 {0.0f, 0.0f},
51 {0.0f, 0.0f},
52 {0.0f, 0.0f},
53 0,
54 1.0f,
55};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040056
Dichen Zhang72fd2b12022-11-01 06:11:50 +000057/*
58 * Helper function used for generating XMP metadata.
59 *
60 * @param prefix The prefix part of the name.
61 * @param suffix The suffix part of the name.
62 * @return A name of the form "prefix:suffix".
63 */
64string Name(const string &prefix, const string &suffix) {
65 std::stringstream ss;
66 ss << prefix << ":" << suffix;
67 return ss.str();
68}
69
Dichen Zhanga8766262022-11-07 23:48:24 +000070/*
71 * Helper function used for writing data to destination.
72 *
73 * @param destination destination of the data to be written.
74 * @param source source of data being written.
75 * @param length length of the data to be written.
76 * @param position cursor in desitination where the data is to be written.
77 * @return status of succeed or error code.
78 */
79status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
80 if (position + length > destination->length) {
81 return ERROR_JPEGR_BUFFER_TOO_SMALL;
82 }
83
84 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
85 position += length;
86 return NO_ERROR;
87}
88
Dichen Zhang6947d532022-10-22 02:16:21 +000089status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
90 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -050091 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -040092 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +000093 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +000094 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +000095 if (uncompressed_p010_image == nullptr
96 || uncompressed_yuv_420_image == nullptr
97 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +000098 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +000099 }
100
Dichen Zhangffa34012022-11-03 23:21:13 +0000101 if (quality < 0 || quality > 100) {
102 return ERROR_JPEGR_INVALID_INPUT_TYPE;
103 }
104
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400105 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
106 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
107 return ERROR_JPEGR_RESOLUTION_MISMATCH;
108 }
109
Nick Deakin6bd90432022-11-20 16:26:37 -0500110 jpegr_metadata metadata;
111 metadata.version = kJpegrVersion;
112 metadata.transferFunction = hdr_tf;
113 if (hdr_tf == JPEGR_TF_PQ) {
114 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
115 }
116
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400117 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000118 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500119 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400120 std::unique_ptr<uint8_t[]> map_data;
121 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
122
123 jpegr_compressed_struct compressed_map;
124 std::unique_ptr<uint8_t[]> compressed_map_data =
125 std::make_unique<uint8_t[]>(map.width * map.height);
126 compressed_map.data = compressed_map_data.get();
127 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
128
129 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500130 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400131 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
132 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000133 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400134 return ERROR_JPEGR_ENCODE_ERROR;
135 }
136 jpegr_compressed_struct jpeg;
137 jpeg.data = jpeg_encoder.getCompressedImagePtr();
138 jpeg.length = jpeg_encoder.getCompressedImageSize();
139
Nick Deakin6bd90432022-11-20 16:26:37 -0500140 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400141
Dichen Zhang6947d532022-10-22 02:16:21 +0000142 return NO_ERROR;
143}
144
145status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
146 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400147 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500148 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000149 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000150 if (uncompressed_p010_image == nullptr
151 || uncompressed_yuv_420_image == nullptr
152 || compressed_jpeg_image == nullptr
153 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000154 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000155 }
156
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400157 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
158 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
159 return ERROR_JPEGR_RESOLUTION_MISMATCH;
160 }
161
Nick Deakin6bd90432022-11-20 16:26:37 -0500162 jpegr_metadata metadata;
163 metadata.version = kJpegrVersion;
164 metadata.transferFunction = hdr_tf;
165 if (hdr_tf == JPEGR_TF_PQ) {
166 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
167 }
168
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400169 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000170 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500171 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400172 std::unique_ptr<uint8_t[]> map_data;
173 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
174
175 jpegr_compressed_struct compressed_map;
176 std::unique_ptr<uint8_t[]> compressed_map_data =
177 std::make_unique<uint8_t[]>(map.width * map.height);
178 compressed_map.data = compressed_map_data.get();
179 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
180
Nick Deakin6bd90432022-11-20 16:26:37 -0500181 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400182
Dichen Zhang6947d532022-10-22 02:16:21 +0000183 return NO_ERROR;
184}
185
186status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400187 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500188 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000189 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000190 if (uncompressed_p010_image == nullptr
191 || compressed_jpeg_image == nullptr
192 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000193 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000194 }
195
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400196 JpegDecoder jpeg_decoder;
197 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
198 return ERROR_JPEGR_DECODE_ERROR;
199 }
200 jpegr_uncompressed_struct uncompressed_yuv_420_image;
201 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
202 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
203 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500204 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400205
206 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
207 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
208 return ERROR_JPEGR_RESOLUTION_MISMATCH;
209 }
210
Nick Deakin6bd90432022-11-20 16:26:37 -0500211 jpegr_metadata metadata;
212 metadata.version = kJpegrVersion;
213 metadata.transferFunction = hdr_tf;
214 if (hdr_tf == JPEGR_TF_PQ) {
215 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
216 }
217
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400218 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000219 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500220 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400221 std::unique_ptr<uint8_t[]> map_data;
222 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
223
224 jpegr_compressed_struct compressed_map;
225 std::unique_ptr<uint8_t[]> compressed_map_data =
226 std::make_unique<uint8_t[]>(map.width * map.height);
227 compressed_map.data = compressed_map_data.get();
228 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
229
Nick Deakin6bd90432022-11-20 16:26:37 -0500230 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400231
Dichen Zhang6947d532022-10-22 02:16:21 +0000232 return NO_ERROR;
233}
234
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400235status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000236 jr_uncompressed_ptr dest,
237 jr_exif_ptr /* exif */,
238 bool /* request_sdr */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000239 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000240 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000241 }
242
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400243 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500244 jpegr_metadata metadata;
245 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map, &metadata));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400246
247 jpegr_uncompressed_struct map;
248 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
249
250 JpegDecoder jpeg_decoder;
251 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
252 return ERROR_JPEGR_DECODE_ERROR;
253 }
254
255 jpegr_uncompressed_struct uncompressed_yuv_420_image;
256 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
257 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
258 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
259
Nick Deakin6bd90432022-11-20 16:26:37 -0500260 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400261
Dichen Zhang6947d532022-10-22 02:16:21 +0000262 return NO_ERROR;
263}
264
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400265status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
266 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700267 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000268 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700269 }
270
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400271 JpegDecoder jpeg_decoder;
272 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
273 compressed_recovery_map->length)) {
274 return ERROR_JPEGR_DECODE_ERROR;
275 }
276
277 dest->data = jpeg_decoder.getDecompressedImagePtr();
278 dest->width = jpeg_decoder.getDecompressedImageWidth();
279 dest->height = jpeg_decoder.getDecompressedImageHeight();
280
Dichen Zhang6947d532022-10-22 02:16:21 +0000281 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700282}
283
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400284status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
285 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700286 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000287 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700288 }
289
Nick Deakin6bd90432022-11-20 16:26:37 -0500290 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400291 JpegEncoder jpeg_encoder;
292 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data, uncompressed_recovery_map->width,
293 uncompressed_recovery_map->height, 85, nullptr, 0,
294 true /* isSingleChannel */)) {
295 return ERROR_JPEGR_ENCODE_ERROR;
296 }
297
298 if (dest->length < jpeg_encoder.getCompressedImageSize()) {
299 return ERROR_JPEGR_BUFFER_TOO_SMALL;
300 }
301
302 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
303 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500304 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400305
Dichen Zhang6947d532022-10-22 02:16:21 +0000306 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700307}
308
Dichen Zhang6947d532022-10-22 02:16:21 +0000309status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
310 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500311 jr_metadata_ptr metadata,
312 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700313 if (uncompressed_yuv_420_image == nullptr
314 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500315 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700316 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000317 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700318 }
319
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400320 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
321 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
322 return ERROR_JPEGR_RESOLUTION_MISMATCH;
323 }
324
Nick Deakin6bd90432022-11-20 16:26:37 -0500325 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
326 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
327 return ERROR_JPEGR_INVALID_COLORGAMUT;
328 }
329
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400330 size_t image_width = uncompressed_yuv_420_image->width;
331 size_t image_height = uncompressed_yuv_420_image->height;
332 size_t map_width = image_width / kMapDimensionScaleFactor;
333 size_t map_height = image_height / kMapDimensionScaleFactor;
334
335 dest->width = map_width;
336 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500337 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400338 dest->data = new uint8_t[map_width * map_height];
339 std::unique_ptr<uint8_t[]> map_data;
340 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
341
Nick Deakin6bd90432022-11-20 16:26:37 -0500342 ColorTransformFn hdrInvOetf = nullptr;
343 switch (metadata->transferFunction) {
344 case JPEGR_TF_HLG:
345 hdrInvOetf = hlgInvOetf;
346 break;
347 case JPEGR_TF_PQ:
348 hdrInvOetf = pqInvOetf;
349 break;
350 }
351
352 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
353 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
354
355 ColorCalculationFn luminanceFn = nullptr;
356 switch (uncompressed_yuv_420_image->colorGamut) {
357 case JPEGR_COLORGAMUT_BT709:
358 luminanceFn = srgbLuminance;
359 break;
360 case JPEGR_COLORGAMUT_P3:
361 luminanceFn = p3Luminance;
362 break;
363 case JPEGR_COLORGAMUT_BT2100:
364 luminanceFn = bt2100Luminance;
365 break;
366 case JPEGR_COLORGAMUT_UNSPECIFIED:
367 // Should be impossible to hit after input validation.
368 return ERROR_JPEGR_INVALID_COLORGAMUT;
369 }
370
Nick Deakin594a4ca2022-11-16 20:57:42 -0500371 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500372 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400373 for (size_t y = 0; y < image_height; ++y) {
374 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500375 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
376 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500377 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
378 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
379 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakin594a4ca2022-11-16 20:57:42 -0500380
Nick Deakin6bd90432022-11-20 16:26:37 -0500381 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500382 if (hdr_y_nits > hdr_y_nits_max) {
383 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400384 }
385 }
386 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500387 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400388
Nick Deakin6bd90432022-11-20 16:26:37 -0500389 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
390 if (metadata->transferFunction == JPEGR_TF_PQ) {
391 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
392 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
393 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400394
395 for (size_t y = 0; y < map_height; ++y) {
396 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500397 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
398 kMapDimensionScaleFactor, x, y);
399 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
400 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500401 float sdr_y_nits = luminanceFn(sdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400402
Nick Deakin594a4ca2022-11-16 20:57:42 -0500403 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
404 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500405 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
406 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
407 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400408
409 size_t pixel_idx = x + y * map_width;
410 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500411 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400412 }
413 }
414
415 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000416 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700417}
418
Dichen Zhang6947d532022-10-22 02:16:21 +0000419status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
420 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500421 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000422 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700423 if (uncompressed_yuv_420_image == nullptr
424 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500425 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700426 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000427 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700428 }
429
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400430 size_t width = uncompressed_yuv_420_image->width;
431 size_t height = uncompressed_yuv_420_image->height;
432
433 dest->width = width;
434 dest->height = height;
435 size_t pixel_count = width * height;
436
Nick Deakin6bd90432022-11-20 16:26:37 -0500437 ColorTransformFn hdrOetf = nullptr;
438 switch (metadata->transferFunction) {
439 case JPEGR_TF_HLG:
440 hdrOetf = hlgOetf;
441 break;
442 case JPEGR_TF_PQ:
443 hdrOetf = pqOetf;
444 break;
445 }
446
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400447 for (size_t y = 0; y < height; ++y) {
448 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500449 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
450 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
451 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400452
Nick Deakin6bd90432022-11-20 16:26:37 -0500453 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400454 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500455 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400456
Nick Deakin6bd90432022-11-20 16:26:37 -0500457 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
458 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400459
Nick Deakin6bd90432022-11-20 16:26:37 -0500460 size_t pixel_idx = x + y * width;
461 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400462 }
463 }
464
Dichen Zhang6947d532022-10-22 02:16:21 +0000465 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700466}
467
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400468status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500469 jr_compressed_ptr dest,
470 jr_metadata_ptr metadata) {
471 if (compressed_jpegr_image == nullptr || dest == nullptr || metadata == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000472 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700473 }
474
475 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000476 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700477}
478
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400479status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
480 jr_compressed_ptr compressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500481 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400482 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000483 if (compressed_jpeg_image == nullptr
484 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500485 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000486 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000487 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700488 }
489
Nick Deakin6bd90432022-11-20 16:26:37 -0500490 string xmp = generateXmp(compressed_recovery_map->length, *metadata);
Dichen Zhanga8766262022-11-07 23:48:24 +0000491 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
492
493 // 2 bytes: APP1 sign (ff e1)
494 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
495 // x bytes: length of xmp packet
496 int length = 2 + nameSpace.size() + xmp.size();
497 uint8_t lengthH = ((length >> 8) & 0xff);
498 uint8_t lengthL = (length & 0xff);
499
500 int pos = 0;
501
502 // JPEG/R structure:
503 // SOI (ff d8)
504 // APP1 (ff e1)
505 // 2 bytes of length (2 + 29 + length of xmp packet)
506 // name space ("http://ns.adobe.com/xap/1.0/\0")
507 // xmp
508 // primary image (without the first two bytes, the SOI sign)
509 // secondary image (the recovery map)
510 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
511 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
512 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
513 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
514 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
515 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
516 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
517 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
518 JPEGR_CHECK(Write(dest,
519 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
520 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
521 dest->length = pos;
522
Dichen Zhang6947d532022-10-22 02:16:21 +0000523 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700524}
525
Nick Deakin6bd90432022-11-20 16:26:37 -0500526string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000527 const string kContainerPrefix = "GContainer";
528 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
529 const string kItemPrefix = "Item";
530 const string kRecoveryMap = "RecoveryMap";
531 const string kDirectory = "Directory";
532 const string kImageJpeg = "image/jpeg";
533 const string kItem = "Item";
534 const string kLength = "Length";
535 const string kMime = "Mime";
536 const string kPrimary = "Primary";
537 const string kSemantic = "Semantic";
538 const string kVersion = "Version";
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000539
540 const string kConDir = Name(kContainerPrefix, kDirectory);
541 const string kContainerItem = Name(kContainerPrefix, kItem);
542 const string kItemLength = Name(kItemPrefix, kLength);
543 const string kItemMime = Name(kItemPrefix, kMime);
544 const string kItemSemantic = Name(kItemPrefix, kSemantic);
545
546 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
547 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
548
549 std::stringstream ss;
550 photos_editing_formats::image_io::XmlWriter writer(ss);
551 writer.StartWritingElement("x:xmpmeta");
552 writer.WriteXmlns("x", "adobe:ns:meta/");
553 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
554 writer.StartWritingElement("rdf:RDF");
555 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
556 writer.StartWritingElement("rdf:Description");
557 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin6bd90432022-11-20 16:26:37 -0500558 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
559 writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
560 metadata.rangeScalingFactor);
561 // TODO: determine structure for hdr10 metadata
562 // TODO: write rest of metadata
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000563 writer.StartWritingElements(kConDirSeq);
564 size_t item_depth = writer.StartWritingElements(kLiItem);
565 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
566 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
567 writer.FinishWritingElementsToDepth(item_depth);
568 writer.StartWritingElements(kLiItem);
569 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
570 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
571 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
572 writer.FinishWriting();
573
574 return ss.str();
575}
576
Dichen Zhang85b37562022-10-11 11:08:28 -0700577} // namespace android::recoverymap