blob: bc353936a0651f6ff4a76fe548cfc8d8c7634909 [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
Dichen Zhang761b52d2023-02-10 22:38:38 +000017#include <jpegrecoverymap/jpegr.h>
Dichen Zhang02dd0592023-02-10 20:16:57 +000018#include <jpegrecoverymap/jpegencoderhelper.h>
19#include <jpegrecoverymap/jpegdecoderhelper.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040020#include <jpegrecoverymap/recoverymapmath.h>
Dichen Zhang761b52d2023-02-10 22:38:38 +000021#include <jpegrecoverymap/jpegrutils.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>
Dichen Zhang53751272023-01-17 19:09:01 -080028#include <utils/Log.h>
Dichen Zhang6438a192023-01-29 07:51:15 +000029#include "SkColorSpace.h"
30#include "SkICC.h"
Nick Deakinf6bca5a2022-11-04 10:43:43 -040031
Dichen Zhang6438a192023-01-29 07:51:15 +000032#include <map>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040033#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000034#include <sstream>
35#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000036#include <cmath>
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080037#include <condition_variable>
38#include <deque>
39#include <mutex>
40#include <thread>
41#include <unistd.h>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000042
43using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000044using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070045
Dichen Zhangb2ed8302023-02-10 23:05:04 +000046namespace android::jpegrecoverymap {
Dichen Zhang85b37562022-10-11 11:08:28 -070047
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080048#define USE_SRGB_INVOETF_LUT 1
49#define USE_HLG_OETF_LUT 1
50#define USE_PQ_OETF_LUT 1
51#define USE_HLG_INVOETF_LUT 1
52#define USE_PQ_INVOETF_LUT 1
Harish Mahendrakarf25991f2022-12-16 11:57:44 -080053#define USE_APPLY_RECOVERY_LUT 1
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080054
Nick Deakinf6bca5a2022-11-04 10:43:43 -040055#define JPEGR_CHECK(x) \
56 { \
57 status_t status = (x); \
58 if ((status) != NO_ERROR) { \
59 return status; \
60 } \
61 }
62
Nick Deakin6bd90432022-11-20 16:26:37 -050063// The current JPEGR version that we encode to
64static const uint32_t kJpegrVersion = 1;
65
Nick Deakinf6bca5a2022-11-04 10:43:43 -040066// Map is quarter res / sixteenth size
67static const size_t kMapDimensionScaleFactor = 4;
Dichen Zhang53751272023-01-17 19:09:01 -080068// JPEG block size.
69// JPEG encoding / decoding will require 8 x 8 DCT transform.
70// Width must be 8 dividable, and height must be 2 dividable.
71static const size_t kJpegBlock = 8;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000072// JPEG compress quality (0 ~ 100) for recovery map
73static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040074
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080075#define CONFIG_MULTITHREAD 1
76int GetCPUCoreCount() {
77 int cpuCoreCount = 1;
78#if CONFIG_MULTITHREAD
79#if defined(_SC_NPROCESSORS_ONLN)
80 cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
81#else
82 // _SC_NPROC_ONLN must be defined...
83 cpuCoreCount = sysconf(_SC_NPROC_ONLN);
84#endif
85#endif
86 return cpuCoreCount;
87}
88
Dichen Zhangb2ed8302023-02-10 23:05:04 +000089static const map<jpegrecoverymap::jpegr_color_gamut, skcms_Matrix3x3> jrGamut_to_skGamut {
Dichen Zhang6438a192023-01-29 07:51:15 +000090 {JPEGR_COLORGAMUT_BT709, SkNamedGamut::kSRGB},
91 {JPEGR_COLORGAMUT_P3, SkNamedGamut::kDisplayP3},
92 {JPEGR_COLORGAMUT_BT2100, SkNamedGamut::kRec2020},
93};
94
95static const map<
Dichen Zhangb2ed8302023-02-10 23:05:04 +000096 jpegrecoverymap::jpegr_transfer_function,
97 skcms_TransferFunction> jrTransFunc_to_skTransFunc {
Dichen Zhang6438a192023-01-29 07:51:15 +000098 {JPEGR_TF_SRGB, SkNamedTransferFn::kSRGB},
99 {JPEGR_TF_LINEAR, SkNamedTransferFn::kLinear},
100 {JPEGR_TF_HLG, SkNamedTransferFn::kHLG},
101 {JPEGR_TF_PQ, SkNamedTransferFn::kPQ},
102};
103
Dichen Zhang636f5242022-12-07 20:25:44 +0000104/* Encode API-0 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000105status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang636f5242022-12-07 20:25:44 +0000106 jpegr_transfer_function hdr_tf,
107 jr_compressed_ptr dest,
108 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000109 jr_exif_ptr exif) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000110 if (uncompressed_p010_image == nullptr || dest == nullptr) {
111 return ERROR_JPEGR_INVALID_NULL_PTR;
112 }
113
114 if (quality < 0 || quality > 100) {
115 return ERROR_JPEGR_INVALID_INPUT_TYPE;
116 }
117
Dichen Zhang53751272023-01-17 19:09:01 -0800118 if (uncompressed_p010_image->width % kJpegBlock != 0
119 || uncompressed_p010_image->height % 2 != 0) {
120 ALOGE("Image size can not be handled: %dx%d",
121 uncompressed_p010_image->width, uncompressed_p010_image->height);
122 return ERROR_JPEGR_INVALID_INPUT_TYPE;
123 }
124
Dichen Zhang636f5242022-12-07 20:25:44 +0000125 jpegr_metadata metadata;
126 metadata.version = kJpegrVersion;
Dichen Zhang636f5242022-12-07 20:25:44 +0000127
128 jpegr_uncompressed_struct uncompressed_yuv_420_image;
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800129 unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
130 uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
131 uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
Dichen Zhang636f5242022-12-07 20:25:44 +0000132 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
133
134 jpegr_uncompressed_struct map;
135 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin01759062023-02-02 18:21:43 -0500136 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Dichen Zhang636f5242022-12-07 20:25:44 +0000137 std::unique_ptr<uint8_t[]> map_data;
138 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
139
140 jpegr_compressed_struct compressed_map;
141 compressed_map.maxLength = map.width * map.height;
142 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
143 compressed_map.data = compressed_map_data.get();
144 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
145
Dichen Zhang6438a192023-01-29 07:51:15 +0000146 sk_sp<SkData> icc = SkWriteICCProfile(
147 jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
148 jrGamut_to_skGamut.at(uncompressed_yuv_420_image.colorGamut));
149
Dichen Zhang02dd0592023-02-10 20:16:57 +0000150 JpegEncoderHelper jpeg_encoder;
Dichen Zhang636f5242022-12-07 20:25:44 +0000151 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
152 uncompressed_yuv_420_image.width,
Dichen Zhang6438a192023-01-29 07:51:15 +0000153 uncompressed_yuv_420_image.height, quality,
154 icc.get()->data(), icc.get()->size())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000155 return ERROR_JPEGR_ENCODE_ERROR;
156 }
157 jpegr_compressed_struct jpeg;
158 jpeg.data = jpeg_encoder.getCompressedImagePtr();
159 jpeg.length = jpeg_encoder.getCompressedImageSize();
160
Dichen Zhang50ff1292023-01-27 18:03:43 +0000161 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000162
163 return NO_ERROR;
164}
165
166/* Encode API-1 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000167status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang6947d532022-10-22 02:16:21 +0000168 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500169 jpegr_transfer_function hdr_tf,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400170 jr_compressed_ptr dest,
Dichen Zhangffa34012022-11-03 23:21:13 +0000171 int quality,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000172 jr_exif_ptr exif) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000173 if (uncompressed_p010_image == nullptr
174 || uncompressed_yuv_420_image == nullptr
175 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000176 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000177 }
178
Dichen Zhangffa34012022-11-03 23:21:13 +0000179 if (quality < 0 || quality > 100) {
180 return ERROR_JPEGR_INVALID_INPUT_TYPE;
181 }
182
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400183 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
184 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
185 return ERROR_JPEGR_RESOLUTION_MISMATCH;
186 }
187
Dichen Zhang53751272023-01-17 19:09:01 -0800188 if (uncompressed_p010_image->width % kJpegBlock != 0
189 || uncompressed_p010_image->height % 2 != 0) {
190 ALOGE("Image size can not be handled: %dx%d",
191 uncompressed_p010_image->width, uncompressed_p010_image->height);
192 return ERROR_JPEGR_INVALID_INPUT_TYPE;
193 }
194
Nick Deakin6bd90432022-11-20 16:26:37 -0500195 jpegr_metadata metadata;
196 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500197
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400198 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000199 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin01759062023-02-02 18:21:43 -0500200 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400201 std::unique_ptr<uint8_t[]> map_data;
202 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
203
204 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000205 compressed_map.maxLength = map.width * map.height;
206 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400207 compressed_map.data = compressed_map_data.get();
208 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
209
Dichen Zhang6438a192023-01-29 07:51:15 +0000210 sk_sp<SkData> icc = SkWriteICCProfile(
211 jrTransFunc_to_skTransFunc.at(JPEGR_TF_SRGB),
212 jrGamut_to_skGamut.at(uncompressed_yuv_420_image->colorGamut));
213
Dichen Zhang02dd0592023-02-10 20:16:57 +0000214 JpegEncoderHelper jpeg_encoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400215 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image->data,
216 uncompressed_yuv_420_image->width,
Dichen Zhang6438a192023-01-29 07:51:15 +0000217 uncompressed_yuv_420_image->height, quality,
218 icc.get()->data(), icc.get()->size())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400219 return ERROR_JPEGR_ENCODE_ERROR;
220 }
221 jpegr_compressed_struct jpeg;
222 jpeg.data = jpeg_encoder.getCompressedImagePtr();
223 jpeg.length = jpeg_encoder.getCompressedImageSize();
224
Dichen Zhang50ff1292023-01-27 18:03:43 +0000225 JPEGR_CHECK(appendRecoveryMap(&jpeg, &compressed_map, exif, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400226
Dichen Zhang6947d532022-10-22 02:16:21 +0000227 return NO_ERROR;
228}
229
Dichen Zhang636f5242022-12-07 20:25:44 +0000230/* Encode API-2 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000231status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang6947d532022-10-22 02:16:21 +0000232 jr_uncompressed_ptr uncompressed_yuv_420_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400233 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500234 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000235 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000236 if (uncompressed_p010_image == nullptr
237 || uncompressed_yuv_420_image == nullptr
238 || compressed_jpeg_image == nullptr
239 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000240 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000241 }
242
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400243 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
244 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
245 return ERROR_JPEGR_RESOLUTION_MISMATCH;
246 }
247
Dichen Zhang53751272023-01-17 19:09:01 -0800248 if (uncompressed_p010_image->width % kJpegBlock != 0
249 || uncompressed_p010_image->height % 2 != 0) {
250 ALOGE("Image size can not be handled: %dx%d",
251 uncompressed_p010_image->width, uncompressed_p010_image->height);
252 return ERROR_JPEGR_INVALID_INPUT_TYPE;
253 }
254
Nick Deakin6bd90432022-11-20 16:26:37 -0500255 jpegr_metadata metadata;
256 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500257
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400258 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000259 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin01759062023-02-02 18:21:43 -0500260 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400261 std::unique_ptr<uint8_t[]> map_data;
262 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
263
264 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000265 compressed_map.maxLength = map.width * map.height;
266 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400267 compressed_map.data = compressed_map_data.get();
268 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
269
Dichen Zhang50ff1292023-01-27 18:03:43 +0000270 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400271
Dichen Zhang6947d532022-10-22 02:16:21 +0000272 return NO_ERROR;
273}
274
Dichen Zhang636f5242022-12-07 20:25:44 +0000275/* Encode API-3 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000276status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400277 jr_compressed_ptr compressed_jpeg_image,
Nick Deakin6bd90432022-11-20 16:26:37 -0500278 jpegr_transfer_function hdr_tf,
Dichen Zhang95cbb9f2022-11-07 18:32:05 +0000279 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000280 if (uncompressed_p010_image == nullptr
281 || compressed_jpeg_image == nullptr
282 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000283 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000284 }
285
Dichen Zhang53751272023-01-17 19:09:01 -0800286 if (uncompressed_p010_image->width % kJpegBlock != 0
287 || uncompressed_p010_image->height % 2 != 0) {
288 ALOGE("Image size can not be handled: %dx%d",
289 uncompressed_p010_image->width, uncompressed_p010_image->height);
290 return ERROR_JPEGR_INVALID_INPUT_TYPE;
291 }
292
Dichen Zhang02dd0592023-02-10 20:16:57 +0000293 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400294 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
295 return ERROR_JPEGR_DECODE_ERROR;
296 }
297 jpegr_uncompressed_struct uncompressed_yuv_420_image;
298 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
299 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
300 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500301 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400302
303 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
304 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
305 return ERROR_JPEGR_RESOLUTION_MISMATCH;
306 }
307
Nick Deakin6bd90432022-11-20 16:26:37 -0500308 jpegr_metadata metadata;
309 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500310
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400311 jpegr_uncompressed_struct map;
Dichen Zhanga8766262022-11-07 23:48:24 +0000312 JPEGR_CHECK(generateRecoveryMap(
Nick Deakin01759062023-02-02 18:21:43 -0500313 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400314 std::unique_ptr<uint8_t[]> map_data;
315 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
316
317 jpegr_compressed_struct compressed_map;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000318 compressed_map.maxLength = map.width * map.height;
319 unique_ptr<uint8_t[]> compressed_map_data = make_unique<uint8_t[]>(compressed_map.maxLength);
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400320 compressed_map.data = compressed_map_data.get();
321 JPEGR_CHECK(compressRecoveryMap(&map, &compressed_map));
322
Dichen Zhang50ff1292023-01-27 18:03:43 +0000323 JPEGR_CHECK(appendRecoveryMap(compressed_jpeg_image, &compressed_map, nullptr, &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400324
Dichen Zhang6947d532022-10-22 02:16:21 +0000325 return NO_ERROR;
326}
327
Dichen Zhang761b52d2023-02-10 22:38:38 +0000328status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000329 jr_info_ptr jpegr_info) {
330 if (compressed_jpegr_image == nullptr || jpegr_info == nullptr) {
331 return ERROR_JPEGR_INVALID_NULL_PTR;
332 }
333
334 jpegr_compressed_struct primary_image, recovery_map;
335 JPEGR_CHECK(extractPrimaryImageAndRecoveryMap(compressed_jpegr_image,
336 &primary_image, &recovery_map));
337
Dichen Zhang02dd0592023-02-10 20:16:57 +0000338 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000339 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
340 &jpegr_info->width, &jpegr_info->height,
341 jpegr_info->iccData, jpegr_info->exifData)) {
342 return ERROR_JPEGR_DECODE_ERROR;
343 }
344
345 return NO_ERROR;
346}
347
Dichen Zhang636f5242022-12-07 20:25:44 +0000348/* Decode API */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000349status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhangffa34012022-11-03 23:21:13 +0000350 jr_uncompressed_ptr dest,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000351 jr_exif_ptr exif,
352 bool request_sdr) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000353 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000354 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000355 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000356 // TODO: fill EXIF data
357 (void) exif;
358
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000359 if (request_sdr) {
Dichen Zhang02dd0592023-02-10 20:16:57 +0000360 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000361 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
362 true)) {
363 return ERROR_JPEGR_DECODE_ERROR;
364 }
365 jpegr_uncompressed_struct uncompressed_rgba_image;
366 uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
367 uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
368 uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
369 memcpy(dest->data, uncompressed_rgba_image.data,
370 uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
371 dest->width = uncompressed_rgba_image.width;
372 dest->height = uncompressed_rgba_image.height;
373 return NO_ERROR;
374 }
375
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400376 jpegr_compressed_struct compressed_map;
Nick Deakin6bd90432022-11-20 16:26:37 -0500377 jpegr_metadata metadata;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000378 JPEGR_CHECK(extractRecoveryMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400379
Dichen Zhang02dd0592023-02-10 20:16:57 +0000380 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400381 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
382 return ERROR_JPEGR_DECODE_ERROR;
383 }
384
Dichen Zhang02dd0592023-02-10 20:16:57 +0000385 JpegDecoderHelper recovery_map_decoder;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000386 if (!recovery_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000387 return ERROR_JPEGR_DECODE_ERROR;
388 }
389
390 jpegr_uncompressed_struct map;
391 map.data = recovery_map_decoder.getDecompressedImagePtr();
392 map.width = recovery_map_decoder.getDecompressedImageWidth();
393 map.height = recovery_map_decoder.getDecompressedImageHeight();
394
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400395 jpegr_uncompressed_struct uncompressed_yuv_420_image;
396 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
397 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
398 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
399
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000400 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_decoder.getXMPPtr()),
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000401 jpeg_decoder.getXMPSize(), &metadata)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000402 return ERROR_JPEGR_DECODE_ERROR;
403 }
404
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000405 JPEGR_CHECK(applyRecoveryMap(&uncompressed_yuv_420_image, &map, &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000406 return NO_ERROR;
407}
408
Dichen Zhang761b52d2023-02-10 22:38:38 +0000409status_t JpegR::compressRecoveryMap(jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400410 jr_compressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700411 if (uncompressed_recovery_map == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000412 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700413 }
414
Dichen Zhang02dd0592023-02-10 20:16:57 +0000415 JpegEncoderHelper jpeg_encoder;
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000416 if (!jpeg_encoder.compressImage(uncompressed_recovery_map->data,
417 uncompressed_recovery_map->width,
418 uncompressed_recovery_map->height,
419 kMapCompressQuality,
420 nullptr,
421 0,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400422 true /* isSingleChannel */)) {
423 return ERROR_JPEGR_ENCODE_ERROR;
424 }
425
Dichen Zhang0b9f7de2022-11-18 06:52:46 +0000426 if (dest->maxLength < jpeg_encoder.getCompressedImageSize()) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400427 return ERROR_JPEGR_BUFFER_TOO_SMALL;
428 }
429
430 memcpy(dest->data, jpeg_encoder.getCompressedImagePtr(), jpeg_encoder.getCompressedImageSize());
431 dest->length = jpeg_encoder.getCompressedImageSize();
Nick Deakin6bd90432022-11-20 16:26:37 -0500432 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400433
Dichen Zhang6947d532022-10-22 02:16:21 +0000434 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700435}
436
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800437const int kJobSzInRows = 16;
438static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
439 "align job size to kMapDimensionScaleFactor");
440
441class JobQueue {
442 public:
443 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
444 void enqueueJob(size_t rowStart, size_t rowEnd);
445 void markQueueForEnd();
446 void reset();
447
448 private:
449 bool mQueuedAllJobs = false;
450 std::deque<std::tuple<size_t, size_t>> mJobs;
451 std::mutex mMutex;
452 std::condition_variable mCv;
453};
454
455bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
456 std::unique_lock<std::mutex> lock{mMutex};
457 while (true) {
458 if (mJobs.empty()) {
459 if (mQueuedAllJobs) {
460 return false;
461 } else {
462 mCv.wait(lock);
463 }
464 } else {
465 auto it = mJobs.begin();
466 rowStart = std::get<0>(*it);
467 rowEnd = std::get<1>(*it);
468 mJobs.erase(it);
469 return true;
470 }
471 }
472 return false;
473}
474
475void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
476 std::unique_lock<std::mutex> lock{mMutex};
477 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
478 lock.unlock();
479 mCv.notify_one();
480}
481
482void JobQueue::markQueueForEnd() {
483 std::unique_lock<std::mutex> lock{mMutex};
484 mQueuedAllJobs = true;
485}
486
487void JobQueue::reset() {
488 std::unique_lock<std::mutex> lock{mMutex};
489 mJobs.clear();
490 mQueuedAllJobs = false;
491}
492
Dichen Zhang761b52d2023-02-10 22:38:38 +0000493status_t JpegR::generateRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
Dichen Zhang6947d532022-10-22 02:16:21 +0000494 jr_uncompressed_ptr uncompressed_p010_image,
Nick Deakin01759062023-02-02 18:21:43 -0500495 jpegr_transfer_function hdr_tf,
Nick Deakin6bd90432022-11-20 16:26:37 -0500496 jr_metadata_ptr metadata,
497 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700498 if (uncompressed_yuv_420_image == nullptr
499 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500500 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700501 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000502 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700503 }
504
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400505 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
506 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
507 return ERROR_JPEGR_RESOLUTION_MISMATCH;
508 }
509
Nick Deakin6bd90432022-11-20 16:26:37 -0500510 if (uncompressed_yuv_420_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED
511 || uncompressed_p010_image->colorGamut == JPEGR_COLORGAMUT_UNSPECIFIED) {
512 return ERROR_JPEGR_INVALID_COLORGAMUT;
513 }
514
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400515 size_t image_width = uncompressed_yuv_420_image->width;
516 size_t image_height = uncompressed_yuv_420_image->height;
517 size_t map_width = image_width / kMapDimensionScaleFactor;
518 size_t map_height = image_height / kMapDimensionScaleFactor;
Dichen Zhang53751272023-01-17 19:09:01 -0800519 size_t map_stride = static_cast<size_t>(
520 floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
521 size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400522
Dichen Zhang53751272023-01-17 19:09:01 -0800523 dest->width = map_stride;
524 dest->height = map_height_aligned;
Nick Deakin6bd90432022-11-20 16:26:37 -0500525 dest->colorGamut = JPEGR_COLORGAMUT_UNSPECIFIED;
Dichen Zhang53751272023-01-17 19:09:01 -0800526 dest->data = new uint8_t[map_stride * map_height_aligned];
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400527 std::unique_ptr<uint8_t[]> map_data;
528 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
529
Nick Deakin6bd90432022-11-20 16:26:37 -0500530 ColorTransformFn hdrInvOetf = nullptr;
Nick Deakin65f492a2022-11-29 22:47:40 -0500531 float hdr_white_nits = 0.0f;
Nick Deakin01759062023-02-02 18:21:43 -0500532 switch (hdr_tf) {
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000533 case JPEGR_TF_LINEAR:
534 hdrInvOetf = identityConversion;
535 break;
Nick Deakin6bd90432022-11-20 16:26:37 -0500536 case JPEGR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800537#if USE_HLG_INVOETF_LUT
538 hdrInvOetf = hlgInvOetfLUT;
539#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500540 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800541#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500542 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500543 break;
544 case JPEGR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800545#if USE_PQ_INVOETF_LUT
546 hdrInvOetf = pqInvOetfLUT;
547#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500548 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800549#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500550 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500551 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000552 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000553 // Should be impossible to hit after input validation.
554 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500555 }
556
Nick Deakina2215292023-02-14 21:40:06 -0500557 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
558 metadata->minContentBoost = 1.0f;
559
Nick Deakin6bd90432022-11-20 16:26:37 -0500560 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
561 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
562
563 ColorCalculationFn luminanceFn = nullptr;
564 switch (uncompressed_yuv_420_image->colorGamut) {
565 case JPEGR_COLORGAMUT_BT709:
566 luminanceFn = srgbLuminance;
567 break;
568 case JPEGR_COLORGAMUT_P3:
569 luminanceFn = p3Luminance;
570 break;
571 case JPEGR_COLORGAMUT_BT2100:
572 luminanceFn = bt2100Luminance;
573 break;
574 case JPEGR_COLORGAMUT_UNSPECIFIED:
575 // Should be impossible to hit after input validation.
576 return ERROR_JPEGR_INVALID_COLORGAMUT;
577 }
578
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800579 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800580 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
581 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
582 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500583
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800584 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
585 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
586 luminanceFn, hdr_white_nits, &jobQueue]() -> void {
587 size_t rowStart, rowEnd;
Dichen Zhang24b4a392023-02-02 22:54:01 +0000588 size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
589 size_t dest_map_stride = dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800590 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
591 for (size_t y = rowStart; y < rowEnd; ++y) {
Dichen Zhang24b4a392023-02-02 22:54:01 +0000592 for (size_t x = 0; x < dest_map_width; ++x) {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800593 Color sdr_yuv_gamma =
594 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
595 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800596#if USE_SRGB_INVOETF_LUT
597 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
598#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800599 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800600#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800601 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
602
603 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
604 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
605 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
606 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
607 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
608
Dichen Zhang24b4a392023-02-02 22:54:01 +0000609 size_t pixel_idx = x + y * dest_map_stride;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800610 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakind19e5762023-02-10 15:39:08 -0500611 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800612 }
613 }
614 }
615 };
616
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800617 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500618 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800619 for (int th = 0; th < threads - 1; th++) {
620 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400621 }
622
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800623 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
624 for (size_t rowStart = 0; rowStart < map_height;) {
625 size_t rowEnd = std::min(rowStart + rowStep, map_height);
626 jobQueue.enqueueJob(rowStart, rowEnd);
627 rowStart = rowEnd;
628 }
629 jobQueue.markQueueForEnd();
630 generateMap();
631 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
632
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400633 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000634 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700635}
636
Dichen Zhang761b52d2023-02-10 22:38:38 +0000637status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
Dichen Zhang6947d532022-10-22 02:16:21 +0000638 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500639 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000640 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700641 if (uncompressed_yuv_420_image == nullptr
642 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500643 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700644 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000645 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700646 }
647
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800648 dest->width = uncompressed_yuv_420_image->width;
649 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530650 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Nick Deakind19e5762023-02-10 15:39:08 -0500651 RecoveryLUT recoveryLUT(metadata);
Ram Mohanfe723d62022-12-15 00:59:11 +0530652
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800653 JobQueue jobQueue;
654 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800655 metadata, dest, &jobQueue, &idwTable,
656 &recoveryLUT]() -> void {
Nick Deakin01759062023-02-02 18:21:43 -0500657 const float hdr_ratio = metadata->maxContentBoost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800658 size_t width = uncompressed_yuv_420_image->width;
659 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400660
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800661#if USE_HLG_OETF_LUT
Nick Deakin01759062023-02-02 18:21:43 -0500662 ColorTransformFn hdrOetf = hlgOetfLUT;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800663#else
Nick Deakin01759062023-02-02 18:21:43 -0500664 ColorTransformFn hdrOetf = hlgOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800665#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800666
667 size_t rowStart, rowEnd;
668 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
669 for (size_t y = rowStart; y < rowEnd; ++y) {
670 for (size_t x = 0; x < width; ++x) {
671 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
672 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800673#if USE_SRGB_INVOETF_LUT
674 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
675#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800676 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800677#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800678 float recovery;
679 // TODO: determine map scaling factor based on actual map dims
680 size_t map_scale_factor = kMapDimensionScaleFactor;
681 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
682 // Currently map_scale_factor is of type size_t, but it could be changed to a float
683 // later.
684 if (map_scale_factor != floorf(map_scale_factor)) {
685 recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
686 } else {
Nick Deakind19e5762023-02-10 15:39:08 -0500687 recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800688 }
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800689#if USE_APPLY_RECOVERY_LUT
690 Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
691#else
Nick Deakind19e5762023-02-10 15:39:08 -0500692 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800693#endif
Nick Deakin01759062023-02-02 18:21:43 -0500694 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800695 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
696
697 size_t pixel_idx = x + y * width;
698 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
699 }
700 }
701 }
702 };
703
704 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
705 std::vector<std::thread> workers;
706 for (int th = 0; th < threads - 1; th++) {
707 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400708 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800709 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
710 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
711 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
712 jobQueue.enqueueJob(rowStart, rowEnd);
713 rowStart = rowEnd;
714 }
715 jobQueue.markQueueForEnd();
716 applyRecMap();
717 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000718 return NO_ERROR;
719}
720
Dichen Zhang761b52d2023-02-10 22:38:38 +0000721status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Nick Deakin01759062023-02-02 18:21:43 -0500722 jr_compressed_ptr primary_image,
723 jr_compressed_ptr recovery_map) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000724 if (compressed_jpegr_image == nullptr) {
725 return ERROR_JPEGR_INVALID_NULL_PTR;
726 }
727
728 MessageHandler msg_handler;
729 std::shared_ptr<DataSegment> seg =
730 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
731 static_cast<const uint8_t*>(compressed_jpegr_image->data),
732 DataSegment::BufferDispositionPolicy::kDontDelete);
733 DataSegmentDataSource data_source(seg);
734 JpegInfoBuilder jpeg_info_builder;
735 jpeg_info_builder.SetImageLimit(2);
736 JpegScanner jpeg_scanner(&msg_handler);
737 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
738 data_source.Reset();
739
740 if (jpeg_scanner.HasError()) {
741 return ERROR_JPEGR_INVALID_INPUT_TYPE;
742 }
743
744 const auto& jpeg_info = jpeg_info_builder.GetInfo();
745 const auto& image_ranges = jpeg_info.GetImageRanges();
746 if (image_ranges.empty()) {
747 return ERROR_JPEGR_INVALID_INPUT_TYPE;
748 }
749
750 if (image_ranges.size() != 2) {
751 // Must be 2 JPEG Images
752 return ERROR_JPEGR_INVALID_INPUT_TYPE;
753 }
754
755 if (primary_image != nullptr) {
756 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
757 image_ranges[0].GetBegin();
758 primary_image->length = image_ranges[0].GetLength();
759 }
760
761 if (recovery_map != nullptr) {
762 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
763 image_ranges[1].GetBegin();
764 recovery_map->length = image_ranges[1].GetLength();
765 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400766
Dichen Zhang6947d532022-10-22 02:16:21 +0000767 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700768}
769
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000770
Dichen Zhang761b52d2023-02-10 22:38:38 +0000771status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000772 jr_compressed_ptr dest) {
773 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000774 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700775 }
776
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000777 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700778}
779
Dichen Zhangd18bc302022-12-16 20:55:24 +0000780// JPEG/R structure:
781// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +0000782//
783// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000784// APP1 (ff e1)
785// 2 bytes of length (2 + length of exif package)
786// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +0000787//
788// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000789// 2 bytes of length (2 + 29 + length of xmp package)
790// name space ("http://ns.adobe.com/xap/1.0/\0")
791// xmp
Dichen Zhang50ff1292023-01-27 18:03:43 +0000792//
793// (Required) primary image (without the first two bytes (SOI), may have other packages)
794//
795// (Required) secondary image (the recovery map)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000796//
797// Metadata versions we are using:
798// ECMA TR-98 for JFIF marker
799// Exif 2.2 spec for EXIF marker
800// Adobe XMP spec part 3 for XMP marker
801// ICC v4.3 spec for ICC
Dichen Zhang761b52d2023-02-10 22:38:38 +0000802status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400803 jr_compressed_ptr compressed_recovery_map,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000804 jr_exif_ptr exif,
Nick Deakin6bd90432022-11-20 16:26:37 -0500805 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400806 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000807 if (compressed_jpeg_image == nullptr
808 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500809 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000810 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000811 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700812 }
813
Dichen Zhanga8766262022-11-07 23:48:24 +0000814 int pos = 0;
815
Dichen Zhangd18bc302022-12-16 20:55:24 +0000816 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +0000817 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
818 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000819
820 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +0000821 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +0000822 const int length = 2 + exif->length;
823 const uint8_t lengthH = ((length >> 8) & 0xff);
824 const uint8_t lengthL = (length & 0xff);
825 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
826 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
827 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
828 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
829 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
830 }
831
832 // Prepare and write XMP
833 {
834 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
835 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
836 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
837 // 2 bytes: representing the length of the package
838 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
839 // x bytes: length of xmp packet
Dichen Zhang25df9c82023-01-03 17:04:10 -0800840 const int length = 2 + nameSpaceLength + xmp.size();
Dichen Zhangd18bc302022-12-16 20:55:24 +0000841 const uint8_t lengthH = ((length >> 8) & 0xff);
842 const uint8_t lengthL = (length & 0xff);
843 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
844 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
845 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
846 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
847 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
848 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
849 }
850
851 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000852 JPEGR_CHECK(Write(dest,
853 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000854
855 // Write secondary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000856 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000857
858 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +0000859 dest->length = pos;
860
Dichen Zhangd18bc302022-12-16 20:55:24 +0000861 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +0000862 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700863}
864
Dichen Zhang761b52d2023-02-10 22:38:38 +0000865status_t JpegR::toneMap(jr_uncompressed_ptr src,
Dichen Zhang636f5242022-12-07 20:25:44 +0000866 jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800867 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000868 return ERROR_JPEGR_INVALID_NULL_PTR;
869 }
870
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800871 dest->width = src->width;
872 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +0000873
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800874 size_t pixel_count = src->width * src->height;
875 for (size_t y = 0; y < src->height; ++y) {
876 for (size_t x = 0; x < src->width; ++x) {
877 size_t pixel_y_idx = x + y * src->width;
878 size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
879
880 uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
881 >> 6;
882 uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
883 >> 6;
884 uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
885 >> 6;
886
887 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
888 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
889 uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
890
891 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
892 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
893 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
894 }
895 }
896
897 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +0000898
899 return NO_ERROR;
900}
901
Dichen Zhangb2ed8302023-02-10 23:05:04 +0000902} // namespace android::jpegrecoverymap