blob: ae8184547a5751012707473cdbcb30bb693ac702 [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 Mohan2c3d7fb2023-08-12 04:37:39 +0530804 size_t map_width = image_width / kMapDimensionScaleFactor;
805 size_t map_height = image_height / kMapDimensionScaleFactor;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400806
Ram Mohanb2359cd2023-07-28 14:33:49 +0530807 dest->data = new uint8_t[map_width * map_height];
Ram Mohan43c3a802023-07-24 18:33:49 +0530808 dest->width = map_width;
809 dest->height = map_height;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000810 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530811 dest->luma_stride = map_width;
812 dest->chroma_data = nullptr;
813 dest->chroma_stride = 0;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400814 std::unique_ptr<uint8_t[]> map_data;
815 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
816
Nick Deakin6bd90432022-11-20 16:26:37 -0500817 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohancd3f6372023-06-02 15:16:15 +0530818 float hdr_white_nits = kSdrWhiteNits;
Nick Deakin01759062023-02-02 18:21:43 -0500819 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000820 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000821 hdrInvOetf = identityConversion;
822 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000823 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800824#if USE_HLG_INVOETF_LUT
825 hdrInvOetf = hlgInvOetfLUT;
826#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500827 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800828#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500829 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500830 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000831 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800832#if USE_PQ_INVOETF_LUT
833 hdrInvOetf = pqInvOetfLUT;
834#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500835 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800836#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500837 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500838 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000839 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000840 // Should be impossible to hit after input validation.
841 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500842 }
843
Nick Deakina2215292023-02-14 21:40:06 -0500844 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
845 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400846 metadata->gamma = 1.0f;
847 metadata->offsetSdr = 0.0f;
848 metadata->offsetHdr = 0.0f;
849 metadata->hdrCapacityMin = 1.0f;
850 metadata->hdrCapacityMax = metadata->maxContentBoost;
851
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700852 float log2MinBoost = log2(metadata->minContentBoost);
853 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500854
Ram Mohanb2359cd2023-07-28 14:33:49 +0530855 ColorTransformFn hdrGamutConversionFn =
856 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500857
858 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400859 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530860 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000861 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500862 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400863 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500864 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000865 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500866 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400867 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500868 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000869 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500870 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400871 sdrYuvToRgbFn = bt2100YuvToRgb;
872 break;
873 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
874 // Should be impossible to hit after input validation.
875 return ERROR_JPEGR_INVALID_COLORGAMUT;
876 }
877 if (sdr_is_601) {
878 sdrYuvToRgbFn = p3YuvToRgb;
879 }
880
881 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530882 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400883 case ULTRAHDR_COLORGAMUT_BT709:
884 hdrYuvToRgbFn = srgbYuvToRgb;
885 break;
886 case ULTRAHDR_COLORGAMUT_P3:
887 hdrYuvToRgbFn = p3YuvToRgb;
888 break;
889 case ULTRAHDR_COLORGAMUT_BT2100:
890 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500891 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000892 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500893 // Should be impossible to hit after input validation.
894 return ERROR_JPEGR_INVALID_COLORGAMUT;
895 }
896
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800897 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800898 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
899 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
900 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500901
Ram Mohanb2359cd2023-07-28 14:33:49 +0530902 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
903 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
904 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
905 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800906 size_t rowStart, rowEnd;
907 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
908 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530909 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530910 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400911 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
912 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800913#if USE_SRGB_INVOETF_LUT
914 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
915#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800916 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800917#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800918 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
919
Ram Mohanb2359cd2023-07-28 14:33:49 +0530920 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400921 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800922 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
923 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
924 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
925
Ram Mohan43c3a802023-07-24 18:33:49 +0530926 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800927 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530928 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800929 }
930 }
931 }
932 };
933
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800934 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500935 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800936 for (int th = 0; th < threads - 1; th++) {
937 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400938 }
939
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800940 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
941 for (size_t rowStart = 0; rowStart < map_height;) {
942 size_t rowEnd = std::min(rowStart + rowStep, map_height);
943 jobQueue.enqueueJob(rowStart, rowEnd);
944 rowStart = rowEnd;
945 }
946 jobQueue.markQueueForEnd();
947 generateMap();
948 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
949
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400950 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000951 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700952}
953
Ram Mohanb2359cd2023-07-28 14:33:49 +0530954status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
955 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
956 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700957 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530958 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530959 dest == nullptr || yuv420_image_ptr->data == nullptr ||
960 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000961 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700962 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530963 if (metadata->version.compare(kJpegrVersion)) {
964 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
965 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400966 }
967 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530968 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
969 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400970 }
971 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530972 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
973 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400974 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530975 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
976 metadata->hdrCapacityMax != metadata->maxContentBoost) {
977 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
978 metadata->hdrCapacityMax);
979 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400980 }
981
Ram Mohan9b3d6852023-05-26 00:09:50 +0530982 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +0530983 size_t image_width = yuv420_image_ptr->width;
984 size_t image_height = yuv420_image_ptr->height;
Ram Mohan2c3d7fb2023-08-12 04:37:39 +0530985 size_t map_width = image_width / kMapDimensionScaleFactor;
986 size_t map_height = image_height / kMapDimensionScaleFactor;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530987 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohaneca81942023-07-29 14:41:48 +0530988 ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
989 "resolution is %dx%d, received gain map resolution is %dx%d",
990 (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
Ram Mohan9b3d6852023-05-26 00:09:50 +0530991 return ERROR_JPEGR_INVALID_INPUT_TYPE;
992 }
993
Ram Mohanb2359cd2023-07-28 14:33:49 +0530994 dest->width = yuv420_image_ptr->width;
995 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530996 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000997 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -0700998 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +0530999
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001000 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301001 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
1002 &jobQueue, &idwTable, output_format, &gainLUT,
1003 display_boost]() -> void {
1004 size_t width = yuv420_image_ptr->width;
1005 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001006
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001007 size_t rowStart, rowEnd;
1008 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1009 for (size_t y = rowStart; y < rowEnd; ++y) {
1010 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301011 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001012 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1013 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1014 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001015#if USE_SRGB_INVOETF_LUT
1016 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1017#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001018 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001019#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001020 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001021 // TODO: determine map scaling factor based on actual map dims
1022 size_t map_scale_factor = kMapDimensionScaleFactor;
1023 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1024 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1025 // later.
1026 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301027 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001028 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301029 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001030 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001031
Dichen Zhang10959a42023-04-10 16:28:16 -07001032#if USE_APPLY_GAIN_LUT
1033 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001034#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001035 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001036#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001037 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001038 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001039
1040 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301041 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001042 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1043 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1044 break;
1045 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301046 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001047#if USE_HLG_OETF_LUT
1048 ColorTransformFn hdrOetf = hlgOetfLUT;
1049#else
1050 ColorTransformFn hdrOetf = hlgOetf;
1051#endif
1052 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1053 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1054 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1055 break;
1056 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301057 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301058#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001059 ColorTransformFn hdrOetf = pqOetfLUT;
1060#else
1061 ColorTransformFn hdrOetf = pqOetf;
1062#endif
1063 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1064 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1065 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1066 break;
1067 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301068 default: {
1069 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001070 // Should be impossible to hit after input validation.
1071 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001072 }
1073 }
1074 }
1075 };
1076
1077 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1078 std::vector<std::thread> workers;
1079 for (int th = 0; th < threads - 1; th++) {
1080 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001081 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301082 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1083 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1084 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001085 jobQueue.enqueueJob(rowStart, rowEnd);
1086 rowStart = rowEnd;
1087 }
1088 jobQueue.markQueueForEnd();
1089 applyRecMap();
1090 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001091 return NO_ERROR;
1092}
1093
Ram Mohanb2359cd2023-07-28 14:33:49 +05301094status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1095 jr_compressed_ptr primary_jpg_image_ptr,
1096 jr_compressed_ptr gainmap_jpg_image_ptr) {
1097 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001098 return ERROR_JPEGR_INVALID_NULL_PTR;
1099 }
1100
1101 MessageHandler msg_handler;
1102 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301103 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1104 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1105 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001106 DataSegmentDataSource data_source(seg);
1107 JpegInfoBuilder jpeg_info_builder;
1108 jpeg_info_builder.SetImageLimit(2);
1109 JpegScanner jpeg_scanner(&msg_handler);
1110 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1111 data_source.Reset();
1112
1113 if (jpeg_scanner.HasError()) {
1114 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1115 }
1116
1117 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1118 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001119
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301120 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001121 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1122 }
1123
Ram Mohanb2359cd2023-07-28 14:33:49 +05301124 if (primary_jpg_image_ptr != nullptr) {
1125 primary_jpg_image_ptr->data =
1126 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1127 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001128 }
1129
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301130 if (image_ranges.size() == 1) {
1131 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1132 }
1133
Ram Mohanb2359cd2023-07-28 14:33:49 +05301134 if (gainmap_jpg_image_ptr != nullptr) {
1135 gainmap_jpg_image_ptr->data =
1136 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1137 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001138 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001139
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301140 // TODO: choose primary image and gain map image carefully
1141 if (image_ranges.size() > 2) {
1142 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1143 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001144 }
1145
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301146 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001147}
1148
Dichen Zhangd18bc302022-12-16 20:55:24 +00001149// JPEG/R structure:
1150// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001151//
1152// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001153// APP1 (ff e1)
1154// 2 bytes of length (2 + length of exif package)
1155// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001156//
1157// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001158// 2 bytes of length (2 + 29 + length of xmp package)
1159// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001160// XMP
1161//
1162// (Required, MPF package) APP2 (ff e2)
1163// 2 bytes of length
1164// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001165//
1166// (Required) primary image (without the first two bytes (SOI), may have other packages)
1167//
Dichen Zhang61ede362023-02-22 18:50:13 +00001168// SOI (ff d8)
1169//
1170// (Required, XMP package) APP1 (ff e1)
1171// 2 bytes of length (2 + 29 + length of xmp package)
1172// name space ("http://ns.adobe.com/xap/1.0/\0")
1173// XMP
1174//
Dichen Zhang10959a42023-04-10 16:28:16 -07001175// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001176//
1177// Metadata versions we are using:
1178// ECMA TR-98 for JFIF marker
1179// Exif 2.2 spec for EXIF marker
1180// Adobe XMP spec part 3 for XMP marker
1181// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301182status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
1183 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
1184 size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001185 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301186 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1187 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001188 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001189 }
Nick Deakin094946b2023-06-09 11:58:41 -04001190 if (metadata->version.compare("1.0")) {
1191 ALOGE("received bad value for version: %s", metadata->version.c_str());
1192 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1193 }
1194 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301195 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301196 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301197 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1198 }
Nick Deakin094946b2023-06-09 11:58:41 -04001199 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1200 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301201 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001202 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1203 }
Nick Deakin094946b2023-06-09 11:58:41 -04001204 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301205 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
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->gamma <= 0.0f) {
1209 ALOGE("received bad value for gamma %f", metadata->gamma);
1210 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1211 }
1212
Dichen Zhang15345ea2023-02-23 23:54:32 +00001213 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301214 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001215
Dichen Zhang15345ea2023-02-23 23:54:32 +00001216 // calculate secondary image length first, because the length will be written into the primary
1217 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001218 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001219 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301220 + nameSpaceLength /* 29 bytes length of name space including \0 */
1221 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001222 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301223 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001224 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001225 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001226 // same as primary
1227 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001228
Dichen Zhang15345ea2023-02-23 23:54:32 +00001229 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001230 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001231 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001232 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1233 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001234
1235 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001236 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001237 const int length = 2 + exif->length;
1238 const uint8_t lengthH = ((length >> 8) & 0xff);
1239 const uint8_t lengthL = (length & 0xff);
1240 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1241 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1242 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1243 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1244 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1245 }
1246
1247 // Prepare and write XMP
1248 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001249 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001250 const uint8_t lengthH = ((length >> 8) & 0xff);
1251 const uint8_t lengthL = (length & 0xff);
1252 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1253 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1254 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1255 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1256 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001257 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1258 }
1259
Nick Deakin0db53ee2023-05-19 17:14:45 -04001260 // Write ICC
1261 if (icc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301262 const int length = icc_size + 2;
1263 const uint8_t lengthH = ((length >> 8) & 0xff);
1264 const uint8_t lengthL = (length & 0xff);
1265 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1266 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1267 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1268 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1269 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001270 }
1271
Dichen Zhang61ede362023-02-22 18:50:13 +00001272 // Prepare and write MPF
1273 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301274 const int length = 2 + calculateMpfSize();
1275 const uint8_t lengthH = ((length >> 8) & 0xff);
1276 const uint8_t lengthL = (length & 0xff);
1277 int primary_image_size = pos + length + primary_jpg_image_ptr->length;
1278 // between APP2 + package size + signature
1279 // ff e2 00 58 4d 50 46 00
1280 // 2 + 2 + 4 = 8 (bytes)
1281 // and ff d8 sign of the secondary image
1282 int secondary_image_offset = primary_image_size - pos - 8;
1283 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1284 secondary_image_size, secondary_image_offset);
1285 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1286 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1287 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1288 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1289 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001290 }
1291
1292 // Write primary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301293 JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
1294 primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001295 // Finish primary image
1296
Dichen Zhang10959a42023-04-10 16:28:16 -07001297 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001298 // Write SOI
1299 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1300 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1301
1302 // Prepare and write XMP
1303 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001304 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001305 const uint8_t lengthH = ((length >> 8) & 0xff);
1306 const uint8_t lengthL = (length & 0xff);
1307 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1308 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1309 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1310 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1311 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1312 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1313 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001314
1315 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301316 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1317 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001318
1319 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001320 dest->length = pos;
1321
Dichen Zhangd18bc302022-12-16 20:55:24 +00001322 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001323 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001324}
1325
Dichen Zhang61ede362023-02-22 18:50:13 +00001326status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001327 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001328 return ERROR_JPEGR_INVALID_NULL_PTR;
1329 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301330 if (src->width != dest->width || src->height != dest->height) {
1331 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001332 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301333 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
Ram Mohanb2359cd2023-07-28 14:33:49 +05301334 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001335 for (size_t y = 0; y < src->height; ++y) {
Ram Mohaneca81942023-07-29 14:41:48 +05301336 uint16_t* src_y_row = src_y_data + y * src->luma_stride;
1337 uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001338 for (size_t x = 0; x < src->width; ++x) {
Ram Mohaneca81942023-07-29 14:41:48 +05301339 uint16_t y_uint = src_y_row[x] >> 6;
1340 dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1341 }
1342 if (dest->width != dest->luma_stride) {
1343 memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
1344 }
1345 }
1346 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1347 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1348 size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
1349 uint8_t* dst_v_data = dst_u_data + dst_v_offset;
1350 for (size_t y = 0; y < src->height / 2; ++y) {
1351 uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
1352 uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
1353 uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
1354 for (size_t x = 0; x < src->width / 2; ++x) {
1355 uint16_t u_uint = src_uv_row[x << 1] >> 6;
1356 uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
1357 dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1358 dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1359 }
1360 if (dest->width / 2 != dest->chroma_stride) {
1361 memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
1362 memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001363 }
1364 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001365 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001366 return NO_ERROR;
1367}
1368
Ram Mohanb2359cd2023-07-28 14:33:49 +05301369status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001370 ultrahdr_color_gamut dest_encoding) {
1371 if (image == nullptr) {
1372 return ERROR_JPEGR_INVALID_NULL_PTR;
1373 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301374 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1375 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001376 return ERROR_JPEGR_INVALID_COLORGAMUT;
1377 }
1378
1379 ColorTransformFn conversionFn = nullptr;
1380 switch (src_encoding) {
1381 case ULTRAHDR_COLORGAMUT_BT709:
1382 switch (dest_encoding) {
1383 case ULTRAHDR_COLORGAMUT_BT709:
1384 return NO_ERROR;
1385 case ULTRAHDR_COLORGAMUT_P3:
1386 conversionFn = yuv709To601;
1387 break;
1388 case ULTRAHDR_COLORGAMUT_BT2100:
1389 conversionFn = yuv709To2100;
1390 break;
1391 default:
1392 // Should be impossible to hit after input validation
1393 return ERROR_JPEGR_INVALID_COLORGAMUT;
1394 }
1395 break;
1396 case ULTRAHDR_COLORGAMUT_P3:
1397 switch (dest_encoding) {
1398 case ULTRAHDR_COLORGAMUT_BT709:
1399 conversionFn = yuv601To709;
1400 break;
1401 case ULTRAHDR_COLORGAMUT_P3:
1402 return NO_ERROR;
1403 case ULTRAHDR_COLORGAMUT_BT2100:
1404 conversionFn = yuv601To2100;
1405 break;
1406 default:
1407 // Should be impossible to hit after input validation
1408 return ERROR_JPEGR_INVALID_COLORGAMUT;
1409 }
1410 break;
1411 case ULTRAHDR_COLORGAMUT_BT2100:
1412 switch (dest_encoding) {
1413 case ULTRAHDR_COLORGAMUT_BT709:
1414 conversionFn = yuv2100To709;
1415 break;
1416 case ULTRAHDR_COLORGAMUT_P3:
1417 conversionFn = yuv2100To601;
1418 break;
1419 case ULTRAHDR_COLORGAMUT_BT2100:
1420 return NO_ERROR;
1421 default:
1422 // Should be impossible to hit after input validation
1423 return ERROR_JPEGR_INVALID_COLORGAMUT;
1424 }
1425 break;
1426 default:
1427 // Should be impossible to hit after input validation
1428 return ERROR_JPEGR_INVALID_COLORGAMUT;
1429 }
1430
1431 if (conversionFn == nullptr) {
1432 // Should be impossible to hit after input validation
1433 return ERROR_JPEGR_INVALID_COLORGAMUT;
1434 }
1435
1436 for (size_t y = 0; y < image->height / 2; ++y) {
1437 for (size_t x = 0; x < image->width / 2; ++x) {
1438 transformYuv420(image, x, y, conversionFn);
1439 }
1440 }
1441
1442 return NO_ERROR;
1443}
1444
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001445} // namespace android::ultrahdr