blob: 200ec99055d35ba72aadfcd3af1961452ad62161 [file] [log] [blame]
Dichen Zhang85b37562022-10-11 11:08:28 -07001/*
2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <jpegrecoverymap/recoverymap.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040018#include <jpegrecoverymap/jpegencoder.h>
19#include <jpegrecoverymap/jpegdecoder.h>
20#include <jpegrecoverymap/recoverymapmath.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000021#include <jpegrecoverymap/recoverymaputils.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040022
Dichen Zhanga8766262022-11-07 23:48:24 +000023#include <image_io/jpeg/jpeg_marker.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000024#include <image_io/jpeg/jpeg_info.h>
25#include <image_io/jpeg/jpeg_scanner.h>
26#include <image_io/jpeg/jpeg_info_builder.h>
27#include <image_io/base/data_segment_data_source.h>
28#include <utils/Log.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040029
30#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000031#include <sstream>
32#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000033#include <cmath>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000034
35using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000036using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070037
38namespace android::recoverymap {
39
Nick Deakinf6bca5a2022-11-04 10:43:43 -040040#define JPEGR_CHECK(x) \
41 { \
42 status_t status = (x); \
43 if ((status) != NO_ERROR) { \
44 return status; \
45 } \
46 }
47
Nick Deakin6bd90432022-11-20 16:26:37 -050048// The current JPEGR version that we encode to
49static const uint32_t kJpegrVersion = 1;
50
Nick Deakinf6bca5a2022-11-04 10:43:43 -040051// Map is quarter res / sixteenth size
52static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000053// JPEG compress quality (0 ~ 100) for recovery map
54static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040055
Nick Deakin6bd90432022-11-20 16:26:37 -050056// TODO: fill in st2086 metadata
57static const st2086_metadata kSt2086Metadata = {
58 {0.0f, 0.0f},
59 {0.0f, 0.0f},
60 {0.0f, 0.0f},
61 {0.0f, 0.0f},
62 0,
63 1.0f,
64};
Nick Deakinf6bca5a2022-11-04 10:43:43 -040065
Dichen Zhang72fd2b12022-11-01 06:11:50 +000066/*
Dichen Zhanga8766262022-11-07 23:48:24 +000067 * Helper function used for writing data to destination.
68 *
69 * @param destination destination of the data to be written.
70 * @param source source of data being written.
71 * @param length length of the data to be written.
72 * @param position cursor in desitination where the data is to be written.
73 * @return status of succeed or error code.
74 */
75status_t Write(jr_compressed_ptr destination, const void* source, size_t length, int &position) {
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000076 if (position + length > destination->maxLength) {
Dichen Zhanga8766262022-11-07 23:48:24 +000077 return ERROR_JPEGR_BUFFER_TOO_SMALL;
78 }
79
80 memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length);
81 position += length;
82 return NO_ERROR;
83}
84
Dichen Zhang636f5242022-12-07 20:25:44 +000085/* Encode API-0 */
86status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
87 jpegr_transfer_function hdr_tf,
88 jr_compressed_ptr dest,
89 int quality,
90 jr_exif_ptr /* exif */) {
91 if (uncompressed_p010_image == nullptr || dest == nullptr) {
92 return ERROR_JPEGR_INVALID_NULL_PTR;
93 }
94
95 if (quality < 0 || quality > 100) {
96 return ERROR_JPEGR_INVALID_INPUT_TYPE;
97 }
98
99 jpegr_metadata metadata;
100 metadata.version = kJpegrVersion;
101 metadata.transferFunction = hdr_tf;
102 if (hdr_tf == JPEGR_TF_PQ) {
103 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
104 }
105
106 jpegr_uncompressed_struct uncompressed_yuv_420_image;
107 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
108
109 jpegr_uncompressed_struct map;
110 JPEGR_CHECK(generateRecoveryMap(
111 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
112 std::unique_ptr<uint8_t[]> map_data;
113 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
114
115 jpegr_compressed_struct compressed_map;
116 compressed_map.maxLength = map.width * map.height;
117 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
118 compressed_map.data = compressed_map_data.get();
119 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
120
121 JpegEncoder jpeg_encoder;
122 // TODO: determine ICC data based on color gamut information
123 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
124 uncompressed_yuv_420_image.width,
125 uncompressed_yuv_420_image.height, quality, nullptr, 0)) {
126 return ERROR_JPEGR_ENCODE_ERROR;
127 }
128 jpegr_compressed_struct jpeg;
129 jpeg.data = jpeg_encoder.getCompressedImagePtr();
130 jpeg.length = jpeg_encoder.getCompressedImageSize();
131
132 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
133
134 return NO_ERROR;
135}
136
137/* Encode API-1 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000138status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
139 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500140 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400141 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000142 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000143 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000144 if (uncompressed_p010_image == nullptr
145 || uncompressed_yuv_420_image == nullptr
146 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000147 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000148 }
149
Dichen Zhangffa34012022-11-03 23:21:13 +0000150 if (quality < 0 || quality > 100) {
151 return ERROR_JPEGR_INVALID_INPUT_TYPE;
152 }
153
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400154 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
155 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
156 return ERROR_JPEGR_RESOLUTION_MISMATCH;
157 }
158
Nick Deakin6bd90432022-11-20 16:26:37 -0500159 jpegr_metadata metadata;
160 metadata.version = kJpegrVersion;
161 metadata.transferFunction = hdr_tf;
162 if (hdr_tf == JPEGR_TF_PQ) {
163 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
164 }
165
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400166 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000167 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500168 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400169 std::unique_ptr<uint8_t[]> map_data;
170 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
171
172 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000173 compressed_map.maxLength = map.width * map.height;
174 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400175 compressed_map.data = compressed_map_data.get();
176 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
177
178 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500179 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400180 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
181 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000182 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400183 return ERROR_JPEGR_ENCODE_ERROR;
184 }
185 jpegr_compressed_struct jpeg;
186 jpeg.data = jpeg_encoder.getCompressedImagePtr();
187 jpeg.length = jpeg_encoder.getCompressedImageSize();
188
Nick Deakin6bd90432022-11-20 16:26:37 -0500189 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400190
Dichen Zhang6947d532022-10-22 02:16:21 +0000191 return NO_ERROR;
192}
193
Dichen Zhang636f5242022-12-07 20:25:44 +0000194/* Encode API-2 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000195status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
196 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400197 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500198 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000199 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000200 if (uncompressed_p010_image == nullptr
201 || uncompressed_yuv_420_image == nullptr
202 || compressed_jpeg_image == nullptr
203 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000204 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000205 }
206
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400207 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
208 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
209 return ERROR_JPEGR_RESOLUTION_MISMATCH;
210 }
211
Nick Deakin6bd90432022-11-20 16:26:37 -0500212 jpegr_metadata metadata;
213 metadata.version = kJpegrVersion;
214 metadata.transferFunction = hdr_tf;
215 if (hdr_tf == JPEGR_TF_PQ) {
216 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
217 }
218
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400219 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000220 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500221 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400222 std::unique_ptr<uint8_t[]> map_data;
223 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
224
225 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000226 compressed_map.maxLength = map.width * map.height;
227 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400228 compressed_map.data = compressed_map_data.get();
229 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
230
Nick Deakin6bd90432022-11-20 16:26:37 -0500231 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400232
Dichen Zhang6947d532022-10-22 02:16:21 +0000233 return NO_ERROR;
234}
235
Dichen Zhang636f5242022-12-07 20:25:44 +0000236/* Encode API-3 */
Dichen Zhang6947d532022-10-22 02:16:21 +0000237status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400238 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500239 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000240 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000241 if (uncompressed_p010_image == nullptr
242 || compressed_jpeg_image == nullptr
243 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000244 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000245 }
246
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400247 JpegDecoder jpeg_decoder;
248 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
249 return ERROR_JPEGR_DECODE_ERROR;
250 }
251 jpegr_uncompressed_struct uncompressed_yuv_420_image;
252 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
253 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
254 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500255 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400256
257 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
258 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
259 return ERROR_JPEGR_RESOLUTION_MISMATCH;
260 }
261
Nick Deakin6bd90432022-11-20 16:26:37 -0500262 jpegr_metadata metadata;
263 metadata.version = kJpegrVersion;
264 metadata.transferFunction = hdr_tf;
265 if (hdr_tf == JPEGR_TF_PQ) {
266 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
267 }
268
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400269 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000270 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500271 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400272 std::unique_ptr<uint8_t[]> map_data;
273 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
274
275 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000276 compressed_map.maxLength = map.width * map.height;
277 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400278 compressed_map.data = compressed_map_data.get();
279 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
280
Nick Deakin6bd90432022-11-20 16:26:37 -0500281 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400282
Dichen Zhang6947d532022-10-22 02:16:21 +0000283 return NO_ERROR;
284}
285
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000286status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
287 jr_info_ptr jpegr_info) {
288 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
289 return ERROR_JPEGR_INVALID_NULL_PTR;
290 }
291
292 jpegr_compressed_struct primary_image, recovery_map;
293 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
294 &primary_image, &recovery_map));
295
296 JpegDecoder jpeg_decoder;
297 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
298 &jpegr_info->width, &jpegr_info->height,
299 jpegr_info->iccData, jpegr_info->exifData)) {
300 return ERROR_JPEGR_DECODE_ERROR;
301 }
302
303 return NO_ERROR;
304}
305
Dichen Zhang636f5242022-12-07 20:25:44 +0000306/* Decode API */
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400307status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000308 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000309 jr_exif_ptr exif,
310 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000311 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000312 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000313 }
314
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000315 // TODO: fill EXIF data
316 (void) exif;
317
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400318 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500319 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000320 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400321
322 jpegr_uncompressed_struct map;
323 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
324
325 JpegDecoder jpeg_decoder;
326 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
327 return ERROR_JPEGR_DECODE_ERROR;
328 }
329
330 jpegr_uncompressed_struct uncompressed_yuv_420_image;
331 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
332 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
333 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
334
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000335 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
336 jpeg_decoder.getXMPSize(), &metadata)) {
337 return ERROR_JPEGR_DECODE_ERROR;
338 }
339
340 if (request_sdr) {
341 memcpy(dest->data, uncompressed_yuv_420_image.data,
342 uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
343 dest->width = uncompressed_yuv_420_image.width;
344 dest->height = uncompressed_yuv_420_image.height;
345 } else {
346 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
347 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400348
Dichen Zhang6947d532022-10-22 02:16:21 +0000349 return NO_ERROR;
350}
351
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400352status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
353 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700354 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000355 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700356 }
357
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400358 JpegDecoder jpeg_decoder;
359 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
360 compressed_recovery_map->length)) {
361 return ERROR_JPEGR_DECODE_ERROR;
362 }
363
364 dest->data = jpeg_decoder.getDecompressedImagePtr();
365 dest->width = jpeg_decoder.getDecompressedImageWidth();
366 dest->height = jpeg_decoder.getDecompressedImageHeight();
367
Dichen Zhang6947d532022-10-22 02:16:21 +0000368 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700369}
370
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400371status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
372 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700373 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000374 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700375 }
376
Nick Deakin6bd90432022-11-20 16:26:37 -0500377 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400378 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000379 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
380 uncompressed_recovery_map->width,
381 uncompressed_recovery_map->height,
382 kMapCompressQuality,
383 nullptr,
384 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400385 true /* isSingleChannel */)) {
386 return ERROR_JPEGR_ENCODE_ERROR;
387 }
388
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000389 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400390 return ERROR_JPEGR_BUFFER_TOO_SMALL;
391 }
392
393 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
394 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500395 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400396
Dichen Zhang6947d532022-10-22 02:16:21 +0000397 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700398}
399
Dichen Zhang6947d532022-10-22 02:16:21 +0000400status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
401 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500402 jr_metadata_ptr metadata,
403 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700404 if (uncompressed_yuv_420_image == nullptr
405 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500406 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700407 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000408 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700409 }
410
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400411 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
412 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
413 return ERROR_JPEGR_RESOLUTION_MISMATCH;
414 }
415
Nick Deakin6bd90432022-11-20 16:26:37 -0500416 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
417 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
418 return ERROR_JPEGR_INVALID_COLORGAMUT;
419 }
420
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400421 size_t image_width = uncompressed_yuv_420_image->width;
422 size_t image_height = uncompressed_yuv_420_image->height;
423 size_t map_width = image_width / kMapDimensionScaleFactor;
424 size_t map_height = image_height / kMapDimensionScaleFactor;
425
426 dest->width = map_width;
427 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500428 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400429 dest->data = new uint8_t[map_width * map_height];
430 std::unique_ptr<uint8_t[]> map_data;
431 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
432
Nick Deakin6bd90432022-11-20 16:26:37 -0500433 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500434 float hdr_white_nits = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500435 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000436 case JPEGR_TF_LINEAR:
437 hdrInvOetf = identityConversion;
438 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500439 case JPEGR_TF_HLG:
440 hdrInvOetf = hlgInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500441 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500442 break;
443 case JPEGR_TF_PQ:
444 hdrInvOetf = pqInvOetf;
Nick Deakin65f492a2022-11-29 22:47:40 -0500445 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500446 break;
447 }
448
449 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
450 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
451
452 ColorCalculationFn luminanceFn = nullptr;
453 switch (uncompressed_yuv_420_image->colorGamut) {
454 case JPEGR_COLORGAMUT_BT709:
455 luminanceFn = srgbLuminance;
456 break;
457 case JPEGR_COLORGAMUT_P3:
458 luminanceFn = p3Luminance;
459 break;
460 case JPEGR_COLORGAMUT_BT2100:
461 luminanceFn = bt2100Luminance;
462 break;
463 case JPEGR_COLORGAMUT_UNSPECIFIED:
464 // Should be impossible to hit after input validation.
465 return ERROR_JPEGR_INVALID_COLORGAMUT;
466 }
467
Nick Deakin594a4ca2022-11-16 20:57:42 -0500468 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500469 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400470 for (size_t y = 0; y < image_height; ++y) {
471 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500472 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
473 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500474 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
475 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500476 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500477
Nick Deakin6bd90432022-11-20 16:26:37 -0500478 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500479 if (hdr_y_nits > hdr_y_nits_max) {
480 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400481 }
482 }
483 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500484 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400485
Nick Deakin6bd90432022-11-20 16:26:37 -0500486 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
487 if (metadata->transferFunction == JPEGR_TF_PQ) {
488 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
489 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
490 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400491
492 for (size_t y = 0; y < map_height; ++y) {
493 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500494 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
495 kMapDimensionScaleFactor, x, y);
496 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
497 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin65f492a2022-11-29 22:47:40 -0500498 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400499
Nick Deakin594a4ca2022-11-16 20:57:42 -0500500 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
501 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500502 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
503 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
Nick Deakin65f492a2022-11-29 22:47:40 -0500504 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400505
506 size_t pixel_idx = x + y * map_width;
507 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500508 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400509 }
510 }
511
512 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000513 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700514}
515
Dichen Zhang6947d532022-10-22 02:16:21 +0000516status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
517 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500518 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000519 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700520 if (uncompressed_yuv_420_image == nullptr
521 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500522 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700523 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000524 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700525 }
526
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400527 size_t width = uncompressed_yuv_420_image->width;
528 size_t height = uncompressed_yuv_420_image->height;
529
530 dest->width = width;
531 dest->height = height;
532 size_t pixel_count = width * height;
533
Nick Deakin6bd90432022-11-20 16:26:37 -0500534 ColorTransformFn hdrOetf = nullptr;
535 switch (metadata->transferFunction) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000536 case JPEGR_TF_LINEAR:
537 hdrOetf = identityConversion;
538 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500539 case JPEGR_TF_HLG:
540 hdrOetf = hlgOetf;
541 break;
542 case JPEGR_TF_PQ:
543 hdrOetf = pqOetf;
544 break;
545 }
546
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400547 for (size_t y = 0; y < height; ++y) {
548 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500549 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
550 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
551 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400552
Nick Deakin6bd90432022-11-20 16:26:37 -0500553 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400554 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500555 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400556
Nick Deakin38125332022-12-12 15:48:24 -0500557 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->rangeScalingFactor);
Nick Deakin6bd90432022-11-20 16:26:37 -0500558 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400559
Nick Deakin6bd90432022-11-20 16:26:37 -0500560 size_t pixel_idx = x + y * width;
561 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400562 }
563 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000564 return NO_ERROR;
565}
566
567status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
568 jr_compressed_ptr primary_image,
569 jr_compressed_ptr recovery_map) {
570 if (compressed_jpegr_image == nullptr) {
571 return ERROR_JPEGR_INVALID_NULL_PTR;
572 }
573
574 MessageHandler msg_handler;
575 std::shared_ptr<DataSegment> seg =
576 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
577 static_cast<const uint8_t*>(compressed_jpegr_image->data),
578 DataSegment::BufferDispositionPolicy::kDontDelete);
579 DataSegmentDataSource data_source(seg);
580 JpegInfoBuilder jpeg_info_builder;
581 jpeg_info_builder.SetImageLimit(2);
582 JpegScanner jpeg_scanner(&msg_handler);
583 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
584 data_source.Reset();
585
586 if (jpeg_scanner.HasError()) {
587 return ERROR_JPEGR_INVALID_INPUT_TYPE;
588 }
589
590 const auto& jpeg_info = jpeg_info_builder.GetInfo();
591 const auto& image_ranges = jpeg_info.GetImageRanges();
592 if (image_ranges.empty()) {
593 return ERROR_JPEGR_INVALID_INPUT_TYPE;
594 }
595
596 if (image_ranges.size() != 2) {
597 // Must be 2 JPEG Images
598 return ERROR_JPEGR_INVALID_INPUT_TYPE;
599 }
600
601 if (primary_image != nullptr) {
602 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
603 image_ranges[0].GetBegin();
604 primary_image->length = image_ranges[0].GetLength();
605 }
606
607 if (recovery_map != nullptr) {
608 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
609 image_ranges[1].GetBegin();
610 recovery_map->length = image_ranges[1].GetLength();
611 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400612
Dichen Zhang6947d532022-10-22 02:16:21 +0000613 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700614}
615
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000616
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400617status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000618 jr_compressed_ptr dest) {
619 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000620 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700621 }
622
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000623 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700624}
625
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400626status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
627 jr_compressed_ptr compressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500628 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400629 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000630 if (compressed_jpeg_image == nullptr
631 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500632 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000633 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000634 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700635 }
636
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000637 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
638 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
639 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +0000640
641 // 2 bytes: APP1 sign (ff e1)
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000642 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
Dichen Zhanga8766262022-11-07 23:48:24 +0000643 // x bytes: length of xmp packet
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000644
645 const int length = 3 + nameSpaceLength + xmp.size();
646 const uint8_t lengthH = ((length >> 8) & 0xff);
647 const uint8_t lengthL = (length & 0xff);
Dichen Zhanga8766262022-11-07 23:48:24 +0000648
649 int pos = 0;
650
651 // JPEG/R structure:
652 // SOI (ff d8)
653 // APP1 (ff e1)
654 // 2 bytes of length (2 + 29 + length of xmp packet)
655 // name space ("http://ns.adobe.com/xap/1.0/\0")
656 // xmp
657 // primary image (without the first two bytes, the SOI sign)
658 // secondary image (the recovery map)
659 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
660 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
661 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
662 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
663 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
664 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhangfbb687a2022-11-22 00:57:00 +0000665 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhanga8766262022-11-07 23:48:24 +0000666 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
667 JPEGR_CHECK(Write(dest,
668 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
669 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
670 dest->length = pos;
671
Dichen Zhang6947d532022-10-22 02:16:21 +0000672 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700673}
674
Dichen Zhang636f5242022-12-07 20:25:44 +0000675status_t RecoveryMap::toneMap(jr_uncompressed_ptr uncompressed_p010_image,
676 jr_uncompressed_ptr dest) {
677 if (uncompressed_p010_image == nullptr || dest == nullptr) {
678 return ERROR_JPEGR_INVALID_NULL_PTR;
679 }
680
681 dest->width = uncompressed_p010_image->width;
682 dest->height = uncompressed_p010_image->height;
683 unique_ptr<uint8_t[]> dest_data = make_unique<uint8_t[]>(dest->width * dest->height * 3 / 2);
684 dest->data = dest_data.get();
685
686 // TODO: Tone map algorighm here.
687
688 return NO_ERROR;
689}
690
Dichen Zhang85b37562022-10-11 11:08:28 -0700691} // namespace android::recoverymap