blob: 74bb51266f1c66c00eb5bd4413f62b4bb54af7df [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>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040024#include <image_io/xml/xml_writer.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000025#include <image_io/jpeg/jpeg_info.h>
26#include <image_io/jpeg/jpeg_scanner.h>
27#include <image_io/jpeg/jpeg_info_builder.h>
28#include <image_io/base/data_segment_data_source.h>
29#include <utils/Log.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040030
31#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000032#include <sstream>
33#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000034#include <cmath>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000035
36using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000037using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070038
39namespace android::recoverymap {
40
Nick Deakinf6bca5a2022-11-04 10:43:43 -040041#define JPEGR_CHECK(x) \
42 { \
43 status_t status = (x); \
44 if ((status) != NO_ERROR) { \
45 return status; \
46 } \
47 }
48
Nick Deakin6bd90432022-11-20 16:26:37 -050049// The current JPEGR version that we encode to
50static const uint32_t kJpegrVersion = 1;
51
Nick Deakinf6bca5a2022-11-04 10:43:43 -040052// Map is quarter res / sixteenth size
53static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000054// JPEG compress quality (0 ~ 100) for recovery map
55static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040056
Nick Deakin6bd90432022-11-20 16:26:37 -050057// TODO: fill in st2086 metadata
58static const st2086_metadata kSt2086Metadata = {
59 {0.0f, 0.0f},
60 {0.0f, 0.0f},
61 {0.0f, 0.0f},
62 {0.0f, 0.0f},
63 0,
64 1.0f,
65};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040066
Dichen Zhang72fd2b12022-11-01 06:11:50 +000067/*
68 * Helper function used for generating XMP metadata.
69 *
70 * @param prefix The prefix part of the name.
71 * @param suffix The suffix part of the name.
72 * @return A name of the form "prefix:suffix".
73 */
74string Name(const string &prefix, const string &suffix) {
75 std::stringstream ss;
76 ss << prefix << ":" << suffix;
77 return ss.str();
78}
79
Dichen Zhanga8766262022-11-07 23:48:24 +000080/*
81 * Helper function used for writing data to destination.
82 *
83 * @param destination destination of the data to be written.
84 * @param source source of data being written.
85 * @param length length of the data to be written.
86 * @param position cursor in desitination where the data is to be written.
87 * @return status of succeed or error code.
88 */
89status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000090 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000091 return ERROR_JPEGR_BUFFER_TOO_SMALL;
92 }
93
94 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
95 position += length;
96 return NO_ERROR;
97}
98
Dichen Zhang636f5242022-12-07 20:25:44 +000099/* Encode API-0 */
100status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
101 jpegr_transfer_function hdr_tf,
102 jr_compressed_ptr dest,
103 int quality,
104 jr_exif_ptr /* exif */) {
105 if (uncompressed_p010_image == nullptr || dest == nullptr) {
106 return ERROR_JPEGR_INVALID_NULL_PTR;
107 }
108
109 if (quality < 0 || quality > 100) {
110 return ERROR_JPEGR_INVALID_INPUT_TYPE;
111 }
112
113 jpegr_metadata metadata;
114 metadata.version = kJpegrVersion;
115 metadata.transferFunction = hdr_tf;
116 if (hdr_tf == JPEGR_TF_PQ) {
117 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
118 }
119
120 jpegr_uncompressed_struct uncompressed_yuv_420_image;
121 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
122
123 jpegr_uncompressed_struct map;
124 JPEGR_CHECK(generateRecoveryMap(
125 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
126 std::unique_ptr<uint8_t[]> map_data;
127 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
128
129 jpegr_compressed_struct compressed_map;
130 compressed_map.maxLength = map.width * map.height;
131 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
132 compressed_map.data = compressed_map_data.get();
133 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
134
135 JpegEncoder jpeg_encoder;
136 // TODO: determine ICC data based on color gamut information
137 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
138 uncompressed_yuv_420_image.width,
139 uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
140 return ERROR_JPEGR_ENCODE_ERROR;
141 }
142 jpegr_compressed_struct jpeg;
143 jpeg.data = jpeg_encoder.getCompressedImagePtr();
144 jpeg.length = jpeg_encoder.getCompressedImageSize();
145
146 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
147
148 return NO_ERROR;
149}
150
151/* Encode API-1 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000152status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
153 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500154 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400155 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000156 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000157 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000158 if (uncompressed_p010_image == nullptr
159 || uncompressed_yuv_420_image == nullptr
160 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000161 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000162 }
163
Dichen Zhangffa34012022-11-03 23:21:13 +0000164 if (quality < 0 || quality > 100) {
165 return ERROR_JPEGR_INVALID_INPUT_TYPE;
166 }
167
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400168 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
169 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
170 return ERROR_JPEGR_RESOLUTION_MISMATCH;
171 }
172
Nick Deakin6bd90432022-11-20 16:26:37 -0500173 jpegr_metadata metadata;
174 metadata.version = kJpegrVersion;
175 metadata.transferFunction = hdr_tf;
176 if (hdr_tf == JPEGR_TF_PQ) {
177 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
178 }
179
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400180 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000181 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500182 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400183 std::unique_ptr<uint8_t[]> map_data;
184 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
185
186 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000187 compressed_map.maxLength = map.width * map.height;
188 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400189 compressed_map.data = compressed_map_data.get();
190 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
191
192 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500193 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400194 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
195 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000196 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400197 return ERROR_JPEGR_ENCODE_ERROR;
198 }
199 jpegr_compressed_struct jpeg;
200 jpeg.data = jpeg_encoder.getCompressedImagePtr();
201 jpeg.length = jpeg_encoder.getCompressedImageSize();
202
Nick Deakin6bd90432022-11-20 16:26:37 -0500203 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400204
Dichen Zhang6947d532022-10-22 02:16:21 +0000205 return NO_ERROR;
206}
207
Dichen Zhang636f5242022-12-07 20:25:44 +0000208/* Encode API-2 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000209status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
210 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400211 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500212 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000213 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000214 if (uncompressed_p010_image == nullptr
215 || uncompressed_yuv_420_image == nullptr
216 || compressed_jpeg_image == nullptr
217 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000218 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000219 }
220
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400221 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
222 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
223 return ERROR_JPEGR_RESOLUTION_MISMATCH;
224 }
225
Nick Deakin6bd90432022-11-20 16:26:37 -0500226 jpegr_metadata metadata;
227 metadata.version = kJpegrVersion;
228 metadata.transferFunction = hdr_tf;
229 if (hdr_tf == JPEGR_TF_PQ) {
230 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
231 }
232
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400233 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000234 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500235 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400236 std::unique_ptr<uint8_t[]> map_data;
237 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
238
239 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000240 compressed_map.maxLength = map.width * map.height;
241 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400242 compressed_map.data = compressed_map_data.get();
243 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
244
Nick Deakin6bd90432022-11-20 16:26:37 -0500245 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400246
Dichen Zhang6947d532022-10-22 02:16:21 +0000247 return NO_ERROR;
248}
249
Dichen Zhang636f5242022-12-07 20:25:44 +0000250/* Encode API-3 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000251status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400252 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500253 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000254 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000255 if (uncompressed_p010_image == nullptr
256 || compressed_jpeg_image == nullptr
257 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000258 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000259 }
260
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400261 JpegDecoder jpeg_decoder;
262 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
263 return ERROR_JPEGR_DECODE_ERROR;
264 }
265 jpegr_uncompressed_struct uncompressed_yuv_420_image;
266 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
267 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
268 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500269 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400270
271 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
272 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
273 return ERROR_JPEGR_RESOLUTION_MISMATCH;
274 }
275
Nick Deakin6bd90432022-11-20 16:26:37 -0500276 jpegr_metadata metadata;
277 metadata.version = kJpegrVersion;
278 metadata.transferFunction = hdr_tf;
279 if (hdr_tf == JPEGR_TF_PQ) {
280 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
281 }
282
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400283 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000284 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500285 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400286 std::unique_ptr<uint8_t[]> map_data;
287 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
288
289 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000290 compressed_map.maxLength = map.width * map.height;
291 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400292 compressed_map.data = compressed_map_data.get();
293 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
294
Nick Deakin6bd90432022-11-20 16:26:37 -0500295 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400296
Dichen Zhang6947d532022-10-22 02:16:21 +0000297 return NO_ERROR;
298}
299
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000300status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
301 jr_info_ptr jpegr_info) {
302 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
303 return ERROR_JPEGR_INVALID_NULL_PTR;
304 }
305
306 jpegr_compressed_struct primary_image, recovery_map;
307 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
308 &primary_image, &recovery_map));
309
310 JpegDecoder jpeg_decoder;
311 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
312 &jpegr_info->width, &jpegr_info->height,
313 jpegr_info->iccData, jpegr_info->exifData)) {
314 return ERROR_JPEGR_DECODE_ERROR;
315 }
316
317 return NO_ERROR;
318}
319
Dichen Zhang636f5242022-12-07 20:25:44 +0000320/* Decode API */
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400321status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000322 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000323 jr_exif_ptr exif,
324 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000325 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000326 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000327 }
328
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000329 // TODO: fill EXIF data
330 (void) exif;
331
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400332 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500333 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000334 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400335
336 jpegr_uncompressed_struct map;
337 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
338
339 JpegDecoder jpeg_decoder;
340 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
341 return ERROR_JPEGR_DECODE_ERROR;
342 }
343
344 jpegr_uncompressed_struct uncompressed_yuv_420_image;
345 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
346 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
347 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
348
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000349 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
350 jpeg_decoder.getXMPSize(), &metadata)) {
351 return ERROR_JPEGR_DECODE_ERROR;
352 }
353
354 if (request_sdr) {
355 memcpy(dest->data, uncompressed_yuv_420_image.data,
356 uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
357 dest->width = uncompressed_yuv_420_image.width;
358 dest->height = uncompressed_yuv_420_image.height;
359 } else {
360 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
361 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400362
Dichen Zhang6947d532022-10-22 02:16:21 +0000363 return NO_ERROR;
364}
365
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400366status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
367 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700368 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000369 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700370 }
371
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400372 JpegDecoder jpeg_decoder;
373 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
374 compressed_recovery_map->length)) {
375 return ERROR_JPEGR_DECODE_ERROR;
376 }
377
378 dest->data = jpeg_decoder.getDecompressedImagePtr();
379 dest->width = jpeg_decoder.getDecompressedImageWidth();
380 dest->height = jpeg_decoder.getDecompressedImageHeight();
381
Dichen Zhang6947d532022-10-22 02:16:21 +0000382 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700383}
384
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400385status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
386 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700387 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000388 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700389 }
390
Nick Deakin6bd90432022-11-20 16:26:37 -0500391 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400392 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000393 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
394 uncompressed_recovery_map->width,
395 uncompressed_recovery_map->height,
396 kMapCompressQuality,
397 nullptr,
398 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400399 true /* isSingleChannel */)) {
400 return ERROR_JPEGR_ENCODE_ERROR;
401 }
402
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000403 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400404 return ERROR_JPEGR_BUFFER_TOO_SMALL;
405 }
406
407 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
408 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500409 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400410
Dichen Zhang6947d532022-10-22 02:16:21 +0000411 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700412}
413
Dichen Zhang6947d532022-10-22 02:16:21 +0000414status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
415 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500416 jr_metadata_ptr metadata,
417 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700418 if (uncompressed_yuv_420_image == nullptr
419 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500420 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700421 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000422 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700423 }
424
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400425 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
426 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
427 return ERROR_JPEGR_RESOLUTION_MISMATCH;
428 }
429
Nick Deakin6bd90432022-11-20 16:26:37 -0500430 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
431 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
432 return ERROR_JPEGR_INVALID_COLORGAMUT;
433 }
434
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400435 size_t image_width = uncompressed_yuv_420_image->width;
436 size_t image_height = uncompressed_yuv_420_image->height;
437 size_t map_width = image_width / kMapDimensionScaleFactor;
438 size_t map_height = image_height / kMapDimensionScaleFactor;
439
440 dest->width = map_width;
441 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500442 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400443 dest->data = new uint8_t[map_width * map_height];
444 std::unique_ptr<uint8_t[]> map_data;
445 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
446
Nick Deakin6bd90432022-11-20 16:26:37 -0500447 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500448 float hdr_white_nits = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500449 switch (metadata->transferFunction) {
450 case JPEGR_TF_HLG:
451 hdrInvOetf = hlgInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500452 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500453 break;
454 case JPEGR_TF_PQ:
455 hdrInvOetf = pqInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500456 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500457 break;
458 }
459
460 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
461 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
462
463 ColorCalculationFn luminanceFn = nullptr;
464 switch (uncompressed_yuv_420_image->colorGamut) {
465 case JPEGR_COLORGAMUT_BT709:
466 luminanceFn = srgbLuminance;
467 break;
468 case JPEGR_COLORGAMUT_P3:
469 luminanceFn = p3Luminance;
470 break;
471 case JPEGR_COLORGAMUT_BT2100:
472 luminanceFn = bt2100Luminance;
473 break;
474 case JPEGR_COLORGAMUT_UNSPECIFIED:
475 // Should be impossible to hit after input validation.
476 return ERROR_JPEGR_INVALID_COLORGAMUT;
477 }
478
Nick Deakin594a4ca2022-11-16 20:57:42 -0500479 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500480 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400481 for (size_t y = 0; y < image_height; ++y) {
482 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500483 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
484 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500485 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
486 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500487 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500488
Nick Deakin6bd90432022-11-20 16:26:37 -0500489 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500490 if (hdr_y_nits > hdr_y_nits_max) {
491 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400492 }
493 }
494 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500495 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400496
Nick Deakin6bd90432022-11-20 16:26:37 -0500497 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
498 if (metadata->transferFunction == JPEGR_TF_PQ) {
499 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
500 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
501 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400502
503 for (size_t y = 0; y < map_height; ++y) {
504 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500505 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
506 kMapDimensionScaleFactor, x, y);
507 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
508 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin65f492a2022-11-29 22:47:40 -0500509 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400510
Nick Deakin594a4ca2022-11-16 20:57:42 -0500511 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
512 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500513 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
514 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500515 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400516
517 size_t pixel_idx = x + y * map_width;
518 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500519 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400520 }
521 }
522
523 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000524 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700525}
526
Dichen Zhang6947d532022-10-22 02:16:21 +0000527status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
528 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500529 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000530 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700531 if (uncompressed_yuv_420_image == nullptr
532 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500533 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700534 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000535 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700536 }
537
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400538 size_t width = uncompressed_yuv_420_image->width;
539 size_t height = uncompressed_yuv_420_image->height;
540
541 dest->width = width;
542 dest->height = height;
543 size_t pixel_count = width * height;
544
Nick Deakin6bd90432022-11-20 16:26:37 -0500545 ColorTransformFn hdrOetf = nullptr;
546 switch (metadata->transferFunction) {
547 case JPEGR_TF_HLG:
548 hdrOetf = hlgOetf;
549 break;
550 case JPEGR_TF_PQ:
551 hdrOetf = pqOetf;
552 break;
553 }
554
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400555 for (size_t y = 0; y < height; ++y) {
556 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500557 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
558 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
559 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400560
Nick Deakin6bd90432022-11-20 16:26:37 -0500561 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400562 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500563 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400564
Nick Deakin38125332022-12-12 15:48:24 -0500565 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
Nick Deakin6bd90432022-11-20 16:26:37 -0500566 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400567
Nick Deakin6bd90432022-11-20 16:26:37 -0500568 size_t pixel_idx = x + y * width;
569 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400570 }
571 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000572 return NO_ERROR;
573}
574
575status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
576 jr_compressed_ptr primary_image,
577 jr_compressed_ptr recovery_map) {
578 if (compressed_jpegr_image == nullptr) {
579 return ERROR_JPEGR_INVALID_NULL_PTR;
580 }
581
582 MessageHandler msg_handler;
583 std::shared_ptr<DataSegment> seg =
584 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
585 static_cast<const uint8_t*>(compressed_jpegr_image->data),
586 DataSegment::BufferDispositionPolicy::kDontDelete);
587 DataSegmentDataSource data_source(seg);
588 JpegInfoBuilder jpeg_info_builder;
589 jpeg_info_builder.SetImageLimit(2);
590 JpegScanner jpeg_scanner(&msg_handler);
591 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
592 data_source.Reset();
593
594 if (jpeg_scanner.HasError()) {
595 return ERROR_JPEGR_INVALID_INPUT_TYPE;
596 }
597
598 const auto& jpeg_info = jpeg_info_builder.GetInfo();
599 const auto& image_ranges = jpeg_info.GetImageRanges();
600 if (image_ranges.empty()) {
601 return ERROR_JPEGR_INVALID_INPUT_TYPE;
602 }
603
604 if (image_ranges.size() != 2) {
605 // Must be 2 JPEG Images
606 return ERROR_JPEGR_INVALID_INPUT_TYPE;
607 }
608
609 if (primary_image != nullptr) {
610 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
611 image_ranges[0].GetBegin();
612 primary_image->length = image_ranges[0].GetLength();
613 }
614
615 if (recovery_map != nullptr) {
616 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
617 image_ranges[1].GetBegin();
618 recovery_map->length = image_ranges[1].GetLength();
619 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400620
Dichen Zhang6947d532022-10-22 02:16:21 +0000621 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700622}
623
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000624
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400625status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000626 jr_compressed_ptr dest) {
627 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000628 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700629 }
630
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000631 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700632}
633
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400634status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
635 jr_compressed_ptr compressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500636 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400637 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000638 if (compressed_jpeg_image == nullptr
639 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500640 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000641 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000642 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700643 }
644
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000645 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
646 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
647 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +0000648
649 // 2 bytes: APP1 sign (ff e1)
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000650 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
Dichen Zhanga8766262022-11-07 23:48:24 +0000651 // x bytes: length of xmp packet
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000652
653 const int length = 3 + nameSpaceLength + xmp.size();
654 const uint8_t lengthH = ((length >> 8) & 0xff);
655 const uint8_t lengthL = (length & 0xff);
Dichen Zhanga8766262022-11-07 23:48:24 +0000656
657 int pos = 0;
658
659 // JPEG/R structure:
660 // SOI (ff d8)
661 // APP1 (ff e1)
662 // 2 bytes of length (2 + 29 + length of xmp packet)
663 // name space ("http://ns.adobe.com/xap/1.0/\0")
664 // xmp
665 // primary image (without the first two bytes, the SOI sign)
666 // secondary image (the recovery map)
667 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
668 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
669 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
670 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
671 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
672 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000673 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhanga8766262022-11-07 23:48:24 +0000674 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
675 JPEGR_CHECK(Write(dest,
676 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
677 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
678 dest->length = pos;
679
Dichen Zhang6947d532022-10-22 02:16:21 +0000680 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700681}
682
Nick Deakin6bd90432022-11-20 16:26:37 -0500683string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000684 const string kContainerPrefix = "GContainer";
685 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
686 const string kItemPrefix = "Item";
687 const string kRecoveryMap = "RecoveryMap";
688 const string kDirectory = "Directory";
689 const string kImageJpeg = "image/jpeg";
690 const string kItem = "Item";
691 const string kLength = "Length";
692 const string kMime = "Mime";
693 const string kPrimary = "Primary";
694 const string kSemantic = "Semantic";
695 const string kVersion = "Version";
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000696
697 const string kConDir = Name(kContainerPrefix, kDirectory);
698 const string kContainerItem = Name(kContainerPrefix, kItem);
699 const string kItemLength = Name(kItemPrefix, kLength);
700 const string kItemMime = Name(kItemPrefix, kMime);
701 const string kItemSemantic = Name(kItemPrefix, kSemantic);
702
703 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
704 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
705
706 std::stringstream ss;
707 photos_editing_formats::image_io::XmlWriter writer(ss);
708 writer.StartWritingElement("x:xmpmeta");
709 writer.WriteXmlns("x", "adobe:ns:meta/");
710 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
711 writer.StartWritingElement("rdf:RDF");
712 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
713 writer.StartWritingElement("rdf:Description");
714 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin6bd90432022-11-20 16:26:37 -0500715 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
716 writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
717 metadata.rangeScalingFactor);
718 // TODO: determine structure for hdr10 metadata
719 // TODO: write rest of metadata
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000720 writer.StartWritingElements(kConDirSeq);
721 size_t item_depth = writer.StartWritingElements(kLiItem);
722 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
723 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
724 writer.FinishWritingElementsToDepth(item_depth);
725 writer.StartWritingElements(kLiItem);
726 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
727 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
728 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
729 writer.FinishWriting();
730
731 return ss.str();
732}
733
Dichen Zhang636f5242022-12-07 20:25:44 +0000734status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
735 jr_uncompressed_ptr dest) {
736 if (uncompressed_p010_image == nullptr || dest == nullptr) {
737 return ERROR_JPEGR_INVALID_NULL_PTR;
738 }
739
740 dest->width = uncompressed_p010_image->width;
741 dest->height = uncompressed_p010_image->height;
742 unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
743 dest->data = dest_data.get();
744
745 // TODO: Tone map algorighm here.
746
747 return NO_ERROR;
748}
749
Dichen Zhang85b37562022-10-11 11:08:28 -0700750} // namespace android::recoverymap