blob: ad418396011e1fc007bf819e8eb8262cda231b84 [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;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000046// JPEG compress quality (0 ~ 100) for recovery map
47static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040048
Nick Deakin6bd90432022-11-20 16:26:37 -050049// TODO: fill in st2086 metadata
50static const st2086_metadata kSt2086Metadata = {
51 {0.0f, 0.0f},
52 {0.0f, 0.0f},
53 {0.0f, 0.0f},
54 {0.0f, 0.0f},
55 0,
56 1.0f,
57};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040058
Dichen Zhang72fd2b12022-11-01 06:11:50 +000059/*
60 * Helper function used for generating XMP metadata.
61 *
62 * @param prefix The prefix part of the name.
63 * @param suffix The suffix part of the name.
64 * @return A name of the form "prefix:suffix".
65 */
66string Name(const string &prefix, const string &suffix) {
67 std::stringstream ss;
68 ss << prefix << ":" << suffix;
69 return ss.str();
70}
71
Dichen Zhanga8766262022-11-07 23:48:24 +000072/*
73 * Helper function used for writing data to destination.
74 *
75 * @param destination destination of the data to be written.
76 * @param source source of data being written.
77 * @param length length of the data to be written.
78 * @param position cursor in desitination where the data is to be written.
79 * @return status of succeed or error code.
80 */
81status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000082 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000083 return ERROR_JPEGR_BUFFER_TOO_SMALL;
84 }
85
86 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
87 position += length;
88 return NO_ERROR;
89}
90
Dichen Zhang6947d532022-10-22 02:16:21 +000091status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
92 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -050093 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -040094 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +000095 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +000096 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +000097 if (uncompressed_p010_image == nullptr
98 || uncompressed_yuv_420_image == nullptr
99 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000100 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000101 }
102
Dichen Zhangffa34012022-11-03 23:21:13 +0000103 if (quality < 0 || quality > 100) {
104 return ERROR_JPEGR_INVALID_INPUT_TYPE;
105 }
106
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400107 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
108 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
109 return ERROR_JPEGR_RESOLUTION_MISMATCH;
110 }
111
Nick Deakin6bd90432022-11-20 16:26:37 -0500112 jpegr_metadata metadata;
113 metadata.version = kJpegrVersion;
114 metadata.transferFunction = hdr_tf;
115 if (hdr_tf == JPEGR_TF_PQ) {
116 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
117 }
118
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400119 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000120 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500121 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400122 std::unique_ptr<uint8_t[]> map_data;
123 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
124
125 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000126 compressed_map.maxLength = map.width * map.height;
127 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400128 compressed_map.data = compressed_map_data.get();
129 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
130
131 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500132 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400133 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
134 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000135 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400136 return ERROR_JPEGR_ENCODE_ERROR;
137 }
138 jpegr_compressed_struct jpeg;
139 jpeg.data = jpeg_encoder.getCompressedImagePtr();
140 jpeg.length = jpeg_encoder.getCompressedImageSize();
141
Nick Deakin6bd90432022-11-20 16:26:37 -0500142 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400143
Dichen Zhang6947d532022-10-22 02:16:21 +0000144 return NO_ERROR;
145}
146
147status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
148 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400149 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500150 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000151 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000152 if (uncompressed_p010_image == nullptr
153 || uncompressed_yuv_420_image == nullptr
154 || compressed_jpeg_image == nullptr
155 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000156 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000157 }
158
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400159 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
160 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
161 return ERROR_JPEGR_RESOLUTION_MISMATCH;
162 }
163
Nick Deakin6bd90432022-11-20 16:26:37 -0500164 jpegr_metadata metadata;
165 metadata.version = kJpegrVersion;
166 metadata.transferFunction = hdr_tf;
167 if (hdr_tf == JPEGR_TF_PQ) {
168 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
169 }
170
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400171 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000172 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500173 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400174 std::unique_ptr<uint8_t[]> map_data;
175 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
176
177 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000178 compressed_map.maxLength = map.width * map.height;
179 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400180 compressed_map.data = compressed_map_data.get();
181 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
182
Nick Deakin6bd90432022-11-20 16:26:37 -0500183 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400184
Dichen Zhang6947d532022-10-22 02:16:21 +0000185 return NO_ERROR;
186}
187
188status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400189 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500190 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000191 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000192 if (uncompressed_p010_image == nullptr
193 || compressed_jpeg_image == nullptr
194 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000195 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000196 }
197
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400198 JpegDecoder jpeg_decoder;
199 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
200 return ERROR_JPEGR_DECODE_ERROR;
201 }
202 jpegr_uncompressed_struct uncompressed_yuv_420_image;
203 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
204 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
205 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500206 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400207
208 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
209 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
210 return ERROR_JPEGR_RESOLUTION_MISMATCH;
211 }
212
Nick Deakin6bd90432022-11-20 16:26:37 -0500213 jpegr_metadata metadata;
214 metadata.version = kJpegrVersion;
215 metadata.transferFunction = hdr_tf;
216 if (hdr_tf == JPEGR_TF_PQ) {
217 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
218 }
219
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400220 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000221 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500222 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400223 std::unique_ptr<uint8_t[]> map_data;
224 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
225
226 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000227 compressed_map.maxLength = map.width * map.height;
228 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400229 compressed_map.data = compressed_map_data.get();
230 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
231
Nick Deakin6bd90432022-11-20 16:26:37 -0500232 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400233
Dichen Zhang6947d532022-10-22 02:16:21 +0000234 return NO_ERROR;
235}
236
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400237status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000238 jr_uncompressed_ptr dest,
239 jr_exif_ptr /* exif */,
240 bool /* request_sdr */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000241 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000242 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000243 }
244
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400245 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500246 jpegr_metadata metadata;
247 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map, &metadata));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400248
249 jpegr_uncompressed_struct map;
250 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
251
252 JpegDecoder jpeg_decoder;
253 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
254 return ERROR_JPEGR_DECODE_ERROR;
255 }
256
257 jpegr_uncompressed_struct uncompressed_yuv_420_image;
258 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
259 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
260 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
261
Nick Deakin6bd90432022-11-20 16:26:37 -0500262 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400263
Dichen Zhang6947d532022-10-22 02:16:21 +0000264 return NO_ERROR;
265}
266
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400267status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
268 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700269 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000270 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700271 }
272
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400273 JpegDecoder jpeg_decoder;
274 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
275 compressed_recovery_map->length)) {
276 return ERROR_JPEGR_DECODE_ERROR;
277 }
278
279 dest->data = jpeg_decoder.getDecompressedImagePtr();
280 dest->width = jpeg_decoder.getDecompressedImageWidth();
281 dest->height = jpeg_decoder.getDecompressedImageHeight();
282
Dichen Zhang6947d532022-10-22 02:16:21 +0000283 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700284}
285
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400286status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
287 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700288 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000289 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700290 }
291
Nick Deakin6bd90432022-11-20 16:26:37 -0500292 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400293 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000294 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
295 uncompressed_recovery_map->width,
296 uncompressed_recovery_map->height,
297 kMapCompressQuality,
298 nullptr,
299 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400300 true /* isSingleChannel */)) {
301 return ERROR_JPEGR_ENCODE_ERROR;
302 }
303
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000304 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400305 return ERROR_JPEGR_BUFFER_TOO_SMALL;
306 }
307
308 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
309 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500310 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400311
Dichen Zhang6947d532022-10-22 02:16:21 +0000312 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700313}
314
Dichen Zhang6947d532022-10-22 02:16:21 +0000315status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
316 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500317 jr_metadata_ptr metadata,
318 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700319 if (uncompressed_yuv_420_image == nullptr
320 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500321 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700322 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000323 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700324 }
325
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400326 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
327 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
328 return ERROR_JPEGR_RESOLUTION_MISMATCH;
329 }
330
Nick Deakin6bd90432022-11-20 16:26:37 -0500331 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
332 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
333 return ERROR_JPEGR_INVALID_COLORGAMUT;
334 }
335
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400336 size_t image_width = uncompressed_yuv_420_image->width;
337 size_t image_height = uncompressed_yuv_420_image->height;
338 size_t map_width = image_width / kMapDimensionScaleFactor;
339 size_t map_height = image_height / kMapDimensionScaleFactor;
340
341 dest->width = map_width;
342 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500343 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400344 dest->data = new uint8_t[map_width * map_height];
345 std::unique_ptr<uint8_t[]> map_data;
346 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
347
Nick Deakin6bd90432022-11-20 16:26:37 -0500348 ColorTransformFn hdrInvOetf = nullptr;
349 switch (metadata->transferFunction) {
350 case JPEGR_TF_HLG:
351 hdrInvOetf = hlgInvOetf;
352 break;
353 case JPEGR_TF_PQ:
354 hdrInvOetf = pqInvOetf;
355 break;
356 }
357
358 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
359 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
360
361 ColorCalculationFn luminanceFn = nullptr;
362 switch (uncompressed_yuv_420_image->colorGamut) {
363 case JPEGR_COLORGAMUT_BT709:
364 luminanceFn = srgbLuminance;
365 break;
366 case JPEGR_COLORGAMUT_P3:
367 luminanceFn = p3Luminance;
368 break;
369 case JPEGR_COLORGAMUT_BT2100:
370 luminanceFn = bt2100Luminance;
371 break;
372 case JPEGR_COLORGAMUT_UNSPECIFIED:
373 // Should be impossible to hit after input validation.
374 return ERROR_JPEGR_INVALID_COLORGAMUT;
375 }
376
Nick Deakin594a4ca2022-11-16 20:57:42 -0500377 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500378 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400379 for (size_t y = 0; y < image_height; ++y) {
380 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500381 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
382 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500383 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
384 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
385 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakin594a4ca2022-11-16 20:57:42 -0500386
Nick Deakin6bd90432022-11-20 16:26:37 -0500387 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500388 if (hdr_y_nits > hdr_y_nits_max) {
389 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400390 }
391 }
392 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500393 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400394
Nick Deakin6bd90432022-11-20 16:26:37 -0500395 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
396 if (metadata->transferFunction == JPEGR_TF_PQ) {
397 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
398 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
399 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400400
401 for (size_t y = 0; y < map_height; ++y) {
402 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500403 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
404 kMapDimensionScaleFactor, x, y);
405 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
406 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500407 float sdr_y_nits = luminanceFn(sdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400408
Nick Deakin594a4ca2022-11-16 20:57:42 -0500409 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
410 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500411 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
412 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
413 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400414
415 size_t pixel_idx = x + y * map_width;
416 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500417 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400418 }
419 }
420
421 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000422 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700423}
424
Dichen Zhang6947d532022-10-22 02:16:21 +0000425status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
426 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500427 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000428 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700429 if (uncompressed_yuv_420_image == nullptr
430 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500431 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700432 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000433 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700434 }
435
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400436 size_t width = uncompressed_yuv_420_image->width;
437 size_t height = uncompressed_yuv_420_image->height;
438
439 dest->width = width;
440 dest->height = height;
441 size_t pixel_count = width * height;
442
Nick Deakin6bd90432022-11-20 16:26:37 -0500443 ColorTransformFn hdrOetf = nullptr;
444 switch (metadata->transferFunction) {
445 case JPEGR_TF_HLG:
446 hdrOetf = hlgOetf;
447 break;
448 case JPEGR_TF_PQ:
449 hdrOetf = pqOetf;
450 break;
451 }
452
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400453 for (size_t y = 0; y < height; ++y) {
454 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500455 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
456 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
457 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400458
Nick Deakin6bd90432022-11-20 16:26:37 -0500459 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400460 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500461 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400462
Nick Deakin6bd90432022-11-20 16:26:37 -0500463 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
464 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400465
Nick Deakin6bd90432022-11-20 16:26:37 -0500466 size_t pixel_idx = x + y * width;
467 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400468 }
469 }
470
Dichen Zhang6947d532022-10-22 02:16:21 +0000471 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700472}
473
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400474status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500475 jr_compressed_ptr dest,
476 jr_metadata_ptr metadata) {
477 if (compressed_jpegr_image == nullptr || dest == nullptr || metadata == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000478 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700479 }
480
481 // TBD
Dichen Zhang6947d532022-10-22 02:16:21 +0000482 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700483}
484
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400485status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
486 jr_compressed_ptr compressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500487 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400488 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000489 if (compressed_jpeg_image == nullptr
490 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500491 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000492 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000493 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700494 }
495
Nick Deakin6bd90432022-11-20 16:26:37 -0500496 string xmp = generateXmp(compressed_recovery_map->length, *metadata);
Dichen Zhanga8766262022-11-07 23:48:24 +0000497 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
498
499 // 2 bytes: APP1 sign (ff e1)
500 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
501 // x bytes: length of xmp packet
502 int length = 2 + nameSpace.size() + xmp.size();
503 uint8_t lengthH = ((length >> 8) & 0xff);
504 uint8_t lengthL = (length & 0xff);
505
506 int pos = 0;
507
508 // JPEG/R structure:
509 // SOI (ff d8)
510 // APP1 (ff e1)
511 // 2 bytes of length (2 + 29 + length of xmp packet)
512 // name space ("http://ns.adobe.com/xap/1.0/\0")
513 // xmp
514 // primary image (without the first two bytes, the SOI sign)
515 // secondary image (the recovery map)
516 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
517 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
518 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
519 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
520 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
521 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
522 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
523 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
524 JPEGR_CHECK(Write(dest,
525 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
526 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
527 dest->length = pos;
528
Dichen Zhang6947d532022-10-22 02:16:21 +0000529 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700530}
531
Nick Deakin6bd90432022-11-20 16:26:37 -0500532string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000533 const string kContainerPrefix = "GContainer";
534 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
535 const string kItemPrefix = "Item";
536 const string kRecoveryMap = "RecoveryMap";
537 const string kDirectory = "Directory";
538 const string kImageJpeg = "image/jpeg";
539 const string kItem = "Item";
540 const string kLength = "Length";
541 const string kMime = "Mime";
542 const string kPrimary = "Primary";
543 const string kSemantic = "Semantic";
544 const string kVersion = "Version";
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000545
546 const string kConDir = Name(kContainerPrefix, kDirectory);
547 const string kContainerItem = Name(kContainerPrefix, kItem);
548 const string kItemLength = Name(kItemPrefix, kLength);
549 const string kItemMime = Name(kItemPrefix, kMime);
550 const string kItemSemantic = Name(kItemPrefix, kSemantic);
551
552 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
553 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
554
555 std::stringstream ss;
556 photos_editing_formats::image_io::XmlWriter writer(ss);
557 writer.StartWritingElement("x:xmpmeta");
558 writer.WriteXmlns("x", "adobe:ns:meta/");
559 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
560 writer.StartWritingElement("rdf:RDF");
561 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
562 writer.StartWritingElement("rdf:Description");
563 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin6bd90432022-11-20 16:26:37 -0500564 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
565 writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
566 metadata.rangeScalingFactor);
567 // TODO: determine structure for hdr10 metadata
568 // TODO: write rest of metadata
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000569 writer.StartWritingElements(kConDirSeq);
570 size_t item_depth = writer.StartWritingElements(kLiItem);
571 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
572 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
573 writer.FinishWritingElementsToDepth(item_depth);
574 writer.StartWritingElements(kLiItem);
575 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
576 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
577 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
578 writer.FinishWriting();
579
580 return ss.str();
581}
582
Dichen Zhang85b37562022-10-11 11:08:28 -0700583} // namespace android::recoverymap