blob: dc439d785aec295aa61b5441d9140dd4c4c821f1 [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 Mohancd3f6372023-06-02 15:16:15 +0530820 float hdr_white_nits = kSdrWhiteNits;
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;
824 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000825 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800826#if USE_HLG_INVOETF_LUT
827 hdrInvOetf = hlgInvOetfLUT;
828#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500829 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800830#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500831 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500832 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000833 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800834#if USE_PQ_INVOETF_LUT
835 hdrInvOetf = pqInvOetfLUT;
836#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500837 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800838#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500839 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500840 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000841 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000842 // Should be impossible to hit after input validation.
843 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500844 }
845
Nick Deakina2215292023-02-14 21:40:06 -0500846 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
847 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400848 metadata->gamma = 1.0f;
849 metadata->offsetSdr = 0.0f;
850 metadata->offsetHdr = 0.0f;
851 metadata->hdrCapacityMin = 1.0f;
852 metadata->hdrCapacityMax = metadata->maxContentBoost;
853
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700854 float log2MinBoost = log2(metadata->minContentBoost);
855 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500856
Ram Mohanb2359cd2023-07-28 14:33:49 +0530857 ColorTransformFn hdrGamutConversionFn =
858 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500859
860 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400861 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530862 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000863 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500864 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400865 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500866 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000867 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500868 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400869 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500870 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000871 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500872 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400873 sdrYuvToRgbFn = bt2100YuvToRgb;
874 break;
875 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
876 // Should be impossible to hit after input validation.
877 return ERROR_JPEGR_INVALID_COLORGAMUT;
878 }
879 if (sdr_is_601) {
880 sdrYuvToRgbFn = p3YuvToRgb;
881 }
882
883 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530884 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400885 case ULTRAHDR_COLORGAMUT_BT709:
886 hdrYuvToRgbFn = srgbYuvToRgb;
887 break;
888 case ULTRAHDR_COLORGAMUT_P3:
889 hdrYuvToRgbFn = p3YuvToRgb;
890 break;
891 case ULTRAHDR_COLORGAMUT_BT2100:
892 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500893 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000894 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500895 // Should be impossible to hit after input validation.
896 return ERROR_JPEGR_INVALID_COLORGAMUT;
897 }
898
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800899 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800900 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
901 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
902 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500903
Ram Mohanb2359cd2023-07-28 14:33:49 +0530904 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
905 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
906 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
907 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800908 size_t rowStart, rowEnd;
909 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
910 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530911 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530912 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400913 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
914 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800915#if USE_SRGB_INVOETF_LUT
916 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
917#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800918 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800919#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800920 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
921
Ram Mohanb2359cd2023-07-28 14:33:49 +0530922 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400923 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800924 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
925 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
926 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
927
Ram Mohan43c3a802023-07-24 18:33:49 +0530928 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800929 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530930 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800931 }
932 }
933 }
934 };
935
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800936 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500937 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800938 for (int th = 0; th < threads - 1; th++) {
939 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400940 }
941
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800942 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
943 for (size_t rowStart = 0; rowStart < map_height;) {
944 size_t rowEnd = std::min(rowStart + rowStep, map_height);
945 jobQueue.enqueueJob(rowStart, rowEnd);
946 rowStart = rowEnd;
947 }
948 jobQueue.markQueueForEnd();
949 generateMap();
950 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
951
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400952 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000953 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700954}
955
Ram Mohanb2359cd2023-07-28 14:33:49 +0530956status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
957 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
958 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700959 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530960 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530961 dest == nullptr || yuv420_image_ptr->data == nullptr ||
962 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000963 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700964 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530965 if (metadata->version.compare(kJpegrVersion)) {
966 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
967 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400968 }
969 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530970 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
971 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400972 }
973 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530974 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
975 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400976 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530977 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
978 metadata->hdrCapacityMax != metadata->maxContentBoost) {
979 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
980 metadata->hdrCapacityMax);
981 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400982 }
983
Ram Mohan9b3d6852023-05-26 00:09:50 +0530984 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +0530985 size_t image_width = yuv420_image_ptr->width;
986 size_t image_height = yuv420_image_ptr->height;
Ram Mohan43c3a802023-07-24 18:33:49 +0530987 size_t map_width = static_cast<size_t>(
988 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
989 size_t map_height = static_cast<size_t>(
990 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Ram Mohanb2359cd2023-07-28 14:33:49 +0530991 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohaneca81942023-07-29 14:41:48 +0530992 ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
993 "resolution is %dx%d, received gain map resolution is %dx%d",
994 (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
Ram Mohan9b3d6852023-05-26 00:09:50 +0530995 return ERROR_JPEGR_INVALID_INPUT_TYPE;
996 }
997
Ram Mohanb2359cd2023-07-28 14:33:49 +0530998 dest->width = yuv420_image_ptr->width;
999 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +05301000 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001001 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001002 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301003
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001004 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301005 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
1006 &jobQueue, &idwTable, output_format, &gainLUT,
1007 display_boost]() -> void {
1008 size_t width = yuv420_image_ptr->width;
1009 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001010
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001011 size_t rowStart, rowEnd;
1012 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1013 for (size_t y = rowStart; y < rowEnd; ++y) {
1014 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301015 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001016 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1017 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1018 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001019#if USE_SRGB_INVOETF_LUT
1020 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1021#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001022 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001023#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001024 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001025 // TODO: determine map scaling factor based on actual map dims
1026 size_t map_scale_factor = kMapDimensionScaleFactor;
1027 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1028 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1029 // later.
1030 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301031 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001032 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301033 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001034 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001035
Dichen Zhang10959a42023-04-10 16:28:16 -07001036#if USE_APPLY_GAIN_LUT
1037 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001038#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001039 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001040#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001041 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001042 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001043
1044 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301045 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001046 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1047 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1048 break;
1049 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301050 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001051#if USE_HLG_OETF_LUT
1052 ColorTransformFn hdrOetf = hlgOetfLUT;
1053#else
1054 ColorTransformFn hdrOetf = hlgOetf;
1055#endif
1056 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1057 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1058 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1059 break;
1060 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301061 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301062#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001063 ColorTransformFn hdrOetf = pqOetfLUT;
1064#else
1065 ColorTransformFn hdrOetf = pqOetf;
1066#endif
1067 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1068 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1069 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1070 break;
1071 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301072 default: {
1073 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001074 // Should be impossible to hit after input validation.
1075 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001076 }
1077 }
1078 }
1079 };
1080
1081 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1082 std::vector<std::thread> workers;
1083 for (int th = 0; th < threads - 1; th++) {
1084 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001085 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301086 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1087 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1088 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001089 jobQueue.enqueueJob(rowStart, rowEnd);
1090 rowStart = rowEnd;
1091 }
1092 jobQueue.markQueueForEnd();
1093 applyRecMap();
1094 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001095 return NO_ERROR;
1096}
1097
Ram Mohanb2359cd2023-07-28 14:33:49 +05301098status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1099 jr_compressed_ptr primary_jpg_image_ptr,
1100 jr_compressed_ptr gainmap_jpg_image_ptr) {
1101 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001102 return ERROR_JPEGR_INVALID_NULL_PTR;
1103 }
1104
1105 MessageHandler msg_handler;
1106 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301107 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1108 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1109 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001110 DataSegmentDataSource data_source(seg);
1111 JpegInfoBuilder jpeg_info_builder;
1112 jpeg_info_builder.SetImageLimit(2);
1113 JpegScanner jpeg_scanner(&msg_handler);
1114 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1115 data_source.Reset();
1116
1117 if (jpeg_scanner.HasError()) {
1118 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1119 }
1120
1121 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1122 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001123
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301124 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001125 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1126 }
1127
Ram Mohanb2359cd2023-07-28 14:33:49 +05301128 if (primary_jpg_image_ptr != nullptr) {
1129 primary_jpg_image_ptr->data =
1130 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1131 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001132 }
1133
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301134 if (image_ranges.size() == 1) {
1135 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1136 }
1137
Ram Mohanb2359cd2023-07-28 14:33:49 +05301138 if (gainmap_jpg_image_ptr != nullptr) {
1139 gainmap_jpg_image_ptr->data =
1140 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1141 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001142 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001143
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301144 // TODO: choose primary image and gain map image carefully
1145 if (image_ranges.size() > 2) {
1146 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1147 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001148 }
1149
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301150 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001151}
1152
Dichen Zhangd18bc302022-12-16 20:55:24 +00001153// JPEG/R structure:
1154// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001155//
1156// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001157// APP1 (ff e1)
1158// 2 bytes of length (2 + length of exif package)
1159// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001160//
1161// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001162// 2 bytes of length (2 + 29 + length of xmp package)
1163// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001164// XMP
1165//
1166// (Required, MPF package) APP2 (ff e2)
1167// 2 bytes of length
1168// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001169//
1170// (Required) primary image (without the first two bytes (SOI), may have other packages)
1171//
Dichen Zhang61ede362023-02-22 18:50:13 +00001172// SOI (ff d8)
1173//
1174// (Required, XMP package) APP1 (ff e1)
1175// 2 bytes of length (2 + 29 + length of xmp package)
1176// name space ("http://ns.adobe.com/xap/1.0/\0")
1177// XMP
1178//
Dichen Zhang10959a42023-04-10 16:28:16 -07001179// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001180//
1181// Metadata versions we are using:
1182// ECMA TR-98 for JFIF marker
1183// Exif 2.2 spec for EXIF marker
1184// Adobe XMP spec part 3 for XMP marker
1185// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301186status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
1187 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
1188 size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001189 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301190 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1191 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001192 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001193 }
Nick Deakin094946b2023-06-09 11:58:41 -04001194 if (metadata->version.compare("1.0")) {
1195 ALOGE("received bad value for version: %s", metadata->version.c_str());
1196 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1197 }
1198 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301199 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301200 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301201 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1202 }
Nick Deakin094946b2023-06-09 11:58:41 -04001203 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1204 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301205 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001206 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1207 }
Nick Deakin094946b2023-06-09 11:58:41 -04001208 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301209 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
Nick Deakin094946b2023-06-09 11:58:41 -04001210 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1211 }
Nick Deakin094946b2023-06-09 11:58:41 -04001212 if (metadata->gamma <= 0.0f) {
1213 ALOGE("received bad value for gamma %f", metadata->gamma);
1214 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1215 }
1216
Dichen Zhang15345ea2023-02-23 23:54:32 +00001217 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301218 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001219
Dichen Zhang15345ea2023-02-23 23:54:32 +00001220 // calculate secondary image length first, because the length will be written into the primary
1221 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001222 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001223 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301224 + nameSpaceLength /* 29 bytes length of name space including \0 */
1225 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001226 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301227 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001228 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001229 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001230 // same as primary
1231 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001232
Dichen Zhang15345ea2023-02-23 23:54:32 +00001233 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001234 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001235 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001236 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1237 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001238
1239 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001240 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001241 const int length = 2 + exif->length;
1242 const uint8_t lengthH = ((length >> 8) & 0xff);
1243 const uint8_t lengthL = (length & 0xff);
1244 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1245 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1246 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1247 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1248 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1249 }
1250
1251 // Prepare and write XMP
1252 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001253 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001254 const uint8_t lengthH = ((length >> 8) & 0xff);
1255 const uint8_t lengthL = (length & 0xff);
1256 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1257 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1258 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1259 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1260 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001261 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1262 }
1263
Nick Deakin0db53ee2023-05-19 17:14:45 -04001264 // Write ICC
1265 if (icc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301266 const int length = icc_size + 2;
1267 const uint8_t lengthH = ((length >> 8) & 0xff);
1268 const uint8_t lengthL = (length & 0xff);
1269 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1270 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1271 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1272 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1273 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001274 }
1275
Dichen Zhang61ede362023-02-22 18:50:13 +00001276 // Prepare and write MPF
1277 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301278 const int length = 2 + calculateMpfSize();
1279 const uint8_t lengthH = ((length >> 8) & 0xff);
1280 const uint8_t lengthL = (length & 0xff);
1281 int primary_image_size = pos + length + primary_jpg_image_ptr->length;
1282 // between APP2 + package size + signature
1283 // ff e2 00 58 4d 50 46 00
1284 // 2 + 2 + 4 = 8 (bytes)
1285 // and ff d8 sign of the secondary image
1286 int secondary_image_offset = primary_image_size - pos - 8;
1287 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1288 secondary_image_size, secondary_image_offset);
1289 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1290 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1291 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1292 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1293 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001294 }
1295
1296 // Write primary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301297 JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
1298 primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001299 // Finish primary image
1300
Dichen Zhang10959a42023-04-10 16:28:16 -07001301 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001302 // Write SOI
1303 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1304 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1305
1306 // Prepare and write XMP
1307 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001308 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001309 const uint8_t lengthH = ((length >> 8) & 0xff);
1310 const uint8_t lengthL = (length & 0xff);
1311 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1312 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1313 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1314 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1315 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1316 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1317 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001318
1319 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301320 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1321 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001322
1323 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001324 dest->length = pos;
1325
Dichen Zhangd18bc302022-12-16 20:55:24 +00001326 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001327 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001328}
1329
Dichen Zhang61ede362023-02-22 18:50:13 +00001330status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001331 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001332 return ERROR_JPEGR_INVALID_NULL_PTR;
1333 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301334 if (src->width != dest->width || src->height != dest->height) {
1335 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001336 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301337 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
Ram Mohanb2359cd2023-07-28 14:33:49 +05301338 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001339 for (size_t y = 0; y < src->height; ++y) {
Ram Mohaneca81942023-07-29 14:41:48 +05301340 uint16_t* src_y_row = src_y_data + y * src->luma_stride;
1341 uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001342 for (size_t x = 0; x < src->width; ++x) {
Ram Mohaneca81942023-07-29 14:41:48 +05301343 uint16_t y_uint = src_y_row[x] >> 6;
1344 dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1345 }
1346 if (dest->width != dest->luma_stride) {
1347 memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
1348 }
1349 }
1350 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1351 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1352 size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
1353 uint8_t* dst_v_data = dst_u_data + dst_v_offset;
1354 for (size_t y = 0; y < src->height / 2; ++y) {
1355 uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
1356 uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
1357 uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
1358 for (size_t x = 0; x < src->width / 2; ++x) {
1359 uint16_t u_uint = src_uv_row[x << 1] >> 6;
1360 uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
1361 dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1362 dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1363 }
1364 if (dest->width / 2 != dest->chroma_stride) {
1365 memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
1366 memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001367 }
1368 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001369 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001370 return NO_ERROR;
1371}
1372
Ram Mohanb2359cd2023-07-28 14:33:49 +05301373status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001374 ultrahdr_color_gamut dest_encoding) {
1375 if (image == nullptr) {
1376 return ERROR_JPEGR_INVALID_NULL_PTR;
1377 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301378 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1379 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001380 return ERROR_JPEGR_INVALID_COLORGAMUT;
1381 }
1382
1383 ColorTransformFn conversionFn = nullptr;
1384 switch (src_encoding) {
1385 case ULTRAHDR_COLORGAMUT_BT709:
1386 switch (dest_encoding) {
1387 case ULTRAHDR_COLORGAMUT_BT709:
1388 return NO_ERROR;
1389 case ULTRAHDR_COLORGAMUT_P3:
1390 conversionFn = yuv709To601;
1391 break;
1392 case ULTRAHDR_COLORGAMUT_BT2100:
1393 conversionFn = yuv709To2100;
1394 break;
1395 default:
1396 // Should be impossible to hit after input validation
1397 return ERROR_JPEGR_INVALID_COLORGAMUT;
1398 }
1399 break;
1400 case ULTRAHDR_COLORGAMUT_P3:
1401 switch (dest_encoding) {
1402 case ULTRAHDR_COLORGAMUT_BT709:
1403 conversionFn = yuv601To709;
1404 break;
1405 case ULTRAHDR_COLORGAMUT_P3:
1406 return NO_ERROR;
1407 case ULTRAHDR_COLORGAMUT_BT2100:
1408 conversionFn = yuv601To2100;
1409 break;
1410 default:
1411 // Should be impossible to hit after input validation
1412 return ERROR_JPEGR_INVALID_COLORGAMUT;
1413 }
1414 break;
1415 case ULTRAHDR_COLORGAMUT_BT2100:
1416 switch (dest_encoding) {
1417 case ULTRAHDR_COLORGAMUT_BT709:
1418 conversionFn = yuv2100To709;
1419 break;
1420 case ULTRAHDR_COLORGAMUT_P3:
1421 conversionFn = yuv2100To601;
1422 break;
1423 case ULTRAHDR_COLORGAMUT_BT2100:
1424 return NO_ERROR;
1425 default:
1426 // Should be impossible to hit after input validation
1427 return ERROR_JPEGR_INVALID_COLORGAMUT;
1428 }
1429 break;
1430 default:
1431 // Should be impossible to hit after input validation
1432 return ERROR_JPEGR_INVALID_COLORGAMUT;
1433 }
1434
1435 if (conversionFn == nullptr) {
1436 // Should be impossible to hit after input validation
1437 return ERROR_JPEGR_INVALID_COLORGAMUT;
1438 }
1439
1440 for (size_t y = 0; y < image->height / 2; ++y) {
1441 for (size_t x = 0; x < image->width / 2; ++x) {
1442 transformYuv420(image, x, y, conversionFn);
1443 }
1444 }
1445
1446 return NO_ERROR;
1447}
1448
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001449} // namespace android::ultrahdr