blob: b685294dcc5771a83d4b7130bec420e2ec4d48d8 [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 Zhang6947d532022-10-22 02:16:21 +000099status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
100 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500101 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400102 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000103 int quality,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000104 jr_exif_ptr /* exif */) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000105 if (uncompressed_p010_image == nullptr
106 || uncompressed_yuv_420_image == nullptr
107 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000108 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000109 }
110
Dichen Zhangffa34012022-11-03 23:21:13 +0000111 if (quality < 0 || quality > 100) {
112 return ERROR_JPEGR_INVALID_INPUT_TYPE;
113 }
114
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400115 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
116 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
117 return ERROR_JPEGR_RESOLUTION_MISMATCH;
118 }
119
Nick Deakin6bd90432022-11-20 16:26:37 -0500120 jpegr_metadata metadata;
121 metadata.version = kJpegrVersion;
122 metadata.transferFunction = hdr_tf;
123 if (hdr_tf == JPEGR_TF_PQ) {
124 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
125 }
126
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400127 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000128 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500129 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400130 std::unique_ptr<uint8_t[]> map_data;
131 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
132
133 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000134 compressed_map.maxLength = map.width * map.height;
135 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400136 compressed_map.data = compressed_map_data.get();
137 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
138
139 JpegEncoder jpeg_encoder;
Nick Deakin6bd90432022-11-20 16:26:37 -0500140 // TODO: determine ICC data based on color gamut information
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400141 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
142 uncompressed_yuv_420_image->width,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000143 uncompressed_yuv_420_image->height, quality, nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400144 return ERROR_JPEGR_ENCODE_ERROR;
145 }
146 jpegr_compressed_struct jpeg;
147 jpeg.data = jpeg_encoder.getCompressedImagePtr();
148 jpeg.length = jpeg_encoder.getCompressedImageSize();
149
Nick Deakin6bd90432022-11-20 16:26:37 -0500150 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400151
Dichen Zhang6947d532022-10-22 02:16:21 +0000152 return NO_ERROR;
153}
154
155status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
156 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400157 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500158 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000159 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000160 if (uncompressed_p010_image == nullptr
161 || uncompressed_yuv_420_image == nullptr
162 || compressed_jpeg_image == nullptr
163 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000164 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000165 }
166
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400167 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
168 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
169 return ERROR_JPEGR_RESOLUTION_MISMATCH;
170 }
171
Nick Deakin6bd90432022-11-20 16:26:37 -0500172 jpegr_metadata metadata;
173 metadata.version = kJpegrVersion;
174 metadata.transferFunction = hdr_tf;
175 if (hdr_tf == JPEGR_TF_PQ) {
176 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
177 }
178
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400179 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000180 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500181 uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400182 std::unique_ptr<uint8_t[]> map_data;
183 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
184
185 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000186 compressed_map.maxLength = map.width * map.height;
187 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400188 compressed_map.data = compressed_map_data.get();
189 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
190
Nick Deakin6bd90432022-11-20 16:26:37 -0500191 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400192
Dichen Zhang6947d532022-10-22 02:16:21 +0000193 return NO_ERROR;
194}
195
196status_t RecoveryMap::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_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 || compressed_jpeg_image == nullptr
202 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000203 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000204 }
205
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400206 JpegDecoder jpeg_decoder;
207 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
208 return ERROR_JPEGR_DECODE_ERROR;
209 }
210 jpegr_uncompressed_struct uncompressed_yuv_420_image;
211 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
212 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
213 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500214 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400215
216 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
217 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
218 return ERROR_JPEGR_RESOLUTION_MISMATCH;
219 }
220
Nick Deakin6bd90432022-11-20 16:26:37 -0500221 jpegr_metadata metadata;
222 metadata.version = kJpegrVersion;
223 metadata.transferFunction = hdr_tf;
224 if (hdr_tf == JPEGR_TF_PQ) {
225 metadata.hdr10Metadata.st2086Metadata = kSt2086Metadata;
226 }
227
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400228 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000229 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin6bd90432022-11-20 16:26:37 -0500230 &uncompressed_yuv_420_image, uncompressed_p010_image, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400231 std::unique_ptr<uint8_t[]> map_data;
232 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
233
234 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000235 compressed_map.maxLength = map.width * map.height;
236 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400237 compressed_map.data = compressed_map_data.get();
238 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
239
Nick Deakin6bd90432022-11-20 16:26:37 -0500240 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400241
Dichen Zhang6947d532022-10-22 02:16:21 +0000242 return NO_ERROR;
243}
244
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000245status_t RecoveryMap::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
246 jr_info_ptr jpegr_info) {
247 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
248 return ERROR_JPEGR_INVALID_NULL_PTR;
249 }
250
251 jpegr_compressed_struct primary_image, recovery_map;
252 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
253 &primary_image, &recovery_map));
254
255 JpegDecoder jpeg_decoder;
256 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
257 &jpegr_info->width, &jpegr_info->height,
258 jpegr_info->iccData, jpegr_info->exifData)) {
259 return ERROR_JPEGR_DECODE_ERROR;
260 }
261
262 return NO_ERROR;
263}
264
265
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400266status_t RecoveryMap::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000267 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000268 jr_exif_ptr exif,
269 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000270 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000271 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000272 }
273
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000274 // TODO: fill EXIF data
275 (void) exif;
276
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400277 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500278 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000279 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400280
281 jpegr_uncompressed_struct map;
282 JPEGR_CHECK(decompressRecoveryMap(&compressed_map, &map));
283
284 JpegDecoder jpeg_decoder;
285 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
286 return ERROR_JPEGR_DECODE_ERROR;
287 }
288
289 jpegr_uncompressed_struct uncompressed_yuv_420_image;
290 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
291 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
292 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
293
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000294 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
295 jpeg_decoder.getXMPSize(), &metadata)) {
296 return ERROR_JPEGR_DECODE_ERROR;
297 }
298
299 if (request_sdr) {
300 memcpy(dest->data, uncompressed_yuv_420_image.data,
301 uncompressed_yuv_420_image.width*uncompressed_yuv_420_image.height *3 / 2);
302 dest->width = uncompressed_yuv_420_image.width;
303 dest->height = uncompressed_yuv_420_image.height;
304 } else {
305 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
306 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400307
Dichen Zhang6947d532022-10-22 02:16:21 +0000308 return NO_ERROR;
309}
310
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400311status_t RecoveryMap::decompressRecoveryMap(jr_compressed_ptr compressed_recovery_map,
312 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700313 if (compressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000314 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700315 }
316
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400317 JpegDecoder jpeg_decoder;
318 if (!jpeg_decoder.decompressImage(compressed_recovery_map->data,
319 compressed_recovery_map->length)) {
320 return ERROR_JPEGR_DECODE_ERROR;
321 }
322
323 dest->data = jpeg_decoder.getDecompressedImagePtr();
324 dest->width = jpeg_decoder.getDecompressedImageWidth();
325 dest->height = jpeg_decoder.getDecompressedImageHeight();
326
Dichen Zhang6947d532022-10-22 02:16:21 +0000327 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700328}
329
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400330status_t RecoveryMap::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
331 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700332 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000333 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700334 }
335
Nick Deakin6bd90432022-11-20 16:26:37 -0500336 // TODO: should we have ICC data for the map?
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400337 JpegEncoder jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000338 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
339 uncompressed_recovery_map->width,
340 uncompressed_recovery_map->height,
341 kMapCompressQuality,
342 nullptr,
343 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400344 true /* isSingleChannel */)) {
345 return ERROR_JPEGR_ENCODE_ERROR;
346 }
347
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000348 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400349 return ERROR_JPEGR_BUFFER_TOO_SMALL;
350 }
351
352 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
353 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500354 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400355
Dichen Zhang6947d532022-10-22 02:16:21 +0000356 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700357}
358
Dichen Zhang6947d532022-10-22 02:16:21 +0000359status_t RecoveryMap::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
360 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500361 jr_metadata_ptr metadata,
362 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700363 if (uncompressed_yuv_420_image == nullptr
364 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500365 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700366 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000367 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700368 }
369
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400370 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
371 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
372 return ERROR_JPEGR_RESOLUTION_MISMATCH;
373 }
374
Nick Deakin6bd90432022-11-20 16:26:37 -0500375 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
376 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
377 return ERROR_JPEGR_INVALID_COLORGAMUT;
378 }
379
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400380 size_t image_width = uncompressed_yuv_420_image->width;
381 size_t image_height = uncompressed_yuv_420_image->height;
382 size_t map_width = image_width / kMapDimensionScaleFactor;
383 size_t map_height = image_height / kMapDimensionScaleFactor;
384
385 dest->width = map_width;
386 dest->height = map_height;
Nick Deakin6bd90432022-11-20 16:26:37 -0500387 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400388 dest->data = new uint8_t[map_width * map_height];
389 std::unique_ptr<uint8_t[]> map_data;
390 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
391
Nick Deakin6bd90432022-11-20 16:26:37 -0500392 ColorTransformFn hdrInvOetf = nullptr;
393 switch (metadata->transferFunction) {
394 case JPEGR_TF_HLG:
395 hdrInvOetf = hlgInvOetf;
396 break;
397 case JPEGR_TF_PQ:
398 hdrInvOetf = pqInvOetf;
399 break;
400 }
401
402 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
403 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
404
405 ColorCalculationFn luminanceFn = nullptr;
406 switch (uncompressed_yuv_420_image->colorGamut) {
407 case JPEGR_COLORGAMUT_BT709:
408 luminanceFn = srgbLuminance;
409 break;
410 case JPEGR_COLORGAMUT_P3:
411 luminanceFn = p3Luminance;
412 break;
413 case JPEGR_COLORGAMUT_BT2100:
414 luminanceFn = bt2100Luminance;
415 break;
416 case JPEGR_COLORGAMUT_UNSPECIFIED:
417 // Should be impossible to hit after input validation.
418 return ERROR_JPEGR_INVALID_COLORGAMUT;
419 }
420
Nick Deakin594a4ca2022-11-16 20:57:42 -0500421 float hdr_y_nits_max = 0.0f;
Nick Deakin6bd90432022-11-20 16:26:37 -0500422 double hdr_y_nits_avg = 0.0f;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400423 for (size_t y = 0; y < image_height; ++y) {
424 for (size_t x = 0; x < image_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500425 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
426 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500427 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
428 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
429 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakin594a4ca2022-11-16 20:57:42 -0500430
Nick Deakin6bd90432022-11-20 16:26:37 -0500431 hdr_y_nits_avg += hdr_y_nits;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500432 if (hdr_y_nits > hdr_y_nits_max) {
433 hdr_y_nits_max = hdr_y_nits;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400434 }
435 }
436 }
Nick Deakin6bd90432022-11-20 16:26:37 -0500437 hdr_y_nits_avg /= image_width * image_height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400438
Nick Deakin6bd90432022-11-20 16:26:37 -0500439 metadata->rangeScalingFactor = hdr_y_nits_max / kSdrWhiteNits;
440 if (metadata->transferFunction == JPEGR_TF_PQ) {
441 metadata->hdr10Metadata.maxFALL = hdr_y_nits_avg;
442 metadata->hdr10Metadata.maxCLL = hdr_y_nits_max;
443 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400444
445 for (size_t y = 0; y < map_height; ++y) {
446 for (size_t x = 0; x < map_width; ++x) {
Nick Deakin594a4ca2022-11-16 20:57:42 -0500447 Color sdr_yuv_gamma = sampleYuv420(uncompressed_yuv_420_image,
448 kMapDimensionScaleFactor, x, y);
449 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
450 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500451 float sdr_y_nits = luminanceFn(sdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400452
Nick Deakin594a4ca2022-11-16 20:57:42 -0500453 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
454 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
Nick Deakin6bd90432022-11-20 16:26:37 -0500455 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
456 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
457 float hdr_y_nits = luminanceFn(hdr_rgb);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400458
459 size_t pixel_idx = x + y * map_width;
460 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakin6bd90432022-11-20 16:26:37 -0500461 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400462 }
463 }
464
465 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000466 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700467}
468
Dichen Zhang6947d532022-10-22 02:16:21 +0000469status_t RecoveryMap::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
470 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500471 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000472 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700473 if (uncompressed_yuv_420_image == nullptr
474 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500475 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700476 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000477 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700478 }
479
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400480 size_t width = uncompressed_yuv_420_image->width;
481 size_t height = uncompressed_yuv_420_image->height;
482
483 dest->width = width;
484 dest->height = height;
485 size_t pixel_count = width * height;
486
Nick Deakin6bd90432022-11-20 16:26:37 -0500487 ColorTransformFn hdrOetf = nullptr;
488 switch (metadata->transferFunction) {
489 case JPEGR_TF_HLG:
490 hdrOetf = hlgOetf;
491 break;
492 case JPEGR_TF_PQ:
493 hdrOetf = pqOetf;
494 break;
495 }
496
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400497 for (size_t y = 0; y < height; ++y) {
498 for (size_t x = 0; x < width; ++x) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500499 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
500 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
501 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400502
Nick Deakin6bd90432022-11-20 16:26:37 -0500503 // TODO: determine map scaling factor based on actual map dims
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400504 float recovery = sampleMap(uncompressed_recovery_map, kMapDimensionScaleFactor, x, y);
Nick Deakin6bd90432022-11-20 16:26:37 -0500505 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata->rangeScalingFactor);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400506
Nick Deakin6bd90432022-11-20 16:26:37 -0500507 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
508 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400509
Nick Deakin6bd90432022-11-20 16:26:37 -0500510 size_t pixel_idx = x + y * width;
511 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400512 }
513 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000514 return NO_ERROR;
515}
516
517status_t RecoveryMap::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
518 jr_compressed_ptr primary_image,
519 jr_compressed_ptr recovery_map) {
520 if (compressed_jpegr_image == nullptr) {
521 return ERROR_JPEGR_INVALID_NULL_PTR;
522 }
523
524 MessageHandler msg_handler;
525 std::shared_ptr<DataSegment> seg =
526 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
527 static_cast<const uint8_t*>(compressed_jpegr_image->data),
528 DataSegment::BufferDispositionPolicy::kDontDelete);
529 DataSegmentDataSource data_source(seg);
530 JpegInfoBuilder jpeg_info_builder;
531 jpeg_info_builder.SetImageLimit(2);
532 JpegScanner jpeg_scanner(&msg_handler);
533 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
534 data_source.Reset();
535
536 if (jpeg_scanner.HasError()) {
537 return ERROR_JPEGR_INVALID_INPUT_TYPE;
538 }
539
540 const auto& jpeg_info = jpeg_info_builder.GetInfo();
541 const auto& image_ranges = jpeg_info.GetImageRanges();
542 if (image_ranges.empty()) {
543 return ERROR_JPEGR_INVALID_INPUT_TYPE;
544 }
545
546 if (image_ranges.size() != 2) {
547 // Must be 2 JPEG Images
548 return ERROR_JPEGR_INVALID_INPUT_TYPE;
549 }
550
551 if (primary_image != nullptr) {
552 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
553 image_ranges[0].GetBegin();
554 primary_image->length = image_ranges[0].GetLength();
555 }
556
557 if (recovery_map != nullptr) {
558 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
559 image_ranges[1].GetBegin();
560 recovery_map->length = image_ranges[1].GetLength();
561 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400562
Dichen Zhang6947d532022-10-22 02:16:21 +0000563 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700564}
565
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000566
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400567status_t RecoveryMap::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000568 jr_compressed_ptr dest) {
569 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000570 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700571 }
572
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000573 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700574}
575
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400576status_t RecoveryMap::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
577 jr_compressed_ptr compressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500578 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400579 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000580 if (compressed_jpeg_image == nullptr
581 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500582 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000583 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000584 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700585 }
586
Nick Deakin6bd90432022-11-20 16:26:37 -0500587 string xmp = generateXmp(compressed_recovery_map->length, *metadata);
Dichen Zhanga8766262022-11-07 23:48:24 +0000588 string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
589
590 // 2 bytes: APP1 sign (ff e1)
591 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0"
592 // x bytes: length of xmp packet
593 int length = 2 + nameSpace.size() + xmp.size();
594 uint8_t lengthH = ((length >> 8) & 0xff);
595 uint8_t lengthL = (length & 0xff);
596
597 int pos = 0;
598
599 // JPEG/R structure:
600 // SOI (ff d8)
601 // APP1 (ff e1)
602 // 2 bytes of length (2 + 29 + length of xmp packet)
603 // name space ("http://ns.adobe.com/xap/1.0/\0")
604 // xmp
605 // primary image (without the first two bytes, the SOI sign)
606 // secondary image (the recovery map)
607 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
608 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
609 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
610 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
611 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
612 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
613 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpace.size(), pos));
614 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
615 JPEGR_CHECK(Write(dest,
616 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
617 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
618 dest->length = pos;
619
Dichen Zhang6947d532022-10-22 02:16:21 +0000620 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700621}
622
Nick Deakin6bd90432022-11-20 16:26:37 -0500623string RecoveryMap::generateXmp(int secondary_image_length, jpegr_metadata& metadata) {
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000624 const string kContainerPrefix = "GContainer";
625 const string kContainerUri = "http://ns.google.com/photos/1.0/container/";
626 const string kItemPrefix = "Item";
627 const string kRecoveryMap = "RecoveryMap";
628 const string kDirectory = "Directory";
629 const string kImageJpeg = "image/jpeg";
630 const string kItem = "Item";
631 const string kLength = "Length";
632 const string kMime = "Mime";
633 const string kPrimary = "Primary";
634 const string kSemantic = "Semantic";
635 const string kVersion = "Version";
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000636
637 const string kConDir = Name(kContainerPrefix, kDirectory);
638 const string kContainerItem = Name(kContainerPrefix, kItem);
639 const string kItemLength = Name(kItemPrefix, kLength);
640 const string kItemMime = Name(kItemPrefix, kMime);
641 const string kItemSemantic = Name(kItemPrefix, kSemantic);
642
643 const vector<string> kConDirSeq({kConDir, string("rdf:Seq")});
644 const vector<string> kLiItem({string("rdf:li"), kContainerItem});
645
646 std::stringstream ss;
647 photos_editing_formats::image_io::XmlWriter writer(ss);
648 writer.StartWritingElement("x:xmpmeta");
649 writer.WriteXmlns("x", "adobe:ns:meta/");
650 writer.WriteAttributeNameAndValue("x:xmptk", "Adobe XMP Core 5.1.2");
651 writer.StartWritingElement("rdf:RDF");
652 writer.WriteXmlns("rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
653 writer.StartWritingElement("rdf:Description");
654 writer.WriteXmlns(kContainerPrefix, kContainerUri);
Nick Deakin6bd90432022-11-20 16:26:37 -0500655 writer.WriteElementAndContent(Name(kContainerPrefix, kVersion), metadata.version);
656 writer.WriteElementAndContent(Name(kContainerPrefix, "rangeScalingFactor"),
657 metadata.rangeScalingFactor);
658 // TODO: determine structure for hdr10 metadata
659 // TODO: write rest of metadata
Dichen Zhang72fd2b12022-11-01 06:11:50 +0000660 writer.StartWritingElements(kConDirSeq);
661 size_t item_depth = writer.StartWritingElements(kLiItem);
662 writer.WriteAttributeNameAndValue(kItemSemantic, kPrimary);
663 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
664 writer.FinishWritingElementsToDepth(item_depth);
665 writer.StartWritingElements(kLiItem);
666 writer.WriteAttributeNameAndValue(kItemSemantic, kRecoveryMap);
667 writer.WriteAttributeNameAndValue(kItemMime, kImageJpeg);
668 writer.WriteAttributeNameAndValue(kItemLength, secondary_image_length);
669 writer.FinishWriting();
670
671 return ss.str();
672}
673
Dichen Zhang85b37562022-10-11 11:08:28 -0700674} // namespace android::recoverymap