blob: bd8874ed5d67e048f27be685588f46508fee6e9b [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
557 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
558 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
559
560 ColorCalculationFn luminanceFn = nullptr;
561 switch (uncompressed_yuv_420_image->colorGamut) {
562 case JPEGR_COLORGAMUT_BT709:
563 luminanceFn = srgbLuminance;
564 break;
565 case JPEGR_COLORGAMUT_P3:
566 luminanceFn = p3Luminance;
567 break;
568 case JPEGR_COLORGAMUT_BT2100:
569 luminanceFn = bt2100Luminance;
570 break;
571 case JPEGR_COLORGAMUT_UNSPECIFIED:
572 // Should be impossible to hit after input validation.
573 return ERROR_JPEGR_INVALID_COLORGAMUT;
574 }
575
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800576 std::mutex mutex;
Nick Deakind19e5762023-02-10 15:39:08 -0500577 float max_gain = 0.0f;
578 float min_gain = 1.0f;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800579 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
580 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
581 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500582
Nick Deakind19e5762023-02-10 15:39:08 -0500583 std::function<void()> computeMetadata = [uncompressed_p010_image, uncompressed_yuv_420_image,
584 hdrInvOetf, hdrGamutConversionFn, luminanceFn,
585 hdr_white_nits, threads, &mutex, &max_gain, &min_gain,
586 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800587 size_t rowStart, rowEnd;
Nick Deakind19e5762023-02-10 15:39:08 -0500588 float max_gain_th = 0.0f;
589 float min_gain_th = 1.0f;
590
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800591 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
592 for (size_t y = rowStart; y < rowEnd; ++y) {
593 for (size_t x = 0; x < uncompressed_p010_image->width; ++x) {
594 Color hdr_yuv_gamma = getP010Pixel(uncompressed_p010_image, x, y);
595 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
596 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
597 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
598 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
599
Nick Deakind19e5762023-02-10 15:39:08 -0500600 Color sdr_yuv_gamma =
601 getYuv420Pixel(uncompressed_yuv_420_image, x, y);
602 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
603#if USE_SRGB_INVOETF_LUT
604 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
605#else
606 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
607#endif
608 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
609
610 float gain = hdr_y_nits / sdr_y_nits;
611 max_gain_th = std::max(max_gain_th, gain);
612 min_gain_th = std::min(min_gain_th, gain);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800613 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400614 }
615 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800616 std::unique_lock<std::mutex> lock{mutex};
Nick Deakind19e5762023-02-10 15:39:08 -0500617 max_gain = std::max(max_gain, max_gain_th);
618 min_gain = std::min(min_gain, min_gain_th);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800619 };
620
621 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
622 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
623 luminanceFn, hdr_white_nits, &jobQueue]() -> void {
624 size_t rowStart, rowEnd;
Dichen Zhang24b4a392023-02-02 22:54:01 +0000625 size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
626 size_t dest_map_stride = dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800627 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
628 for (size_t y = rowStart; y < rowEnd; ++y) {
Dichen Zhang24b4a392023-02-02 22:54:01 +0000629 for (size_t x = 0; x < dest_map_width; ++x) {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800630 Color sdr_yuv_gamma =
631 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
632 Color sdr_rgb_gamma = srgbYuvToRgb(sdr_yuv_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800633#if USE_SRGB_INVOETF_LUT
634 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
635#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800636 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800637#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800638 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
639
640 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
641 Color hdr_rgb_gamma = bt2100YuvToRgb(hdr_yuv_gamma);
642 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
643 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
644 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
645
Dichen Zhang24b4a392023-02-02 22:54:01 +0000646 size_t pixel_idx = x + y * dest_map_stride;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800647 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Nick Deakind19e5762023-02-10 15:39:08 -0500648 encodeRecovery(sdr_y_nits, hdr_y_nits, metadata);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800649 }
650 }
651 }
652 };
653
654 std::vector<std::thread> workers;
655 for (int th = 0; th < threads - 1; th++) {
656 workers.push_back(std::thread(computeMetadata));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400657 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800658
659 // compute metadata
660 for (size_t rowStart = 0; rowStart < image_height;) {
661 size_t rowEnd = std::min(rowStart + rowStep, image_height);
662 jobQueue.enqueueJob(rowStart, rowEnd);
663 rowStart = rowEnd;
664 }
665 jobQueue.markQueueForEnd();
666 computeMetadata();
667 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
668 workers.clear();
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400669
Nick Deakind19e5762023-02-10 15:39:08 -0500670 metadata->maxContentBoost = max_gain;
671 metadata->minContentBoost = min_gain;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400672
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800673 // generate map
674 jobQueue.reset();
675 for (int th = 0; th < threads - 1; th++) {
676 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400677 }
678
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800679 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
680 for (size_t rowStart = 0; rowStart < map_height;) {
681 size_t rowEnd = std::min(rowStart + rowStep, map_height);
682 jobQueue.enqueueJob(rowStart, rowEnd);
683 rowStart = rowEnd;
684 }
685 jobQueue.markQueueForEnd();
686 generateMap();
687 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
688
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400689 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000690 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700691}
692
Dichen Zhang761b52d2023-02-10 22:38:38 +0000693status_t JpegR::applyRecoveryMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
Dichen Zhang6947d532022-10-22 02:16:21 +0000694 jr_uncompressed_ptr uncompressed_recovery_map,
Nick Deakin6bd90432022-11-20 16:26:37 -0500695 jr_metadata_ptr metadata,
Dichen Zhang6947d532022-10-22 02:16:21 +0000696 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700697 if (uncompressed_yuv_420_image == nullptr
698 || uncompressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500699 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700700 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000701 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700702 }
703
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800704 dest->width = uncompressed_yuv_420_image->width;
705 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530706 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Nick Deakind19e5762023-02-10 15:39:08 -0500707 RecoveryLUT recoveryLUT(metadata);
Ram Mohanfe723d62022-12-15 00:59:11 +0530708
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800709 JobQueue jobQueue;
710 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_recovery_map,
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800711 metadata, dest, &jobQueue, &idwTable,
712 &recoveryLUT]() -> void {
Nick Deakin01759062023-02-02 18:21:43 -0500713 const float hdr_ratio = metadata->maxContentBoost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800714 size_t width = uncompressed_yuv_420_image->width;
715 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400716
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800717#if USE_HLG_OETF_LUT
Nick Deakin01759062023-02-02 18:21:43 -0500718 ColorTransformFn hdrOetf = hlgOetfLUT;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800719#else
Nick Deakin01759062023-02-02 18:21:43 -0500720 ColorTransformFn hdrOetf = hlgOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800721#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800722
723 size_t rowStart, rowEnd;
724 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
725 for (size_t y = rowStart; y < rowEnd; ++y) {
726 for (size_t x = 0; x < width; ++x) {
727 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
728 Color rgb_gamma_sdr = srgbYuvToRgb(yuv_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800729#if USE_SRGB_INVOETF_LUT
730 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
731#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800732 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800733#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800734 float recovery;
735 // TODO: determine map scaling factor based on actual map dims
736 size_t map_scale_factor = kMapDimensionScaleFactor;
737 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
738 // Currently map_scale_factor is of type size_t, but it could be changed to a float
739 // later.
740 if (map_scale_factor != floorf(map_scale_factor)) {
741 recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y);
742 } else {
Nick Deakind19e5762023-02-10 15:39:08 -0500743 recovery = sampleMap(uncompressed_recovery_map, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800744 }
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800745#if USE_APPLY_RECOVERY_LUT
746 Color rgb_hdr = applyRecoveryLUT(rgb_sdr, recovery, recoveryLUT);
747#else
Nick Deakind19e5762023-02-10 15:39:08 -0500748 Color rgb_hdr = applyRecovery(rgb_sdr, recovery, metadata);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -0800749#endif
Nick Deakin01759062023-02-02 18:21:43 -0500750 Color rgb_gamma_hdr = hdrOetf(rgb_hdr / metadata->maxContentBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800751 uint32_t rgba1010102 = colorToRgba1010102(rgb_gamma_hdr);
752
753 size_t pixel_idx = x + y * width;
754 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba1010102;
755 }
756 }
757 }
758 };
759
760 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
761 std::vector<std::thread> workers;
762 for (int th = 0; th < threads - 1; th++) {
763 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400764 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800765 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
766 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
767 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
768 jobQueue.enqueueJob(rowStart, rowEnd);
769 rowStart = rowEnd;
770 }
771 jobQueue.markQueueForEnd();
772 applyRecMap();
773 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000774 return NO_ERROR;
775}
776
Dichen Zhang761b52d2023-02-10 22:38:38 +0000777status_t JpegR::extractPrimaryImageAndRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Nick Deakin01759062023-02-02 18:21:43 -0500778 jr_compressed_ptr primary_image,
779 jr_compressed_ptr recovery_map) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000780 if (compressed_jpegr_image == nullptr) {
781 return ERROR_JPEGR_INVALID_NULL_PTR;
782 }
783
784 MessageHandler msg_handler;
785 std::shared_ptr<DataSegment> seg =
786 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
787 static_cast<const uint8_t*>(compressed_jpegr_image->data),
788 DataSegment::BufferDispositionPolicy::kDontDelete);
789 DataSegmentDataSource data_source(seg);
790 JpegInfoBuilder jpeg_info_builder;
791 jpeg_info_builder.SetImageLimit(2);
792 JpegScanner jpeg_scanner(&msg_handler);
793 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
794 data_source.Reset();
795
796 if (jpeg_scanner.HasError()) {
797 return ERROR_JPEGR_INVALID_INPUT_TYPE;
798 }
799
800 const auto& jpeg_info = jpeg_info_builder.GetInfo();
801 const auto& image_ranges = jpeg_info.GetImageRanges();
802 if (image_ranges.empty()) {
803 return ERROR_JPEGR_INVALID_INPUT_TYPE;
804 }
805
806 if (image_ranges.size() != 2) {
807 // Must be 2 JPEG Images
808 return ERROR_JPEGR_INVALID_INPUT_TYPE;
809 }
810
811 if (primary_image != nullptr) {
812 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
813 image_ranges[0].GetBegin();
814 primary_image->length = image_ranges[0].GetLength();
815 }
816
817 if (recovery_map != nullptr) {
818 recovery_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
819 image_ranges[1].GetBegin();
820 recovery_map->length = image_ranges[1].GetLength();
821 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400822
Dichen Zhang6947d532022-10-22 02:16:21 +0000823 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700824}
825
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000826
Dichen Zhang761b52d2023-02-10 22:38:38 +0000827status_t JpegR::extractRecoveryMap(jr_compressed_ptr compressed_jpegr_image,
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000828 jr_compressed_ptr dest) {
829 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000830 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700831 }
832
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000833 return extractPrimaryImageAndRecoveryMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -0700834}
835
Dichen Zhangd18bc302022-12-16 20:55:24 +0000836// JPEG/R structure:
837// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +0000838//
839// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000840// APP1 (ff e1)
841// 2 bytes of length (2 + length of exif package)
842// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +0000843//
844// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000845// 2 bytes of length (2 + 29 + length of xmp package)
846// name space ("http://ns.adobe.com/xap/1.0/\0")
847// xmp
Dichen Zhang50ff1292023-01-27 18:03:43 +0000848//
849// (Required) primary image (without the first two bytes (SOI), may have other packages)
850//
851// (Required) secondary image (the recovery map)
Dichen Zhangd18bc302022-12-16 20:55:24 +0000852//
853// Metadata versions we are using:
854// ECMA TR-98 for JFIF marker
855// Exif 2.2 spec for EXIF marker
856// Adobe XMP spec part 3 for XMP marker
857// ICC v4.3 spec for ICC
Dichen Zhang761b52d2023-02-10 22:38:38 +0000858status_t JpegR::appendRecoveryMap(jr_compressed_ptr compressed_jpeg_image,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400859 jr_compressed_ptr compressed_recovery_map,
Dichen Zhangd18bc302022-12-16 20:55:24 +0000860 jr_exif_ptr exif,
Nick Deakin6bd90432022-11-20 16:26:37 -0500861 jr_metadata_ptr metadata,
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400862 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +0000863 if (compressed_jpeg_image == nullptr
864 || compressed_recovery_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500865 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +0000866 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000867 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700868 }
869
Dichen Zhanga8766262022-11-07 23:48:24 +0000870 int pos = 0;
871
Dichen Zhangd18bc302022-12-16 20:55:24 +0000872 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +0000873 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
874 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000875
876 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +0000877 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +0000878 const int length = 2 + exif->length;
879 const uint8_t lengthH = ((length >> 8) & 0xff);
880 const uint8_t lengthL = (length & 0xff);
881 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
882 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
883 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
884 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
885 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
886 }
887
888 // Prepare and write XMP
889 {
890 const string xmp = generateXmp(compressed_recovery_map->length, *metadata);
891 const string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
892 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
893 // 2 bytes: representing the length of the package
894 // 29 bytes: length of name space "http://ns.adobe.com/xap/1.0/\0",
895 // x bytes: length of xmp packet
Dichen Zhang25df9c82023-01-03 17:04:10 -0800896 const int length = 2 + nameSpaceLength + xmp.size();
Dichen Zhangd18bc302022-12-16 20:55:24 +0000897 const uint8_t lengthH = ((length >> 8) & 0xff);
898 const uint8_t lengthL = (length & 0xff);
899 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
900 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
901 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
902 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
903 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
904 JPEGR_CHECK(Write(dest, (void*)xmp.c_str(), xmp.size(), pos));
905 }
906
907 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000908 JPEGR_CHECK(Write(dest,
909 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000910
911 // Write secondary image
Dichen Zhanga8766262022-11-07 23:48:24 +0000912 JPEGR_CHECK(Write(dest, compressed_recovery_map->data, compressed_recovery_map->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +0000913
914 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +0000915 dest->length = pos;
916
Dichen Zhangd18bc302022-12-16 20:55:24 +0000917 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +0000918 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -0700919}
920
Dichen Zhang761b52d2023-02-10 22:38:38 +0000921status_t JpegR::toneMap(jr_uncompressed_ptr src,
Dichen Zhang636f5242022-12-07 20:25:44 +0000922 jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800923 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000924 return ERROR_JPEGR_INVALID_NULL_PTR;
925 }
926
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800927 dest->width = src->width;
928 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +0000929
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800930 size_t pixel_count = src->width * src->height;
931 for (size_t y = 0; y < src->height; ++y) {
932 for (size_t x = 0; x < src->width; ++x) {
933 size_t pixel_y_idx = x + y * src->width;
934 size_t pixel_uv_idx = x / 2 + (y / 2) * (src->width / 2);
935
936 uint16_t y_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_y_idx]
937 >> 6;
938 uint16_t u_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2]
939 >> 6;
940 uint16_t v_uint = reinterpret_cast<uint16_t*>(src->data)[pixel_count + pixel_uv_idx * 2 + 1]
941 >> 6;
942
943 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[pixel_y_idx];
944 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count + pixel_uv_idx];
945 uint8_t* v = &reinterpret_cast<uint8_t*>(dest->data)[pixel_count * 5 / 4 + pixel_uv_idx];
946
947 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
948 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
949 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
950 }
951 }
952
953 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +0000954
955 return NO_ERROR;
956}
957
Dichen Zhangb2ed8302023-02-10 23:05:04 +0000958} // namespace android::jpegrecoverymap