blob: fdfbb9cec292665030d180ee69ca5d0b8dab7015 [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
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000017#include <cmath>
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080018#include <condition_variable>
19#include <deque>
Ram Mohanb2359cd2023-07-28 14:33:49 +053020#include <memory>
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080021#include <mutex>
22#include <thread>
Ram Mohanb2359cd2023-07-28 14:33:49 +053023
24#include <ultrahdr/gainmapmath.h>
25#include <ultrahdr/icc.h>
26#include <ultrahdr/jpegr.h>
27#include <ultrahdr/jpegrutils.h>
28#include <ultrahdr/multipictureformat.h>
29
30#include <image_io/base/data_segment_data_source.h>
31#include <image_io/jpeg/jpeg_info.h>
32#include <image_io/jpeg/jpeg_info_builder.h>
33#include <image_io/jpeg/jpeg_marker.h>
34#include <image_io/jpeg/jpeg_scanner.h>
35
36#include <utils/Log.h>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000037
38using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000039using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070040
Dichen Zhangdbceb0e2023-04-14 19:03:18 +000041namespace android::ultrahdr {
Dichen Zhang85b37562022-10-11 11:08:28 -070042
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080043#define USE_SRGB_INVOETF_LUT 1
44#define USE_HLG_OETF_LUT 1
45#define USE_PQ_OETF_LUT 1
46#define USE_HLG_INVOETF_LUT 1
47#define USE_PQ_INVOETF_LUT 1
Dichen Zhang10959a42023-04-10 16:28:16 -070048#define USE_APPLY_GAIN_LUT 1
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080049
Nick Deakinf6bca5a2022-11-04 10:43:43 -040050#define JPEGR_CHECK(x) \
51 { \
52 status_t status = (x); \
53 if ((status) != NO_ERROR) { \
54 return status; \
55 } \
56 }
57
Dichen Zhang10959a42023-04-10 16:28:16 -070058// JPEG compress quality (0 ~ 100) for gain map
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000059static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040060
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080061#define CONFIG_MULTITHREAD 1
62int GetCPUCoreCount() {
63 int cpuCoreCount = 1;
64#if CONFIG_MULTITHREAD
65#if defined(_SC_NPROCESSORS_ONLN)
66 cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
67#else
68 // _SC_NPROC_ONLN must be defined...
69 cpuCoreCount = sysconf(_SC_NPROC_ONLN);
70#endif
71#endif
72 return cpuCoreCount;
73}
74
Ram Mohanb2359cd2023-07-28 14:33:49 +053075status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
76 jr_uncompressed_ptr yuv420_image_ptr,
Ram Mohanac1cfec2023-05-18 14:41:15 +053077 ultrahdr_transfer_function hdr_tf,
Ram Mohanb2359cd2023-07-28 14:33:49 +053078 jr_compressed_ptr dest_ptr) {
79 if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
80 ALOGE("Received nullptr for input p010 image");
Dichen Zhang66ca6e32023-04-05 12:22:54 -070081 return ERROR_JPEGR_INVALID_NULL_PTR;
82 }
Ram Mohanb2359cd2023-07-28 14:33:49 +053083 if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
84 ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
85 p010_image_ptr->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +053086 return ERROR_JPEGR_INVALID_INPUT_TYPE;
87 }
Ram Mohanb2359cd2023-07-28 14:33:49 +053088 if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
89 ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
90 kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +053091 return ERROR_JPEGR_INVALID_INPUT_TYPE;
92 }
Ram Mohanb2359cd2023-07-28 14:33:49 +053093 if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
94 ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
95 kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
Ram Mohand136b8a2023-06-02 09:06:40 +053096 return ERROR_JPEGR_INVALID_INPUT_TYPE;
97 }
Ram Mohanb2359cd2023-07-28 14:33:49 +053098 if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
99 p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
100 ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530101 return ERROR_JPEGR_INVALID_INPUT_TYPE;
102 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530103 if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
104 ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
105 p010_image_ptr->luma_stride, p010_image_ptr->width);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700106 return ERROR_JPEGR_INVALID_INPUT_TYPE;
107 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530108 if (p010_image_ptr->chroma_data != nullptr &&
109 p010_image_ptr->chroma_stride < p010_image_ptr->width) {
110 ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
111 p010_image_ptr->chroma_stride, p010_image_ptr->width);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530112 return ERROR_JPEGR_INVALID_INPUT_TYPE;
113 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530114 if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
115 ALOGE("Received nullptr for destination");
Ram Mohanac1cfec2023-05-18 14:41:15 +0530116 return ERROR_JPEGR_INVALID_NULL_PTR;
117 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530118 if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530119 ALOGE("Invalid hdr transfer function %d", hdr_tf);
120 return ERROR_JPEGR_INVALID_INPUT_TYPE;
121 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530122 if (yuv420_image_ptr == nullptr) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700123 return NO_ERROR;
124 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530125 if (yuv420_image_ptr->data == nullptr) {
126 ALOGE("Received nullptr for uncompressed 420 image");
Ram Mohanac1cfec2023-05-18 14:41:15 +0530127 return ERROR_JPEGR_INVALID_NULL_PTR;
128 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530129 if (yuv420_image_ptr->luma_stride != 0 &&
130 yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
131 ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
132 yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
Ram Mohan43c3a802023-07-24 18:33:49 +0530133 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700134 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530135 if (yuv420_image_ptr->chroma_data != nullptr &&
136 yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
137 ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
138 yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
Ram Mohan43c3a802023-07-24 18:33:49 +0530139 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700140 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530141 if (p010_image_ptr->width != yuv420_image_ptr->width ||
142 p010_image_ptr->height != yuv420_image_ptr->height) {
143 ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
144 p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700145 return ERROR_JPEGR_RESOLUTION_MISMATCH;
146 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530147 if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
148 yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
149 ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530150 return ERROR_JPEGR_INVALID_INPUT_TYPE;
151 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530152 return NO_ERROR;
153}
154
Ram Mohanb2359cd2023-07-28 14:33:49 +0530155status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
156 jr_uncompressed_ptr yuv420_image_ptr,
Ram Mohanac1cfec2023-05-18 14:41:15 +0530157 ultrahdr_transfer_function hdr_tf,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530158 jr_compressed_ptr dest_ptr, int quality) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530159 if (quality < 0 || quality > 100) {
160 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
161 return ERROR_JPEGR_INVALID_INPUT_TYPE;
162 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530163 return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700164}
165
Dichen Zhang636f5242022-12-07 20:25:44 +0000166/* Encode API-0 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530167status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
168 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
169 // validate input arguments
170 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
171 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700172 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800173 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530174 if (exif != nullptr && exif->data == nullptr) {
175 ALOGE("received nullptr for exif metadata");
176 return ERROR_JPEGR_INVALID_NULL_PTR;
177 }
178
Ram Mohanb2359cd2023-07-28 14:33:49 +0530179 // clean up input structure for later usage
180 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
181 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
182 if (!p010_image.chroma_data) {
183 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
184 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
185 p010_image.chroma_stride = p010_image.luma_stride;
186 }
Dichen Zhang636f5242022-12-07 20:25:44 +0000187
Ram Mohanb2359cd2023-07-28 14:33:49 +0530188 unique_ptr<uint8_t[]> yuv420_image_data =
189 make_unique<uint8_t[]>(p010_image.width * p010_image.height * 3 / 2);
190 jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
191 .width = p010_image.width,
192 .height = p010_image.height,
193 .colorGamut = p010_image.colorGamut,
194 .luma_stride = 0,
195 .chroma_data = nullptr,
196 .chroma_stride = 0};
197 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
198 if (!yuv420_image.chroma_data) {
199 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
200 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
201 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
202 }
Dichen Zhang636f5242022-12-07 20:25:44 +0000203
Ram Mohanb2359cd2023-07-28 14:33:49 +0530204 // tone map
205 JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
206
207 // gain map
208 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
209 jpegr_uncompressed_struct gainmap_image;
210 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
Dichen Zhang636f5242022-12-07 20:25:44 +0000211 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530212 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Dichen Zhang636f5242022-12-07 20:25:44 +0000213
Ram Mohanb2359cd2023-07-28 14:33:49 +0530214 // compress gain map
215 JpegEncoderHelper jpeg_enc_obj_gm;
216 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
217 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
218 .length = static_cast<int>(
219 jpeg_enc_obj_gm.getCompressedImageSize()),
220 .maxLength = static_cast<int>(
221 jpeg_enc_obj_gm.getCompressedImageSize()),
222 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Dichen Zhang636f5242022-12-07 20:25:44 +0000223
Ram Mohanb2359cd2023-07-28 14:33:49 +0530224 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000225
Ram Mohanb2359cd2023-07-28 14:33:49 +0530226 // convert to Bt601 YUV encoding for JPEG encode
227 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
228 JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
229 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400230
Ram Mohanb2359cd2023-07-28 14:33:49 +0530231 // compress 420 image
232 JpegEncoderHelper jpeg_enc_obj_yuv420;
233 if (!jpeg_enc_obj_yuv420.compressImage(yuv420_image.data, yuv420_image.width, yuv420_image.height,
234 quality, icc->getData(), icc->getLength())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000235 return ERROR_JPEGR_ENCODE_ERROR;
236 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530237 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
238 .length = static_cast<int>(
239 jpeg_enc_obj_yuv420.getCompressedImageSize()),
240 .maxLength = static_cast<int>(
241 jpeg_enc_obj_yuv420.getCompressedImageSize()),
242 .colorGamut = yuv420_image.colorGamut};
Dichen Zhang636f5242022-12-07 20:25:44 +0000243
Ram Mohanb2359cd2023-07-28 14:33:49 +0530244 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400245 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
246 &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000247
248 return NO_ERROR;
249}
250
251/* Encode API-1 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530252status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
253 jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
254 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
255 // validate input arguments
256 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530257 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000258 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000259 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530260 if (exif != nullptr && exif->data == nullptr) {
261 ALOGE("received nullptr for exif metadata");
262 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhangffa34012022-11-03 23:21:13 +0000263 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530264 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
265 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700266 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800267 }
268
Ram Mohanb2359cd2023-07-28 14:33:49 +0530269 // clean up input structure for later usage
270 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
271 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
272 if (!p010_image.chroma_data) {
273 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
274 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
275 p010_image.chroma_stride = p010_image.luma_stride;
276 }
277 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
278 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
279 if (!yuv420_image.chroma_data) {
280 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
281 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
282 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Ram Mohan43c3a802023-07-24 18:33:49 +0530283 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400284
Ram Mohanb2359cd2023-07-28 14:33:49 +0530285 // gain map
286 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
287 jpegr_uncompressed_struct gainmap_image;
288 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
289 std::unique_ptr<uint8_t[]> map_data;
290 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400291
Ram Mohanb2359cd2023-07-28 14:33:49 +0530292 // compress gain map
293 JpegEncoderHelper jpeg_enc_obj_gm;
294 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
295 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
296 .length = static_cast<int>(
297 jpeg_enc_obj_gm.getCompressedImageSize()),
298 .maxLength = static_cast<int>(
299 jpeg_enc_obj_gm.getCompressedImageSize()),
300 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
301
302 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
303
304 jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
305 unique_ptr<uint8_t[]> yuv_420_bt601_data;
306 // Convert to bt601 YUV encoding for JPEG encode
307 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
308 yuv_420_bt601_data = make_unique<uint8_t[]>(yuv420_image.width * yuv420_image.height * 3 / 2);
309 yuv420_bt601_image.data = yuv_420_bt601_data.get();
310 yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
311 yuv420_bt601_image.luma_stride = yuv420_image.width;
312 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
313 yuv420_bt601_image.chroma_data = data + yuv420_bt601_image.luma_stride * yuv420_image.height;
314 yuv420_bt601_image.chroma_stride = yuv420_bt601_image.luma_stride >> 1;
315
316 {
317 // copy luma
318 uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
319 uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
320 if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
321 memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
322 } else {
323 for (size_t i = 0; i < yuv420_image.height; i++) {
324 memcpy(y_dst, y_src, yuv420_image.width);
325 y_dst += yuv420_bt601_image.luma_stride;
326 y_src += yuv420_image.luma_stride;
327 }
328 }
329 }
330
331 if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
332 // copy luma
333 uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
334 uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
335 memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
336 } else {
337 // copy cb & cr
338 uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
339 uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
340 uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
341 uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
342 for (size_t i = 0; i < yuv420_image.height / 2; i++) {
343 memcpy(cb_dst, cb_src, yuv420_image.width / 2);
344 memcpy(cr_dst, cr_src, yuv420_image.width / 2);
345 cb_dst += yuv420_bt601_image.chroma_stride;
346 cb_src += yuv420_image.chroma_stride;
347 cr_dst += yuv420_bt601_image.chroma_stride;
348 cr_src += yuv420_image.chroma_stride;
349 }
350 }
351 JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
352 }
353
354 // compress 420 image
355 JpegEncoderHelper jpeg_enc_obj_yuv420;
356 if (!jpeg_enc_obj_yuv420.compressImage(yuv420_bt601_image.data, yuv420_bt601_image.width,
357 yuv420_bt601_image.height, quality, icc->getData(),
358 icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400359 return ERROR_JPEGR_ENCODE_ERROR;
360 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400361
Ram Mohanb2359cd2023-07-28 14:33:49 +0530362 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
363 .length = static_cast<int>(
364 jpeg_enc_obj_yuv420.getCompressedImageSize()),
365 .maxLength = static_cast<int>(
366 jpeg_enc_obj_yuv420.getCompressedImageSize()),
367 .colorGamut = yuv420_image.colorGamut};
368
369 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400370 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
371 &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000372 return NO_ERROR;
373}
374
Dichen Zhang636f5242022-12-07 20:25:44 +0000375/* Encode API-2 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530376status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
377 jr_uncompressed_ptr yuv420_image_ptr,
378 jr_compressed_ptr yuv420jpg_image_ptr,
379 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
380 // validate input arguments
381 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530382 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000383 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000384 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530385 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530386 ALOGE("received nullptr for compressed jpeg image");
387 return ERROR_JPEGR_INVALID_NULL_PTR;
388 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530389 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
390 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700391 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800392 }
393
Ram Mohanb2359cd2023-07-28 14:33:49 +0530394 // clean up input structure for later usage
395 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
396 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
397 if (!p010_image.chroma_data) {
398 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
399 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
400 p010_image.chroma_stride = p010_image.luma_stride;
401 }
402 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
403 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
404 if (!yuv420_image.chroma_data) {
405 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
406 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
407 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400408 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400409
Ram Mohanb2359cd2023-07-28 14:33:49 +0530410 // gain map
411 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
412 jpegr_uncompressed_struct gainmap_image;
413 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
414 std::unique_ptr<uint8_t[]> map_data;
415 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
416
417 // compress gain map
418 JpegEncoderHelper jpeg_enc_obj_gm;
419 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
420 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
421 .length = static_cast<int>(
422 jpeg_enc_obj_gm.getCompressedImageSize()),
423 .maxLength = static_cast<int>(
424 jpeg_enc_obj_gm.getCompressedImageSize()),
425 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
426
427 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000428}
429
Dichen Zhang636f5242022-12-07 20:25:44 +0000430/* Encode API-3 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530431status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
432 jr_compressed_ptr yuv420jpg_image_ptr,
433 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
434 // validate input arguments
435 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530436 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000437 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000438 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530439 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700440 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800441 }
442
Ram Mohanb2359cd2023-07-28 14:33:49 +0530443 // clean up input structure for later usage
444 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
445 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
446 if (!p010_image.chroma_data) {
447 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
448 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
449 p010_image.chroma_stride = p010_image.luma_stride;
450 }
451
452 // decode input jpeg, gamut is going to be bt601.
453 JpegDecoderHelper jpeg_dec_obj_yuv420;
454 if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
455 yuv420jpg_image_ptr->length)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400456 return ERROR_JPEGR_DECODE_ERROR;
457 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530458 jpegr_uncompressed_struct yuv420_image{};
459 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
460 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
461 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
462 yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
463 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
464 if (!yuv420_image.chroma_data) {
465 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
466 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
467 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
468 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400469
Ram Mohanb2359cd2023-07-28 14:33:49 +0530470 if (p010_image_ptr->width != yuv420_image.width ||
471 p010_image_ptr->height != yuv420_image.height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400472 return ERROR_JPEGR_RESOLUTION_MISMATCH;
473 }
474
Ram Mohanb2359cd2023-07-28 14:33:49 +0530475 // gain map
476 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
477 jpegr_uncompressed_struct gainmap_image;
478 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
479 true /* sdr_is_601 */));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400480 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530481 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400482
Ram Mohanb2359cd2023-07-28 14:33:49 +0530483 // compress gain map
484 JpegEncoderHelper jpeg_enc_obj_gm;
485 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
486 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
487 .length = static_cast<int>(
488 jpeg_enc_obj_gm.getCompressedImageSize()),
489 .maxLength = static_cast<int>(
490 jpeg_enc_obj_gm.getCompressedImageSize()),
491 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400492
Ram Mohanb2359cd2023-07-28 14:33:49 +0530493 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000494}
495
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000496/* Encode API-4 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530497status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
498 jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000499 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530500 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530501 ALOGE("received nullptr for compressed jpeg image");
502 return ERROR_JPEGR_INVALID_NULL_PTR;
503 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530504 if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530505 ALOGE("received nullptr for compressed gain map");
506 return ERROR_JPEGR_INVALID_NULL_PTR;
507 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530508 if (dest == nullptr || dest->data == nullptr) {
509 ALOGE("received nullptr for destination");
510 return ERROR_JPEGR_INVALID_NULL_PTR;
511 }
512
Nick Deakin0db53ee2023-05-19 17:14:45 -0400513 // We just want to check if ICC is present, so don't do a full decode. Note,
514 // this doesn't verify that the ICC is valid.
515 JpegDecoderHelper decoder;
516 std::vector<uint8_t> icc;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530517 decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
518 /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
519 /* exifData */ nullptr);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400520
521 // Add ICC if not already present.
522 if (icc.size() > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530523 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
524 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400525 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530526 sp<DataStruct> newIcc =
527 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
528 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
529 newIcc->getData(), newIcc->getLength(), metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400530 }
531
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000532 return NO_ERROR;
533}
534
Ram Mohanb2359cd2023-07-28 14:33:49 +0530535status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
536 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530537 ALOGE("received nullptr for compressed jpegr image");
538 return ERROR_JPEGR_INVALID_NULL_PTR;
539 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530540 if (jpeg_image_info_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530541 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000542 return ERROR_JPEGR_INVALID_NULL_PTR;
543 }
544
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530545 jpegr_compressed_struct primary_image, gainmap_image;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530546 status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530547 if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
548 return status;
549 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000550
Ram Mohanb2359cd2023-07-28 14:33:49 +0530551 JpegDecoderHelper jpeg_dec_obj_hdr;
552 if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
553 &jpeg_image_info_ptr->width,
554 &jpeg_image_info_ptr->height,
555 jpeg_image_info_ptr->iccData,
556 jpeg_image_info_ptr->exifData)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000557 return ERROR_JPEGR_DECODE_ERROR;
558 }
559
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530560 return status;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000561}
562
Dichen Zhang636f5242022-12-07 20:25:44 +0000563/* Decode API */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530564status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
565 float max_display_boost, jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000566 ultrahdr_output_format output_format,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530567 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
568 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanb0375052023-05-20 04:03:48 +0530569 ALOGE("received nullptr for compressed jpegr image");
570 return ERROR_JPEGR_INVALID_NULL_PTR;
571 }
Ram Mohanb0375052023-05-20 04:03:48 +0530572 if (dest == nullptr || dest->data == nullptr) {
573 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000574 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000575 }
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000576 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530577 ALOGE("received bad value for max_display_boost %f", max_display_boost);
578 return ERROR_JPEGR_INVALID_INPUT_TYPE;
579 }
Ram Mohanb0375052023-05-20 04:03:48 +0530580 if (exif != nullptr && exif->data == nullptr) {
581 ALOGE("received nullptr address for exif data");
582 return ERROR_JPEGR_INVALID_INPUT_TYPE;
583 }
Ram Mohanb0375052023-05-20 04:03:48 +0530584 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
585 ALOGE("received bad value for output format %d", output_format);
586 return ERROR_JPEGR_INVALID_INPUT_TYPE;
587 }
588
Ram Mohanb2359cd2023-07-28 14:33:49 +0530589 jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530590 status_t status =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530591 extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530592 if (status != NO_ERROR) {
593 if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
594 ALOGE("received invalid compressed jpegr image");
595 return status;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000596 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000597 }
598
Ram Mohanb2359cd2023-07-28 14:33:49 +0530599 JpegDecoderHelper jpeg_dec_obj_yuv420;
600 if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
601 (output_format == ULTRAHDR_OUTPUT_SDR))) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400602 return ERROR_JPEGR_DECODE_ERROR;
603 }
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530604
605 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530606 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
607 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
608 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530609 return ERROR_JPEGR_CALCULATION_ERROR;
610 }
611 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530612 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
613 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
614 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530615 return ERROR_JPEGR_CALCULATION_ERROR;
616 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530617 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400618
Dichen Zhangb80e2262023-03-08 06:59:51 +0000619 if (exif != nullptr) {
620 if (exif->data == nullptr) {
621 return ERROR_JPEGR_INVALID_NULL_PTR;
622 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530623 if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
Dichen Zhangb80e2262023-03-08 06:59:51 +0000624 return ERROR_JPEGR_BUFFER_TOO_SMALL;
625 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530626 memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
627 exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
Dichen Zhangb80e2262023-03-08 06:59:51 +0000628 }
629
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530630 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530631 dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
632 dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
633 memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
634 dest->width * dest->height * 4);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530635 return NO_ERROR;
636 }
637
Ram Mohanb2359cd2023-07-28 14:33:49 +0530638 JpegDecoderHelper jpeg_dec_obj_gm;
639 if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530640 return ERROR_JPEGR_DECODE_ERROR;
641 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530642 if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
643 jpeg_dec_obj_gm.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530644 return ERROR_JPEGR_CALCULATION_ERROR;
645 }
646
Ram Mohanb2359cd2023-07-28 14:33:49 +0530647 jpegr_uncompressed_struct gainmap_image;
648 gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
649 gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
650 gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000651
Ram Mohanb2359cd2023-07-28 14:33:49 +0530652 if (gainmap_image_ptr != nullptr) {
653 gainmap_image_ptr->width = gainmap_image.width;
654 gainmap_image_ptr->height = gainmap_image.height;
655 int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
656 gainmap_image_ptr->data = malloc(size);
657 memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530658 }
659
660 ultrahdr_metadata_struct uhdr_metadata;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530661 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
662 jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530663 return ERROR_JPEGR_INVALID_METADATA;
664 }
665
666 if (metadata != nullptr) {
667 metadata->version = uhdr_metadata.version;
668 metadata->minContentBoost = uhdr_metadata.minContentBoost;
669 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
670 metadata->gamma = uhdr_metadata.gamma;
671 metadata->offsetSdr = uhdr_metadata.offsetSdr;
672 metadata->offsetHdr = uhdr_metadata.offsetHdr;
673 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
674 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
675 }
676
Ram Mohanb2359cd2023-07-28 14:33:49 +0530677 jpegr_uncompressed_struct yuv420_image;
678 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
679 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
680 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
681 yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
682 jpeg_dec_obj_yuv420.getICCSize());
683 yuv420_image.luma_stride = yuv420_image.width;
684 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
685 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
686 yuv420_image.chroma_stride = yuv420_image.width >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400687
Ram Mohanb2359cd2023-07-28 14:33:49 +0530688 JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700689 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000690 return NO_ERROR;
691}
692
Ram Mohanb2359cd2023-07-28 14:33:49 +0530693status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
694 JpegEncoderHelper* jpeg_enc_obj_ptr) {
695 if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000696 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700697 }
698
Nick Deakin0db53ee2023-05-19 17:14:45 -0400699 // Don't need to convert YUV to Bt601 since single channel
Ram Mohanb2359cd2023-07-28 14:33:49 +0530700 if (!jpeg_enc_obj_ptr->compressImage(gainmap_image_ptr->data, gainmap_image_ptr->width,
701 gainmap_image_ptr->height, kMapCompressQuality, nullptr, 0,
702 true /* isSingleChannel */)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400703 return ERROR_JPEGR_ENCODE_ERROR;
704 }
705
Dichen Zhang6947d532022-10-22 02:16:21 +0000706 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700707}
708
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800709const int kJobSzInRows = 16;
710static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
711 "align job size to kMapDimensionScaleFactor");
712
713class JobQueue {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530714public:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800715 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
716 void enqueueJob(size_t rowStart, size_t rowEnd);
717 void markQueueForEnd();
718 void reset();
719
Ram Mohanb2359cd2023-07-28 14:33:49 +0530720private:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800721 bool mQueuedAllJobs = false;
722 std::deque<std::tuple<size_t, size_t>> mJobs;
723 std::mutex mMutex;
724 std::condition_variable mCv;
725};
726
727bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
728 std::unique_lock<std::mutex> lock{mMutex};
729 while (true) {
730 if (mJobs.empty()) {
731 if (mQueuedAllJobs) {
732 return false;
733 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000734 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800735 }
736 } else {
737 auto it = mJobs.begin();
738 rowStart = std::get<0>(*it);
739 rowEnd = std::get<1>(*it);
740 mJobs.erase(it);
741 return true;
742 }
743 }
744 return false;
745}
746
747void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
748 std::unique_lock<std::mutex> lock{mMutex};
749 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
750 lock.unlock();
751 mCv.notify_one();
752}
753
754void JobQueue::markQueueForEnd() {
755 std::unique_lock<std::mutex> lock{mMutex};
756 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000757 lock.unlock();
758 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800759}
760
761void JobQueue::reset() {
762 std::unique_lock<std::mutex> lock{mMutex};
763 mJobs.clear();
764 mQueuedAllJobs = false;
765}
766
Ram Mohanb2359cd2023-07-28 14:33:49 +0530767status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
768 jr_uncompressed_ptr p010_image_ptr,
769 ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
770 jr_uncompressed_ptr dest, bool sdr_is_601) {
771 if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
772 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000773 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700774 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530775 if (yuv420_image_ptr->width != p010_image_ptr->width ||
776 yuv420_image_ptr->height != p010_image_ptr->height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400777 return ERROR_JPEGR_RESOLUTION_MISMATCH;
778 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530779 if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
780 p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500781 return ERROR_JPEGR_INVALID_COLORGAMUT;
782 }
783
Ram Mohanb2359cd2023-07-28 14:33:49 +0530784 size_t image_width = yuv420_image_ptr->width;
785 size_t image_height = yuv420_image_ptr->height;
Ram Mohan43c3a802023-07-24 18:33:49 +0530786 size_t map_width = static_cast<size_t>(
787 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
788 size_t map_height = static_cast<size_t>(
789 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400790
Ram Mohanb2359cd2023-07-28 14:33:49 +0530791 dest->data = new uint8_t[map_width * map_height];
Ram Mohan43c3a802023-07-24 18:33:49 +0530792 dest->width = map_width;
793 dest->height = map_height;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000794 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530795 dest->luma_stride = map_width;
796 dest->chroma_data = nullptr;
797 dest->chroma_stride = 0;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400798 std::unique_ptr<uint8_t[]> map_data;
799 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
800
Nick Deakin6bd90432022-11-20 16:26:37 -0500801 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohancd3f6372023-06-02 15:16:15 +0530802 float hdr_white_nits = kSdrWhiteNits;
Nick Deakin01759062023-02-02 18:21:43 -0500803 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000804 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000805 hdrInvOetf = identityConversion;
806 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000807 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800808#if USE_HLG_INVOETF_LUT
809 hdrInvOetf = hlgInvOetfLUT;
810#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500811 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800812#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500813 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500814 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000815 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800816#if USE_PQ_INVOETF_LUT
817 hdrInvOetf = pqInvOetfLUT;
818#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500819 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800820#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500821 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500822 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000823 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000824 // Should be impossible to hit after input validation.
825 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500826 }
827
Nick Deakina2215292023-02-14 21:40:06 -0500828 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
829 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400830 metadata->gamma = 1.0f;
831 metadata->offsetSdr = 0.0f;
832 metadata->offsetHdr = 0.0f;
833 metadata->hdrCapacityMin = 1.0f;
834 metadata->hdrCapacityMax = metadata->maxContentBoost;
835
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700836 float log2MinBoost = log2(metadata->minContentBoost);
837 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500838
Ram Mohanb2359cd2023-07-28 14:33:49 +0530839 ColorTransformFn hdrGamutConversionFn =
840 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500841
842 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400843 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530844 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000845 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500846 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400847 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500848 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000849 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500850 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400851 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500852 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000853 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500854 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400855 sdrYuvToRgbFn = bt2100YuvToRgb;
856 break;
857 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
858 // Should be impossible to hit after input validation.
859 return ERROR_JPEGR_INVALID_COLORGAMUT;
860 }
861 if (sdr_is_601) {
862 sdrYuvToRgbFn = p3YuvToRgb;
863 }
864
865 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530866 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400867 case ULTRAHDR_COLORGAMUT_BT709:
868 hdrYuvToRgbFn = srgbYuvToRgb;
869 break;
870 case ULTRAHDR_COLORGAMUT_P3:
871 hdrYuvToRgbFn = p3YuvToRgb;
872 break;
873 case ULTRAHDR_COLORGAMUT_BT2100:
874 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500875 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000876 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500877 // Should be impossible to hit after input validation.
878 return ERROR_JPEGR_INVALID_COLORGAMUT;
879 }
880
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800881 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800882 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
883 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
884 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500885
Ram Mohanb2359cd2023-07-28 14:33:49 +0530886 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
887 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
888 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
889 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800890 size_t rowStart, rowEnd;
891 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
892 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530893 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530894 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400895 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
896 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800897#if USE_SRGB_INVOETF_LUT
898 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
899#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800900 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800901#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800902 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
903
Ram Mohanb2359cd2023-07-28 14:33:49 +0530904 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400905 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800906 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
907 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
908 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
909
Ram Mohan43c3a802023-07-24 18:33:49 +0530910 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800911 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530912 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800913 }
914 }
915 }
916 };
917
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800918 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500919 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800920 for (int th = 0; th < threads - 1; th++) {
921 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400922 }
923
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800924 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
925 for (size_t rowStart = 0; rowStart < map_height;) {
926 size_t rowEnd = std::min(rowStart + rowStep, map_height);
927 jobQueue.enqueueJob(rowStart, rowEnd);
928 rowStart = rowEnd;
929 }
930 jobQueue.markQueueForEnd();
931 generateMap();
932 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
933
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400934 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000935 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700936}
937
Ram Mohanb2359cd2023-07-28 14:33:49 +0530938status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
939 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
940 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700941 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530942 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
943 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000944 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700945 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530946 if (metadata->version.compare(kJpegrVersion)) {
947 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
948 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400949 }
950 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530951 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
952 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400953 }
954 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530955 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
956 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400957 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530958 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
959 metadata->hdrCapacityMax != metadata->maxContentBoost) {
960 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
961 metadata->hdrCapacityMax);
962 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400963 }
964
Ram Mohan9b3d6852023-05-26 00:09:50 +0530965 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +0530966 size_t image_width = yuv420_image_ptr->width;
967 size_t image_height = yuv420_image_ptr->height;
Ram Mohan43c3a802023-07-24 18:33:49 +0530968 size_t map_width = static_cast<size_t>(
969 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
970 size_t map_height = static_cast<size_t>(
971 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Ram Mohanb2359cd2023-07-28 14:33:49 +0530972 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohan9b3d6852023-05-26 00:09:50 +0530973 ALOGE("gain map dimensions and primary image dimensions are not to scale");
974 return ERROR_JPEGR_INVALID_INPUT_TYPE;
975 }
976
Ram Mohanb2359cd2023-07-28 14:33:49 +0530977 dest->width = yuv420_image_ptr->width;
978 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530979 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000980 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -0700981 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +0530982
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800983 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530984 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
985 &jobQueue, &idwTable, output_format, &gainLUT,
986 display_boost]() -> void {
987 size_t width = yuv420_image_ptr->width;
988 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400989
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800990 size_t rowStart, rowEnd;
991 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
992 for (size_t y = rowStart; y < rowEnd; ++y) {
993 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530994 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400995 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
996 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
997 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800998#if USE_SRGB_INVOETF_LUT
999 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1000#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001001 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001002#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001003 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001004 // TODO: determine map scaling factor based on actual map dims
1005 size_t map_scale_factor = kMapDimensionScaleFactor;
1006 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1007 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1008 // later.
1009 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301010 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001011 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301012 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001013 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001014
Dichen Zhang10959a42023-04-10 16:28:16 -07001015#if USE_APPLY_GAIN_LUT
1016 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001017#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001018 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001019#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001020 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001021 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001022
1023 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301024 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001025 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1026 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1027 break;
1028 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301029 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001030#if USE_HLG_OETF_LUT
1031 ColorTransformFn hdrOetf = hlgOetfLUT;
1032#else
1033 ColorTransformFn hdrOetf = hlgOetf;
1034#endif
1035 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1036 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1037 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1038 break;
1039 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301040 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301041#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001042 ColorTransformFn hdrOetf = pqOetfLUT;
1043#else
1044 ColorTransformFn hdrOetf = pqOetf;
1045#endif
1046 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1047 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1048 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1049 break;
1050 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301051 default: {
1052 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001053 // Should be impossible to hit after input validation.
1054 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001055 }
1056 }
1057 }
1058 };
1059
1060 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1061 std::vector<std::thread> workers;
1062 for (int th = 0; th < threads - 1; th++) {
1063 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001064 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301065 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1066 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1067 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001068 jobQueue.enqueueJob(rowStart, rowEnd);
1069 rowStart = rowEnd;
1070 }
1071 jobQueue.markQueueForEnd();
1072 applyRecMap();
1073 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001074 return NO_ERROR;
1075}
1076
Ram Mohanb2359cd2023-07-28 14:33:49 +05301077status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1078 jr_compressed_ptr primary_jpg_image_ptr,
1079 jr_compressed_ptr gainmap_jpg_image_ptr) {
1080 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001081 return ERROR_JPEGR_INVALID_NULL_PTR;
1082 }
1083
1084 MessageHandler msg_handler;
1085 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301086 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1087 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1088 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001089 DataSegmentDataSource data_source(seg);
1090 JpegInfoBuilder jpeg_info_builder;
1091 jpeg_info_builder.SetImageLimit(2);
1092 JpegScanner jpeg_scanner(&msg_handler);
1093 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1094 data_source.Reset();
1095
1096 if (jpeg_scanner.HasError()) {
1097 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1098 }
1099
1100 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1101 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001102
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301103 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001104 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1105 }
1106
Ram Mohanb2359cd2023-07-28 14:33:49 +05301107 if (primary_jpg_image_ptr != nullptr) {
1108 primary_jpg_image_ptr->data =
1109 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1110 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001111 }
1112
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301113 if (image_ranges.size() == 1) {
1114 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1115 }
1116
Ram Mohanb2359cd2023-07-28 14:33:49 +05301117 if (gainmap_jpg_image_ptr != nullptr) {
1118 gainmap_jpg_image_ptr->data =
1119 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1120 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001121 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001122
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301123 // TODO: choose primary image and gain map image carefully
1124 if (image_ranges.size() > 2) {
1125 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1126 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001127 }
1128
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301129 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001130}
1131
Dichen Zhangd18bc302022-12-16 20:55:24 +00001132// JPEG/R structure:
1133// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001134//
1135// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001136// APP1 (ff e1)
1137// 2 bytes of length (2 + length of exif package)
1138// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001139//
1140// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001141// 2 bytes of length (2 + 29 + length of xmp package)
1142// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001143// XMP
1144//
1145// (Required, MPF package) APP2 (ff e2)
1146// 2 bytes of length
1147// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001148//
1149// (Required) primary image (without the first two bytes (SOI), may have other packages)
1150//
Dichen Zhang61ede362023-02-22 18:50:13 +00001151// SOI (ff d8)
1152//
1153// (Required, XMP package) APP1 (ff e1)
1154// 2 bytes of length (2 + 29 + length of xmp package)
1155// name space ("http://ns.adobe.com/xap/1.0/\0")
1156// XMP
1157//
Dichen Zhang10959a42023-04-10 16:28:16 -07001158// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001159//
1160// Metadata versions we are using:
1161// ECMA TR-98 for JFIF marker
1162// Exif 2.2 spec for EXIF marker
1163// Adobe XMP spec part 3 for XMP marker
1164// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301165status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
1166 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
1167 size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001168 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301169 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1170 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001171 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001172 }
Nick Deakin094946b2023-06-09 11:58:41 -04001173 if (metadata->version.compare("1.0")) {
1174 ALOGE("received bad value for version: %s", metadata->version.c_str());
1175 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1176 }
1177 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301178 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301179 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301180 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1181 }
Nick Deakin094946b2023-06-09 11:58:41 -04001182 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1183 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301184 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001185 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1186 }
Nick Deakin094946b2023-06-09 11:58:41 -04001187 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301188 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
Nick Deakin094946b2023-06-09 11:58:41 -04001189 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1190 }
Nick Deakin094946b2023-06-09 11:58:41 -04001191 if (metadata->gamma <= 0.0f) {
1192 ALOGE("received bad value for gamma %f", metadata->gamma);
1193 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1194 }
1195
Dichen Zhang15345ea2023-02-23 23:54:32 +00001196 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301197 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001198
Dichen Zhang15345ea2023-02-23 23:54:32 +00001199 // calculate secondary image length first, because the length will be written into the primary
1200 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001201 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001202 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301203 + nameSpaceLength /* 29 bytes length of name space including \0 */
1204 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001205 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301206 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001207 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001208 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001209 // same as primary
1210 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001211
Dichen Zhang15345ea2023-02-23 23:54:32 +00001212 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001213 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001214 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001215 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1216 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001217
1218 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001219 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001220 const int length = 2 + exif->length;
1221 const uint8_t lengthH = ((length >> 8) & 0xff);
1222 const uint8_t lengthL = (length & 0xff);
1223 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1224 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1225 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1226 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1227 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1228 }
1229
1230 // Prepare and write XMP
1231 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001232 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001233 const uint8_t lengthH = ((length >> 8) & 0xff);
1234 const uint8_t lengthL = (length & 0xff);
1235 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1236 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1237 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1238 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1239 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001240 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1241 }
1242
Nick Deakin0db53ee2023-05-19 17:14:45 -04001243 // Write ICC
1244 if (icc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301245 const int length = icc_size + 2;
1246 const uint8_t lengthH = ((length >> 8) & 0xff);
1247 const uint8_t lengthL = (length & 0xff);
1248 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1249 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1250 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1251 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1252 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001253 }
1254
Dichen Zhang61ede362023-02-22 18:50:13 +00001255 // Prepare and write MPF
1256 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301257 const int length = 2 + calculateMpfSize();
1258 const uint8_t lengthH = ((length >> 8) & 0xff);
1259 const uint8_t lengthL = (length & 0xff);
1260 int primary_image_size = pos + length + primary_jpg_image_ptr->length;
1261 // between APP2 + package size + signature
1262 // ff e2 00 58 4d 50 46 00
1263 // 2 + 2 + 4 = 8 (bytes)
1264 // and ff d8 sign of the secondary image
1265 int secondary_image_offset = primary_image_size - pos - 8;
1266 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1267 secondary_image_size, secondary_image_offset);
1268 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1269 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1270 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1271 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1272 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001273 }
1274
1275 // Write primary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301276 JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
1277 primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001278 // Finish primary image
1279
Dichen Zhang10959a42023-04-10 16:28:16 -07001280 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001281 // Write SOI
1282 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1283 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1284
1285 // Prepare and write XMP
1286 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001287 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001288 const uint8_t lengthH = ((length >> 8) & 0xff);
1289 const uint8_t lengthL = (length & 0xff);
1290 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1291 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1292 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1293 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1294 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1295 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1296 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001297
1298 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301299 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1300 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001301
1302 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001303 dest->length = pos;
1304
Dichen Zhangd18bc302022-12-16 20:55:24 +00001305 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001306 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001307}
1308
Dichen Zhang61ede362023-02-22 18:50:13 +00001309status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001310 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001311 return ERROR_JPEGR_INVALID_NULL_PTR;
1312 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301313 if (src->width != dest->width || src->height != dest->height) {
1314 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001315 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301316 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
1317 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1318 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
1319 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1320 size_t v_offset = (dest->chroma_stride * dest->height / 2);
1321 uint8_t* dst_v_data = dst_u_data + v_offset;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001322 for (size_t y = 0; y < src->height; ++y) {
1323 for (size_t x = 0; x < src->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301324 size_t src_y_idx = y * src->luma_stride + x;
1325 size_t src_u_idx = (y >> 1) * src->chroma_stride + (x & ~0x1);
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001326 size_t src_v_idx = src_u_idx + 1;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001327
Ram Mohanb2359cd2023-07-28 14:33:49 +05301328 uint16_t y_uint = src_y_data[src_y_idx] >> 6;
1329 uint16_t u_uint = src_uv_data[src_u_idx] >> 6;
1330 uint16_t v_uint = src_uv_data[src_v_idx] >> 6;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001331
Ram Mohanb2359cd2023-07-28 14:33:49 +05301332 size_t dest_y_idx = x + y * dest->luma_stride;
1333 size_t dest_chroma_idx = (x / 2) + (y / 2) * (dest->chroma_stride);
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001334
Ram Mohanb2359cd2023-07-28 14:33:49 +05301335 dst_y_data[dest_y_idx] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1336 dst_u_data[dest_chroma_idx] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1337 dst_v_data[dest_chroma_idx] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001338 }
1339 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001340 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001341 return NO_ERROR;
1342}
1343
Ram Mohanb2359cd2023-07-28 14:33:49 +05301344status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001345 ultrahdr_color_gamut dest_encoding) {
1346 if (image == nullptr) {
1347 return ERROR_JPEGR_INVALID_NULL_PTR;
1348 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301349 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1350 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001351 return ERROR_JPEGR_INVALID_COLORGAMUT;
1352 }
1353
1354 ColorTransformFn conversionFn = nullptr;
1355 switch (src_encoding) {
1356 case ULTRAHDR_COLORGAMUT_BT709:
1357 switch (dest_encoding) {
1358 case ULTRAHDR_COLORGAMUT_BT709:
1359 return NO_ERROR;
1360 case ULTRAHDR_COLORGAMUT_P3:
1361 conversionFn = yuv709To601;
1362 break;
1363 case ULTRAHDR_COLORGAMUT_BT2100:
1364 conversionFn = yuv709To2100;
1365 break;
1366 default:
1367 // Should be impossible to hit after input validation
1368 return ERROR_JPEGR_INVALID_COLORGAMUT;
1369 }
1370 break;
1371 case ULTRAHDR_COLORGAMUT_P3:
1372 switch (dest_encoding) {
1373 case ULTRAHDR_COLORGAMUT_BT709:
1374 conversionFn = yuv601To709;
1375 break;
1376 case ULTRAHDR_COLORGAMUT_P3:
1377 return NO_ERROR;
1378 case ULTRAHDR_COLORGAMUT_BT2100:
1379 conversionFn = yuv601To2100;
1380 break;
1381 default:
1382 // Should be impossible to hit after input validation
1383 return ERROR_JPEGR_INVALID_COLORGAMUT;
1384 }
1385 break;
1386 case ULTRAHDR_COLORGAMUT_BT2100:
1387 switch (dest_encoding) {
1388 case ULTRAHDR_COLORGAMUT_BT709:
1389 conversionFn = yuv2100To709;
1390 break;
1391 case ULTRAHDR_COLORGAMUT_P3:
1392 conversionFn = yuv2100To601;
1393 break;
1394 case ULTRAHDR_COLORGAMUT_BT2100:
1395 return NO_ERROR;
1396 default:
1397 // Should be impossible to hit after input validation
1398 return ERROR_JPEGR_INVALID_COLORGAMUT;
1399 }
1400 break;
1401 default:
1402 // Should be impossible to hit after input validation
1403 return ERROR_JPEGR_INVALID_COLORGAMUT;
1404 }
1405
1406 if (conversionFn == nullptr) {
1407 // Should be impossible to hit after input validation
1408 return ERROR_JPEGR_INVALID_COLORGAMUT;
1409 }
1410
1411 for (size_t y = 0; y < image->height / 2; ++y) {
1412 for (size_t x = 0; x < image->width / 2; ++x) {
1413 transformYuv420(image, x, y, conversionFn);
1414 }
1415 }
1416
1417 return NO_ERROR;
1418}
1419
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001420} // namespace android::ultrahdr