blob: 55333b347a13e194aa8f9237421ef9e6d07a6b8a [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 Mohaneca81942023-07-29 14:41:48 +0530188 const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530189 unique_ptr<uint8_t[]> yuv420_image_data =
Ram Mohaneca81942023-07-29 14:41:48 +0530190 make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530191 jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
192 .width = p010_image.width,
193 .height = p010_image.height,
194 .colorGamut = p010_image.colorGamut,
Ram Mohaneca81942023-07-29 14:41:48 +0530195 .luma_stride = yu420_luma_stride,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530196 .chroma_data = nullptr,
Ram Mohaneca81942023-07-29 14:41:48 +0530197 .chroma_stride = yu420_luma_stride >> 1};
198 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
199 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
Dichen Zhang636f5242022-12-07 20:25:44 +0000200
Ram Mohanb2359cd2023-07-28 14:33:49 +0530201 // tone map
202 JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
203
204 // gain map
205 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
206 jpegr_uncompressed_struct gainmap_image;
207 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
Dichen Zhang636f5242022-12-07 20:25:44 +0000208 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530209 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Dichen Zhang636f5242022-12-07 20:25:44 +0000210
Ram Mohanb2359cd2023-07-28 14:33:49 +0530211 // compress gain map
212 JpegEncoderHelper jpeg_enc_obj_gm;
213 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
214 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
215 .length = static_cast<int>(
216 jpeg_enc_obj_gm.getCompressedImageSize()),
217 .maxLength = static_cast<int>(
218 jpeg_enc_obj_gm.getCompressedImageSize()),
219 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Dichen Zhang636f5242022-12-07 20:25:44 +0000220
Ram Mohanb2359cd2023-07-28 14:33:49 +0530221 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000222
Ram Mohanb2359cd2023-07-28 14:33:49 +0530223 // convert to Bt601 YUV encoding for JPEG encode
224 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
225 JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
226 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400227
Ram Mohanb2359cd2023-07-28 14:33:49 +0530228 // compress 420 image
229 JpegEncoderHelper jpeg_enc_obj_yuv420;
Ram Mohaneca81942023-07-29 14:41:48 +0530230 if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
231 reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
232 yuv420_image.width, yuv420_image.height,
233 yuv420_image.luma_stride, yuv420_image.chroma_stride,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530234 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) {
Ram Mohaneca81942023-07-29 14:41:48 +0530308 const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
309 yuv_420_bt601_data =
310 make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530311 yuv420_bt601_image.data = yuv_420_bt601_data.get();
312 yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
Ram Mohaneca81942023-07-29 14:41:48 +0530313 yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530314 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
Ram Mohaneca81942023-07-29 14:41:48 +0530315 yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
316 yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530317
318 {
319 // copy luma
320 uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
321 uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
322 if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
323 memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
324 } else {
325 for (size_t i = 0; i < yuv420_image.height; i++) {
326 memcpy(y_dst, y_src, yuv420_image.width);
Ram Mohaneca81942023-07-29 14:41:48 +0530327 if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
328 memset(y_dst + yuv420_image.width, 0,
329 yuv420_bt601_image.luma_stride - yuv420_image.width);
330 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530331 y_dst += yuv420_bt601_image.luma_stride;
332 y_src += yuv420_image.luma_stride;
333 }
334 }
335 }
336
337 if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
338 // copy luma
339 uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
340 uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
341 memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
342 } else {
343 // copy cb & cr
344 uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
345 uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
346 uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
347 uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
348 for (size_t i = 0; i < yuv420_image.height / 2; i++) {
349 memcpy(cb_dst, cb_src, yuv420_image.width / 2);
350 memcpy(cr_dst, cr_src, yuv420_image.width / 2);
Ram Mohaneca81942023-07-29 14:41:48 +0530351 if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
352 memset(cb_dst + yuv420_image.width / 2, 0,
353 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
354 memset(cr_dst + yuv420_image.width / 2, 0,
355 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
356 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530357 cb_dst += yuv420_bt601_image.chroma_stride;
358 cb_src += yuv420_image.chroma_stride;
359 cr_dst += yuv420_bt601_image.chroma_stride;
360 cr_src += yuv420_image.chroma_stride;
361 }
362 }
363 JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
364 }
365
366 // compress 420 image
367 JpegEncoderHelper jpeg_enc_obj_yuv420;
Ram Mohaneca81942023-07-29 14:41:48 +0530368 if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
369 reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
370 yuv420_bt601_image.width, yuv420_bt601_image.height,
371 yuv420_bt601_image.luma_stride,
372 yuv420_bt601_image.chroma_stride, quality, icc->getData(),
Ram Mohanb2359cd2023-07-28 14:33:49 +0530373 icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400374 return ERROR_JPEGR_ENCODE_ERROR;
375 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400376
Ram Mohanb2359cd2023-07-28 14:33:49 +0530377 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
378 .length = static_cast<int>(
379 jpeg_enc_obj_yuv420.getCompressedImageSize()),
380 .maxLength = static_cast<int>(
381 jpeg_enc_obj_yuv420.getCompressedImageSize()),
382 .colorGamut = yuv420_image.colorGamut};
383
384 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400385 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
386 &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000387 return NO_ERROR;
388}
389
Dichen Zhang636f5242022-12-07 20:25:44 +0000390/* Encode API-2 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530391status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
392 jr_uncompressed_ptr yuv420_image_ptr,
393 jr_compressed_ptr yuv420jpg_image_ptr,
394 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
395 // validate input arguments
396 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530397 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000398 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000399 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530400 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530401 ALOGE("received nullptr for compressed jpeg image");
402 return ERROR_JPEGR_INVALID_NULL_PTR;
403 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530404 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
405 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700406 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800407 }
408
Ram Mohanb2359cd2023-07-28 14:33:49 +0530409 // clean up input structure for later usage
410 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
411 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
412 if (!p010_image.chroma_data) {
413 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
414 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
415 p010_image.chroma_stride = p010_image.luma_stride;
416 }
417 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
418 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
419 if (!yuv420_image.chroma_data) {
420 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
421 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
422 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400423 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400424
Ram Mohanb2359cd2023-07-28 14:33:49 +0530425 // gain map
426 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
427 jpegr_uncompressed_struct gainmap_image;
428 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
429 std::unique_ptr<uint8_t[]> map_data;
430 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
431
432 // compress gain map
433 JpegEncoderHelper jpeg_enc_obj_gm;
434 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
435 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
436 .length = static_cast<int>(
437 jpeg_enc_obj_gm.getCompressedImageSize()),
438 .maxLength = static_cast<int>(
439 jpeg_enc_obj_gm.getCompressedImageSize()),
440 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
441
442 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000443}
444
Dichen Zhang636f5242022-12-07 20:25:44 +0000445/* Encode API-3 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530446status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
447 jr_compressed_ptr yuv420jpg_image_ptr,
448 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
449 // validate input arguments
450 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530451 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000452 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000453 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530454 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700455 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800456 }
457
Ram Mohanb2359cd2023-07-28 14:33:49 +0530458 // clean up input structure for later usage
459 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
460 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
461 if (!p010_image.chroma_data) {
462 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
463 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
464 p010_image.chroma_stride = p010_image.luma_stride;
465 }
466
467 // decode input jpeg, gamut is going to be bt601.
468 JpegDecoderHelper jpeg_dec_obj_yuv420;
469 if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
470 yuv420jpg_image_ptr->length)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400471 return ERROR_JPEGR_DECODE_ERROR;
472 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530473 jpegr_uncompressed_struct yuv420_image{};
474 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
475 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
476 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
477 yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
478 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
479 if (!yuv420_image.chroma_data) {
480 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
481 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
482 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
483 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400484
Ram Mohanb2359cd2023-07-28 14:33:49 +0530485 if (p010_image_ptr->width != yuv420_image.width ||
486 p010_image_ptr->height != yuv420_image.height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400487 return ERROR_JPEGR_RESOLUTION_MISMATCH;
488 }
489
Ram Mohanb2359cd2023-07-28 14:33:49 +0530490 // gain map
491 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
492 jpegr_uncompressed_struct gainmap_image;
493 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
494 true /* sdr_is_601 */));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400495 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530496 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400497
Ram Mohanb2359cd2023-07-28 14:33:49 +0530498 // compress gain map
499 JpegEncoderHelper jpeg_enc_obj_gm;
500 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
501 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
502 .length = static_cast<int>(
503 jpeg_enc_obj_gm.getCompressedImageSize()),
504 .maxLength = static_cast<int>(
505 jpeg_enc_obj_gm.getCompressedImageSize()),
506 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400507
Ram Mohanb2359cd2023-07-28 14:33:49 +0530508 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000509}
510
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000511/* Encode API-4 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530512status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
513 jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000514 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530515 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530516 ALOGE("received nullptr for compressed jpeg image");
517 return ERROR_JPEGR_INVALID_NULL_PTR;
518 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530519 if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530520 ALOGE("received nullptr for compressed gain map");
521 return ERROR_JPEGR_INVALID_NULL_PTR;
522 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530523 if (dest == nullptr || dest->data == nullptr) {
524 ALOGE("received nullptr for destination");
525 return ERROR_JPEGR_INVALID_NULL_PTR;
526 }
527
Nick Deakin0db53ee2023-05-19 17:14:45 -0400528 // We just want to check if ICC is present, so don't do a full decode. Note,
529 // this doesn't verify that the ICC is valid.
530 JpegDecoderHelper decoder;
531 std::vector<uint8_t> icc;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530532 decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
533 /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
534 /* exifData */ nullptr);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400535
536 // Add ICC if not already present.
537 if (icc.size() > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530538 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
539 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400540 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530541 sp<DataStruct> newIcc =
542 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
543 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
544 newIcc->getData(), newIcc->getLength(), metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400545 }
546
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000547 return NO_ERROR;
548}
549
Ram Mohanb2359cd2023-07-28 14:33:49 +0530550status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
551 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530552 ALOGE("received nullptr for compressed jpegr image");
553 return ERROR_JPEGR_INVALID_NULL_PTR;
554 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530555 if (jpeg_image_info_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530556 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000557 return ERROR_JPEGR_INVALID_NULL_PTR;
558 }
559
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530560 jpegr_compressed_struct primary_image, gainmap_image;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530561 status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530562 if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
563 return status;
564 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000565
Ram Mohanb2359cd2023-07-28 14:33:49 +0530566 JpegDecoderHelper jpeg_dec_obj_hdr;
567 if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
568 &jpeg_image_info_ptr->width,
569 &jpeg_image_info_ptr->height,
570 jpeg_image_info_ptr->iccData,
571 jpeg_image_info_ptr->exifData)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000572 return ERROR_JPEGR_DECODE_ERROR;
573 }
574
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530575 return status;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000576}
577
Dichen Zhang636f5242022-12-07 20:25:44 +0000578/* Decode API */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530579status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
580 float max_display_boost, jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000581 ultrahdr_output_format output_format,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530582 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
583 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanb0375052023-05-20 04:03:48 +0530584 ALOGE("received nullptr for compressed jpegr image");
585 return ERROR_JPEGR_INVALID_NULL_PTR;
586 }
Ram Mohanb0375052023-05-20 04:03:48 +0530587 if (dest == nullptr || dest->data == nullptr) {
588 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000589 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000590 }
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000591 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530592 ALOGE("received bad value for max_display_boost %f", max_display_boost);
593 return ERROR_JPEGR_INVALID_INPUT_TYPE;
594 }
Ram Mohanb0375052023-05-20 04:03:48 +0530595 if (exif != nullptr && exif->data == nullptr) {
596 ALOGE("received nullptr address for exif data");
597 return ERROR_JPEGR_INVALID_INPUT_TYPE;
598 }
Ram Mohanb0375052023-05-20 04:03:48 +0530599 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
600 ALOGE("received bad value for output format %d", output_format);
601 return ERROR_JPEGR_INVALID_INPUT_TYPE;
602 }
603
Ram Mohanb2359cd2023-07-28 14:33:49 +0530604 jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530605 status_t status =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530606 extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530607 if (status != NO_ERROR) {
608 if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
609 ALOGE("received invalid compressed jpegr image");
610 return status;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000611 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000612 }
613
Ram Mohanb2359cd2023-07-28 14:33:49 +0530614 JpegDecoderHelper jpeg_dec_obj_yuv420;
615 if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
616 (output_format == ULTRAHDR_OUTPUT_SDR))) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400617 return ERROR_JPEGR_DECODE_ERROR;
618 }
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530619
620 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530621 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
622 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
623 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530624 return ERROR_JPEGR_CALCULATION_ERROR;
625 }
626 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530627 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
628 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
629 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530630 return ERROR_JPEGR_CALCULATION_ERROR;
631 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530632 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400633
Dichen Zhangb80e2262023-03-08 06:59:51 +0000634 if (exif != nullptr) {
635 if (exif->data == nullptr) {
636 return ERROR_JPEGR_INVALID_NULL_PTR;
637 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530638 if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
Dichen Zhangb80e2262023-03-08 06:59:51 +0000639 return ERROR_JPEGR_BUFFER_TOO_SMALL;
640 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530641 memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
642 exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
Dichen Zhangb80e2262023-03-08 06:59:51 +0000643 }
644
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530645 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530646 dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
647 dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
648 memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
649 dest->width * dest->height * 4);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530650 return NO_ERROR;
651 }
652
Ram Mohanb2359cd2023-07-28 14:33:49 +0530653 JpegDecoderHelper jpeg_dec_obj_gm;
654 if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530655 return ERROR_JPEGR_DECODE_ERROR;
656 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530657 if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
658 jpeg_dec_obj_gm.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530659 return ERROR_JPEGR_CALCULATION_ERROR;
660 }
661
Ram Mohanb2359cd2023-07-28 14:33:49 +0530662 jpegr_uncompressed_struct gainmap_image;
663 gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
664 gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
665 gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000666
Ram Mohanb2359cd2023-07-28 14:33:49 +0530667 if (gainmap_image_ptr != nullptr) {
668 gainmap_image_ptr->width = gainmap_image.width;
669 gainmap_image_ptr->height = gainmap_image.height;
670 int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
671 gainmap_image_ptr->data = malloc(size);
672 memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530673 }
674
675 ultrahdr_metadata_struct uhdr_metadata;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530676 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
677 jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530678 return ERROR_JPEGR_INVALID_METADATA;
679 }
680
681 if (metadata != nullptr) {
682 metadata->version = uhdr_metadata.version;
683 metadata->minContentBoost = uhdr_metadata.minContentBoost;
684 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
685 metadata->gamma = uhdr_metadata.gamma;
686 metadata->offsetSdr = uhdr_metadata.offsetSdr;
687 metadata->offsetHdr = uhdr_metadata.offsetHdr;
688 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
689 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
690 }
691
Ram Mohanb2359cd2023-07-28 14:33:49 +0530692 jpegr_uncompressed_struct yuv420_image;
693 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
694 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
695 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
696 yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
697 jpeg_dec_obj_yuv420.getICCSize());
698 yuv420_image.luma_stride = yuv420_image.width;
699 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
700 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
701 yuv420_image.chroma_stride = yuv420_image.width >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400702
Ram Mohanb2359cd2023-07-28 14:33:49 +0530703 JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700704 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000705 return NO_ERROR;
706}
707
Ram Mohanb2359cd2023-07-28 14:33:49 +0530708status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
709 JpegEncoderHelper* jpeg_enc_obj_ptr) {
710 if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000711 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700712 }
713
Nick Deakin0db53ee2023-05-19 17:14:45 -0400714 // Don't need to convert YUV to Bt601 since single channel
Ram Mohaneca81942023-07-29 14:41:48 +0530715 if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
716 gainmap_image_ptr->width, gainmap_image_ptr->height,
717 gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
718 nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400719 return ERROR_JPEGR_ENCODE_ERROR;
720 }
721
Dichen Zhang6947d532022-10-22 02:16:21 +0000722 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700723}
724
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800725const int kJobSzInRows = 16;
726static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
727 "align job size to kMapDimensionScaleFactor");
728
729class JobQueue {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530730public:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800731 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
732 void enqueueJob(size_t rowStart, size_t rowEnd);
733 void markQueueForEnd();
734 void reset();
735
Ram Mohanb2359cd2023-07-28 14:33:49 +0530736private:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800737 bool mQueuedAllJobs = false;
738 std::deque<std::tuple<size_t, size_t>> mJobs;
739 std::mutex mMutex;
740 std::condition_variable mCv;
741};
742
743bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
744 std::unique_lock<std::mutex> lock{mMutex};
745 while (true) {
746 if (mJobs.empty()) {
747 if (mQueuedAllJobs) {
748 return false;
749 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000750 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800751 }
752 } else {
753 auto it = mJobs.begin();
754 rowStart = std::get<0>(*it);
755 rowEnd = std::get<1>(*it);
756 mJobs.erase(it);
757 return true;
758 }
759 }
760 return false;
761}
762
763void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
764 std::unique_lock<std::mutex> lock{mMutex};
765 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
766 lock.unlock();
767 mCv.notify_one();
768}
769
770void JobQueue::markQueueForEnd() {
771 std::unique_lock<std::mutex> lock{mMutex};
772 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000773 lock.unlock();
774 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800775}
776
777void JobQueue::reset() {
778 std::unique_lock<std::mutex> lock{mMutex};
779 mJobs.clear();
780 mQueuedAllJobs = false;
781}
782
Ram Mohanb2359cd2023-07-28 14:33:49 +0530783status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
784 jr_uncompressed_ptr p010_image_ptr,
785 ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
786 jr_uncompressed_ptr dest, bool sdr_is_601) {
787 if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530788 dest == nullptr || yuv420_image_ptr->data == nullptr ||
789 yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
790 p010_image_ptr->chroma_data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000791 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700792 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530793 if (yuv420_image_ptr->width != p010_image_ptr->width ||
794 yuv420_image_ptr->height != p010_image_ptr->height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400795 return ERROR_JPEGR_RESOLUTION_MISMATCH;
796 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530797 if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
798 p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500799 return ERROR_JPEGR_INVALID_COLORGAMUT;
800 }
801
Ram Mohanb2359cd2023-07-28 14:33:49 +0530802 size_t image_width = yuv420_image_ptr->width;
803 size_t image_height = yuv420_image_ptr->height;
Ram Mohan43c3a802023-07-24 18:33:49 +0530804 size_t map_width = static_cast<size_t>(
805 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
806 size_t map_height = static_cast<size_t>(
807 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400808
Ram Mohanb2359cd2023-07-28 14:33:49 +0530809 dest->data = new uint8_t[map_width * map_height];
Ram Mohan43c3a802023-07-24 18:33:49 +0530810 dest->width = map_width;
811 dest->height = map_height;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000812 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530813 dest->luma_stride = map_width;
814 dest->chroma_data = nullptr;
815 dest->chroma_stride = 0;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400816 std::unique_ptr<uint8_t[]> map_data;
817 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
818
Nick Deakin6bd90432022-11-20 16:26:37 -0500819 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohanebfb3732023-08-21 18:12:53 +0530820 float hdr_white_nits;
Nick Deakin01759062023-02-02 18:21:43 -0500821 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000822 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000823 hdrInvOetf = identityConversion;
Ram Mohanebfb3732023-08-21 18:12:53 +0530824 // Note: this will produce clipping if the input exceeds kHlgMaxNits.
825 // TODO: TF LINEAR will be deprecated.
826 hdr_white_nits = kHlgMaxNits;
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000827 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000828 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800829#if USE_HLG_INVOETF_LUT
830 hdrInvOetf = hlgInvOetfLUT;
831#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500832 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800833#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500834 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500835 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000836 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800837#if USE_PQ_INVOETF_LUT
838 hdrInvOetf = pqInvOetfLUT;
839#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500840 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800841#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500842 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500843 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000844 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000845 // Should be impossible to hit after input validation.
846 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500847 }
848
Nick Deakina2215292023-02-14 21:40:06 -0500849 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
850 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400851 metadata->gamma = 1.0f;
852 metadata->offsetSdr = 0.0f;
853 metadata->offsetHdr = 0.0f;
854 metadata->hdrCapacityMin = 1.0f;
855 metadata->hdrCapacityMax = metadata->maxContentBoost;
856
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700857 float log2MinBoost = log2(metadata->minContentBoost);
858 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500859
Ram Mohanb2359cd2023-07-28 14:33:49 +0530860 ColorTransformFn hdrGamutConversionFn =
861 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500862
863 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400864 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530865 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000866 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500867 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400868 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500869 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000870 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500871 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400872 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500873 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000874 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500875 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400876 sdrYuvToRgbFn = bt2100YuvToRgb;
877 break;
878 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
879 // Should be impossible to hit after input validation.
880 return ERROR_JPEGR_INVALID_COLORGAMUT;
881 }
882 if (sdr_is_601) {
883 sdrYuvToRgbFn = p3YuvToRgb;
884 }
885
886 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530887 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400888 case ULTRAHDR_COLORGAMUT_BT709:
889 hdrYuvToRgbFn = srgbYuvToRgb;
890 break;
891 case ULTRAHDR_COLORGAMUT_P3:
892 hdrYuvToRgbFn = p3YuvToRgb;
893 break;
894 case ULTRAHDR_COLORGAMUT_BT2100:
895 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500896 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000897 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500898 // Should be impossible to hit after input validation.
899 return ERROR_JPEGR_INVALID_COLORGAMUT;
900 }
901
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800902 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800903 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
904 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
905 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500906
Ram Mohanb2359cd2023-07-28 14:33:49 +0530907 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
908 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
909 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
910 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800911 size_t rowStart, rowEnd;
912 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
913 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530914 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530915 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400916 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
917 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800918#if USE_SRGB_INVOETF_LUT
919 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
920#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800921 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800922#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800923 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
924
Ram Mohanb2359cd2023-07-28 14:33:49 +0530925 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400926 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800927 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
928 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
929 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
930
Ram Mohan43c3a802023-07-24 18:33:49 +0530931 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800932 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530933 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800934 }
935 }
936 }
937 };
938
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800939 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500940 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800941 for (int th = 0; th < threads - 1; th++) {
942 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400943 }
944
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800945 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
946 for (size_t rowStart = 0; rowStart < map_height;) {
947 size_t rowEnd = std::min(rowStart + rowStep, map_height);
948 jobQueue.enqueueJob(rowStart, rowEnd);
949 rowStart = rowEnd;
950 }
951 jobQueue.markQueueForEnd();
952 generateMap();
953 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
954
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400955 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000956 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700957}
958
Ram Mohanb2359cd2023-07-28 14:33:49 +0530959status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
960 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
961 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700962 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530963 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530964 dest == nullptr || yuv420_image_ptr->data == nullptr ||
965 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000966 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700967 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530968 if (metadata->version.compare(kJpegrVersion)) {
969 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
970 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400971 }
972 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530973 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
974 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400975 }
976 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530977 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
978 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400979 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530980 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
981 metadata->hdrCapacityMax != metadata->maxContentBoost) {
982 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
983 metadata->hdrCapacityMax);
984 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400985 }
986
Ram Mohan9b3d6852023-05-26 00:09:50 +0530987 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +0530988 size_t image_width = yuv420_image_ptr->width;
989 size_t image_height = yuv420_image_ptr->height;
Ram Mohan43c3a802023-07-24 18:33:49 +0530990 size_t map_width = static_cast<size_t>(
991 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
992 size_t map_height = static_cast<size_t>(
993 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Ram Mohanb2359cd2023-07-28 14:33:49 +0530994 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohaneca81942023-07-29 14:41:48 +0530995 ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
996 "resolution is %dx%d, received gain map resolution is %dx%d",
997 (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
Ram Mohan9b3d6852023-05-26 00:09:50 +0530998 return ERROR_JPEGR_INVALID_INPUT_TYPE;
999 }
1000
Ram Mohanb2359cd2023-07-28 14:33:49 +05301001 dest->width = yuv420_image_ptr->width;
1002 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +05301003 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001004 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001005 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301006
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001007 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301008 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
1009 &jobQueue, &idwTable, output_format, &gainLUT,
1010 display_boost]() -> void {
1011 size_t width = yuv420_image_ptr->width;
1012 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001013
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001014 size_t rowStart, rowEnd;
1015 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1016 for (size_t y = rowStart; y < rowEnd; ++y) {
1017 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301018 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001019 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1020 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1021 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001022#if USE_SRGB_INVOETF_LUT
1023 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1024#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001025 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001026#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001027 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001028 // TODO: determine map scaling factor based on actual map dims
1029 size_t map_scale_factor = kMapDimensionScaleFactor;
1030 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1031 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1032 // later.
1033 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301034 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001035 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301036 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001037 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001038
Dichen Zhang10959a42023-04-10 16:28:16 -07001039#if USE_APPLY_GAIN_LUT
1040 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001041#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001042 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001043#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001044 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001045 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001046
1047 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301048 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001049 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1050 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1051 break;
1052 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301053 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001054#if USE_HLG_OETF_LUT
1055 ColorTransformFn hdrOetf = hlgOetfLUT;
1056#else
1057 ColorTransformFn hdrOetf = hlgOetf;
1058#endif
1059 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1060 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1061 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1062 break;
1063 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301064 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301065#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001066 ColorTransformFn hdrOetf = pqOetfLUT;
1067#else
1068 ColorTransformFn hdrOetf = pqOetf;
1069#endif
1070 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1071 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1072 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1073 break;
1074 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301075 default: {
1076 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001077 // Should be impossible to hit after input validation.
1078 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001079 }
1080 }
1081 }
1082 };
1083
1084 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1085 std::vector<std::thread> workers;
1086 for (int th = 0; th < threads - 1; th++) {
1087 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001088 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301089 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1090 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1091 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001092 jobQueue.enqueueJob(rowStart, rowEnd);
1093 rowStart = rowEnd;
1094 }
1095 jobQueue.markQueueForEnd();
1096 applyRecMap();
1097 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001098 return NO_ERROR;
1099}
1100
Ram Mohanb2359cd2023-07-28 14:33:49 +05301101status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1102 jr_compressed_ptr primary_jpg_image_ptr,
1103 jr_compressed_ptr gainmap_jpg_image_ptr) {
1104 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001105 return ERROR_JPEGR_INVALID_NULL_PTR;
1106 }
1107
1108 MessageHandler msg_handler;
1109 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301110 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1111 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1112 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001113 DataSegmentDataSource data_source(seg);
1114 JpegInfoBuilder jpeg_info_builder;
1115 jpeg_info_builder.SetImageLimit(2);
1116 JpegScanner jpeg_scanner(&msg_handler);
1117 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1118 data_source.Reset();
1119
1120 if (jpeg_scanner.HasError()) {
1121 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1122 }
1123
1124 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1125 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001126
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301127 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001128 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1129 }
1130
Ram Mohanb2359cd2023-07-28 14:33:49 +05301131 if (primary_jpg_image_ptr != nullptr) {
1132 primary_jpg_image_ptr->data =
1133 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1134 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001135 }
1136
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301137 if (image_ranges.size() == 1) {
1138 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1139 }
1140
Ram Mohanb2359cd2023-07-28 14:33:49 +05301141 if (gainmap_jpg_image_ptr != nullptr) {
1142 gainmap_jpg_image_ptr->data =
1143 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1144 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001145 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001146
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301147 // TODO: choose primary image and gain map image carefully
1148 if (image_ranges.size() > 2) {
1149 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1150 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001151 }
1152
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301153 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001154}
1155
Dichen Zhangd18bc302022-12-16 20:55:24 +00001156// JPEG/R structure:
1157// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001158//
1159// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001160// APP1 (ff e1)
1161// 2 bytes of length (2 + length of exif package)
1162// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001163//
1164// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001165// 2 bytes of length (2 + 29 + length of xmp package)
1166// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001167// XMP
1168//
1169// (Required, MPF package) APP2 (ff e2)
1170// 2 bytes of length
1171// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001172//
1173// (Required) primary image (without the first two bytes (SOI), may have other packages)
1174//
Dichen Zhang61ede362023-02-22 18:50:13 +00001175// SOI (ff d8)
1176//
1177// (Required, XMP package) APP1 (ff e1)
1178// 2 bytes of length (2 + 29 + length of xmp package)
1179// name space ("http://ns.adobe.com/xap/1.0/\0")
1180// XMP
1181//
Dichen Zhang10959a42023-04-10 16:28:16 -07001182// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001183//
1184// Metadata versions we are using:
1185// ECMA TR-98 for JFIF marker
1186// Exif 2.2 spec for EXIF marker
1187// Adobe XMP spec part 3 for XMP marker
1188// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301189status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
1190 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
1191 size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001192 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301193 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1194 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001195 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001196 }
Nick Deakin094946b2023-06-09 11:58:41 -04001197 if (metadata->version.compare("1.0")) {
1198 ALOGE("received bad value for version: %s", metadata->version.c_str());
1199 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1200 }
1201 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301202 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301203 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301204 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1205 }
Nick Deakin094946b2023-06-09 11:58:41 -04001206 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1207 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301208 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001209 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1210 }
Nick Deakin094946b2023-06-09 11:58:41 -04001211 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301212 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
Nick Deakin094946b2023-06-09 11:58:41 -04001213 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1214 }
Nick Deakin094946b2023-06-09 11:58:41 -04001215 if (metadata->gamma <= 0.0f) {
1216 ALOGE("received bad value for gamma %f", metadata->gamma);
1217 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1218 }
1219
Dichen Zhang15345ea2023-02-23 23:54:32 +00001220 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301221 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001222
Dichen Zhang15345ea2023-02-23 23:54:32 +00001223 // calculate secondary image length first, because the length will be written into the primary
1224 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001225 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001226 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301227 + nameSpaceLength /* 29 bytes length of name space including \0 */
1228 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001229 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301230 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001231 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001232 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001233 // same as primary
1234 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001235
Dichen Zhang15345ea2023-02-23 23:54:32 +00001236 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001237 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001238 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001239 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1240 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001241
1242 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001243 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001244 const int length = 2 + exif->length;
1245 const uint8_t lengthH = ((length >> 8) & 0xff);
1246 const uint8_t lengthL = (length & 0xff);
1247 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1248 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1249 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1250 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1251 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1252 }
1253
1254 // Prepare and write XMP
1255 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001256 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001257 const uint8_t lengthH = ((length >> 8) & 0xff);
1258 const uint8_t lengthL = (length & 0xff);
1259 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1260 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1261 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1262 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1263 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001264 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1265 }
1266
Nick Deakin0db53ee2023-05-19 17:14:45 -04001267 // Write ICC
1268 if (icc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301269 const int length = icc_size + 2;
1270 const uint8_t lengthH = ((length >> 8) & 0xff);
1271 const uint8_t lengthL = (length & 0xff);
1272 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1273 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1274 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1275 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1276 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001277 }
1278
Dichen Zhang61ede362023-02-22 18:50:13 +00001279 // Prepare and write MPF
1280 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301281 const int length = 2 + calculateMpfSize();
1282 const uint8_t lengthH = ((length >> 8) & 0xff);
1283 const uint8_t lengthL = (length & 0xff);
1284 int primary_image_size = pos + length + primary_jpg_image_ptr->length;
1285 // between APP2 + package size + signature
1286 // ff e2 00 58 4d 50 46 00
1287 // 2 + 2 + 4 = 8 (bytes)
1288 // and ff d8 sign of the secondary image
1289 int secondary_image_offset = primary_image_size - pos - 8;
1290 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1291 secondary_image_size, secondary_image_offset);
1292 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1293 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1294 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1295 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1296 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001297 }
1298
1299 // Write primary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301300 JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
1301 primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001302 // Finish primary image
1303
Dichen Zhang10959a42023-04-10 16:28:16 -07001304 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001305 // Write SOI
1306 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1307 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1308
1309 // Prepare and write XMP
1310 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001311 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001312 const uint8_t lengthH = ((length >> 8) & 0xff);
1313 const uint8_t lengthL = (length & 0xff);
1314 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1315 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1316 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1317 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1318 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1319 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1320 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001321
1322 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301323 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1324 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001325
1326 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001327 dest->length = pos;
1328
Dichen Zhangd18bc302022-12-16 20:55:24 +00001329 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001330 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001331}
1332
Dichen Zhang61ede362023-02-22 18:50:13 +00001333status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001334 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001335 return ERROR_JPEGR_INVALID_NULL_PTR;
1336 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301337 if (src->width != dest->width || src->height != dest->height) {
1338 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001339 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301340 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
Ram Mohanb2359cd2023-07-28 14:33:49 +05301341 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001342 for (size_t y = 0; y < src->height; ++y) {
Ram Mohaneca81942023-07-29 14:41:48 +05301343 uint16_t* src_y_row = src_y_data + y * src->luma_stride;
1344 uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001345 for (size_t x = 0; x < src->width; ++x) {
Ram Mohaneca81942023-07-29 14:41:48 +05301346 uint16_t y_uint = src_y_row[x] >> 6;
1347 dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1348 }
1349 if (dest->width != dest->luma_stride) {
1350 memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
1351 }
1352 }
1353 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1354 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1355 size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
1356 uint8_t* dst_v_data = dst_u_data + dst_v_offset;
1357 for (size_t y = 0; y < src->height / 2; ++y) {
1358 uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
1359 uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
1360 uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
1361 for (size_t x = 0; x < src->width / 2; ++x) {
1362 uint16_t u_uint = src_uv_row[x << 1] >> 6;
1363 uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
1364 dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1365 dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1366 }
1367 if (dest->width / 2 != dest->chroma_stride) {
1368 memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
1369 memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001370 }
1371 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001372 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001373 return NO_ERROR;
1374}
1375
Ram Mohanb2359cd2023-07-28 14:33:49 +05301376status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001377 ultrahdr_color_gamut dest_encoding) {
1378 if (image == nullptr) {
1379 return ERROR_JPEGR_INVALID_NULL_PTR;
1380 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301381 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1382 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001383 return ERROR_JPEGR_INVALID_COLORGAMUT;
1384 }
1385
1386 ColorTransformFn conversionFn = nullptr;
1387 switch (src_encoding) {
1388 case ULTRAHDR_COLORGAMUT_BT709:
1389 switch (dest_encoding) {
1390 case ULTRAHDR_COLORGAMUT_BT709:
1391 return NO_ERROR;
1392 case ULTRAHDR_COLORGAMUT_P3:
1393 conversionFn = yuv709To601;
1394 break;
1395 case ULTRAHDR_COLORGAMUT_BT2100:
1396 conversionFn = yuv709To2100;
1397 break;
1398 default:
1399 // Should be impossible to hit after input validation
1400 return ERROR_JPEGR_INVALID_COLORGAMUT;
1401 }
1402 break;
1403 case ULTRAHDR_COLORGAMUT_P3:
1404 switch (dest_encoding) {
1405 case ULTRAHDR_COLORGAMUT_BT709:
1406 conversionFn = yuv601To709;
1407 break;
1408 case ULTRAHDR_COLORGAMUT_P3:
1409 return NO_ERROR;
1410 case ULTRAHDR_COLORGAMUT_BT2100:
1411 conversionFn = yuv601To2100;
1412 break;
1413 default:
1414 // Should be impossible to hit after input validation
1415 return ERROR_JPEGR_INVALID_COLORGAMUT;
1416 }
1417 break;
1418 case ULTRAHDR_COLORGAMUT_BT2100:
1419 switch (dest_encoding) {
1420 case ULTRAHDR_COLORGAMUT_BT709:
1421 conversionFn = yuv2100To709;
1422 break;
1423 case ULTRAHDR_COLORGAMUT_P3:
1424 conversionFn = yuv2100To601;
1425 break;
1426 case ULTRAHDR_COLORGAMUT_BT2100:
1427 return NO_ERROR;
1428 default:
1429 // Should be impossible to hit after input validation
1430 return ERROR_JPEGR_INVALID_COLORGAMUT;
1431 }
1432 break;
1433 default:
1434 // Should be impossible to hit after input validation
1435 return ERROR_JPEGR_INVALID_COLORGAMUT;
1436 }
1437
1438 if (conversionFn == nullptr) {
1439 // Should be impossible to hit after input validation
1440 return ERROR_JPEGR_INVALID_COLORGAMUT;
1441 }
1442
1443 for (size_t y = 0; y < image->height / 2; ++y) {
1444 for (size_t x = 0; x < image->width / 2; ++x) {
1445 transformYuv420(image, x, y, conversionFn);
1446 }
1447 }
1448
1449 return NO_ERROR;
1450}
1451
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001452} // namespace android::ultrahdr