blob: f27bd2d774aa4fb80135b466c8db070f217f060b [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 Mohanebfb3732023-08-21 18:12:53 +0530818 float hdr_white_nits;
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;
Ram Mohanebfb3732023-08-21 18:12:53 +0530822 // Note: this will produce clipping if the input exceeds kHlgMaxNits.
823 // TODO: TF LINEAR will be deprecated.
824 hdr_white_nits = kHlgMaxNits;
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000825 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000826 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800827#if USE_HLG_INVOETF_LUT
828 hdrInvOetf = hlgInvOetfLUT;
829#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500830 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800831#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500832 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500833 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000834 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800835#if USE_PQ_INVOETF_LUT
836 hdrInvOetf = pqInvOetfLUT;
837#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500838 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800839#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500840 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500841 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000842 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000843 // Should be impossible to hit after input validation.
844 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500845 }
846
Nick Deakina2215292023-02-14 21:40:06 -0500847 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
848 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400849 metadata->gamma = 1.0f;
850 metadata->offsetSdr = 0.0f;
851 metadata->offsetHdr = 0.0f;
852 metadata->hdrCapacityMin = 1.0f;
853 metadata->hdrCapacityMax = metadata->maxContentBoost;
854
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700855 float log2MinBoost = log2(metadata->minContentBoost);
856 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500857
Ram Mohanb2359cd2023-07-28 14:33:49 +0530858 ColorTransformFn hdrGamutConversionFn =
859 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500860
861 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400862 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530863 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000864 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500865 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400866 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500867 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000868 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500869 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400870 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500871 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000872 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500873 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400874 sdrYuvToRgbFn = bt2100YuvToRgb;
875 break;
876 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
877 // Should be impossible to hit after input validation.
878 return ERROR_JPEGR_INVALID_COLORGAMUT;
879 }
880 if (sdr_is_601) {
881 sdrYuvToRgbFn = p3YuvToRgb;
882 }
883
884 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530885 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400886 case ULTRAHDR_COLORGAMUT_BT709:
887 hdrYuvToRgbFn = srgbYuvToRgb;
888 break;
889 case ULTRAHDR_COLORGAMUT_P3:
890 hdrYuvToRgbFn = p3YuvToRgb;
891 break;
892 case ULTRAHDR_COLORGAMUT_BT2100:
893 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500894 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000895 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500896 // Should be impossible to hit after input validation.
897 return ERROR_JPEGR_INVALID_COLORGAMUT;
898 }
899
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800900 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800901 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
902 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
903 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500904
Ram Mohanb2359cd2023-07-28 14:33:49 +0530905 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
906 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
907 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
908 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800909 size_t rowStart, rowEnd;
910 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
911 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530912 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530913 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400914 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
915 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800916#if USE_SRGB_INVOETF_LUT
917 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
918#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800919 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800920#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800921 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
922
Ram Mohanb2359cd2023-07-28 14:33:49 +0530923 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400924 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800925 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
926 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
927 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
928
Ram Mohan43c3a802023-07-24 18:33:49 +0530929 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800930 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530931 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800932 }
933 }
934 }
935 };
936
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800937 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500938 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800939 for (int th = 0; th < threads - 1; th++) {
940 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400941 }
942
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800943 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
944 for (size_t rowStart = 0; rowStart < map_height;) {
945 size_t rowEnd = std::min(rowStart + rowStep, map_height);
946 jobQueue.enqueueJob(rowStart, rowEnd);
947 rowStart = rowEnd;
948 }
949 jobQueue.markQueueForEnd();
950 generateMap();
951 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
952
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400953 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000954 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700955}
956
Ram Mohanb2359cd2023-07-28 14:33:49 +0530957status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
958 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
959 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700960 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530961 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530962 dest == nullptr || yuv420_image_ptr->data == nullptr ||
963 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000964 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700965 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530966 if (metadata->version.compare(kJpegrVersion)) {
967 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
968 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400969 }
970 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530971 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
972 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400973 }
974 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530975 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
976 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400977 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530978 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
979 metadata->hdrCapacityMax != metadata->maxContentBoost) {
980 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
981 metadata->hdrCapacityMax);
982 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400983 }
984
Ram Mohan9b3d6852023-05-26 00:09:50 +0530985 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +0530986 size_t image_width = yuv420_image_ptr->width;
987 size_t image_height = yuv420_image_ptr->height;
Ram Mohan2c3d7fb2023-08-12 04:37:39 +0530988 size_t map_width = image_width / kMapDimensionScaleFactor;
989 size_t map_height = image_height / kMapDimensionScaleFactor;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530990 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohaneca81942023-07-29 14:41:48 +0530991 ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
992 "resolution is %dx%d, received gain map resolution is %dx%d",
993 (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
Ram Mohan9b3d6852023-05-26 00:09:50 +0530994 return ERROR_JPEGR_INVALID_INPUT_TYPE;
995 }
996
Ram Mohanb2359cd2023-07-28 14:33:49 +0530997 dest->width = yuv420_image_ptr->width;
998 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530999 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001000 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001001 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301002
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001003 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301004 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
1005 &jobQueue, &idwTable, output_format, &gainLUT,
1006 display_boost]() -> void {
1007 size_t width = yuv420_image_ptr->width;
1008 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001009
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001010 size_t rowStart, rowEnd;
1011 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1012 for (size_t y = rowStart; y < rowEnd; ++y) {
1013 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301014 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001015 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1016 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1017 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001018#if USE_SRGB_INVOETF_LUT
1019 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1020#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001021 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001022#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001023 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001024 // TODO: determine map scaling factor based on actual map dims
1025 size_t map_scale_factor = kMapDimensionScaleFactor;
1026 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1027 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1028 // later.
1029 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301030 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001031 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301032 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001033 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001034
Dichen Zhang10959a42023-04-10 16:28:16 -07001035#if USE_APPLY_GAIN_LUT
1036 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001037#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001038 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001039#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001040 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001041 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001042
1043 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301044 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001045 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1046 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1047 break;
1048 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301049 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001050#if USE_HLG_OETF_LUT
1051 ColorTransformFn hdrOetf = hlgOetfLUT;
1052#else
1053 ColorTransformFn hdrOetf = hlgOetf;
1054#endif
1055 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1056 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1057 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1058 break;
1059 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301060 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301061#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001062 ColorTransformFn hdrOetf = pqOetfLUT;
1063#else
1064 ColorTransformFn hdrOetf = pqOetf;
1065#endif
1066 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1067 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1068 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1069 break;
1070 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301071 default: {
1072 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001073 // Should be impossible to hit after input validation.
1074 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001075 }
1076 }
1077 }
1078 };
1079
1080 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1081 std::vector<std::thread> workers;
1082 for (int th = 0; th < threads - 1; th++) {
1083 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001084 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301085 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1086 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1087 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001088 jobQueue.enqueueJob(rowStart, rowEnd);
1089 rowStart = rowEnd;
1090 }
1091 jobQueue.markQueueForEnd();
1092 applyRecMap();
1093 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001094 return NO_ERROR;
1095}
1096
Ram Mohanb2359cd2023-07-28 14:33:49 +05301097status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1098 jr_compressed_ptr primary_jpg_image_ptr,
1099 jr_compressed_ptr gainmap_jpg_image_ptr) {
1100 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001101 return ERROR_JPEGR_INVALID_NULL_PTR;
1102 }
1103
1104 MessageHandler msg_handler;
1105 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301106 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1107 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1108 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001109 DataSegmentDataSource data_source(seg);
1110 JpegInfoBuilder jpeg_info_builder;
1111 jpeg_info_builder.SetImageLimit(2);
1112 JpegScanner jpeg_scanner(&msg_handler);
1113 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1114 data_source.Reset();
1115
1116 if (jpeg_scanner.HasError()) {
1117 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1118 }
1119
1120 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1121 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001122
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301123 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001124 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1125 }
1126
Ram Mohanb2359cd2023-07-28 14:33:49 +05301127 if (primary_jpg_image_ptr != nullptr) {
1128 primary_jpg_image_ptr->data =
1129 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1130 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001131 }
1132
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301133 if (image_ranges.size() == 1) {
1134 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1135 }
1136
Ram Mohanb2359cd2023-07-28 14:33:49 +05301137 if (gainmap_jpg_image_ptr != nullptr) {
1138 gainmap_jpg_image_ptr->data =
1139 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1140 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001141 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001142
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301143 // TODO: choose primary image and gain map image carefully
1144 if (image_ranges.size() > 2) {
1145 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1146 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001147 }
1148
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301149 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001150}
1151
Dichen Zhangd18bc302022-12-16 20:55:24 +00001152// JPEG/R structure:
1153// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001154//
1155// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001156// APP1 (ff e1)
1157// 2 bytes of length (2 + length of exif package)
1158// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001159//
1160// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001161// 2 bytes of length (2 + 29 + length of xmp package)
1162// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001163// XMP
1164//
1165// (Required, MPF package) APP2 (ff e2)
1166// 2 bytes of length
1167// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001168//
1169// (Required) primary image (without the first two bytes (SOI), may have other packages)
1170//
Dichen Zhang61ede362023-02-22 18:50:13 +00001171// SOI (ff d8)
1172//
1173// (Required, XMP package) APP1 (ff e1)
1174// 2 bytes of length (2 + 29 + length of xmp package)
1175// name space ("http://ns.adobe.com/xap/1.0/\0")
1176// XMP
1177//
Dichen Zhang10959a42023-04-10 16:28:16 -07001178// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001179//
1180// Metadata versions we are using:
1181// ECMA TR-98 for JFIF marker
1182// Exif 2.2 spec for EXIF marker
1183// Adobe XMP spec part 3 for XMP marker
1184// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301185status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
1186 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr exif, void* icc,
1187 size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001188 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301189 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1190 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001191 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001192 }
Nick Deakin094946b2023-06-09 11:58:41 -04001193 if (metadata->version.compare("1.0")) {
1194 ALOGE("received bad value for version: %s", metadata->version.c_str());
1195 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1196 }
1197 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301198 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301199 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301200 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1201 }
Nick Deakin094946b2023-06-09 11:58:41 -04001202 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1203 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301204 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001205 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1206 }
Nick Deakin094946b2023-06-09 11:58:41 -04001207 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301208 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
Nick Deakin094946b2023-06-09 11:58:41 -04001209 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1210 }
Nick Deakin094946b2023-06-09 11:58:41 -04001211 if (metadata->gamma <= 0.0f) {
1212 ALOGE("received bad value for gamma %f", metadata->gamma);
1213 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1214 }
1215
Dichen Zhang15345ea2023-02-23 23:54:32 +00001216 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301217 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001218
Dichen Zhang15345ea2023-02-23 23:54:32 +00001219 // calculate secondary image length first, because the length will be written into the primary
1220 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001221 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001222 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301223 + nameSpaceLength /* 29 bytes length of name space including \0 */
1224 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001225 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301226 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001227 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001228 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001229 // same as primary
1230 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001231
Dichen Zhang15345ea2023-02-23 23:54:32 +00001232 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001233 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001234 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001235 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1236 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001237
1238 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001239 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001240 const int length = 2 + exif->length;
1241 const uint8_t lengthH = ((length >> 8) & 0xff);
1242 const uint8_t lengthL = (length & 0xff);
1243 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1244 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1245 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1246 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1247 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1248 }
1249
1250 // Prepare and write XMP
1251 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001252 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001253 const uint8_t lengthH = ((length >> 8) & 0xff);
1254 const uint8_t lengthL = (length & 0xff);
1255 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1256 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1257 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1258 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1259 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001260 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1261 }
1262
Nick Deakin0db53ee2023-05-19 17:14:45 -04001263 // Write ICC
1264 if (icc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301265 const int length = icc_size + 2;
1266 const uint8_t lengthH = ((length >> 8) & 0xff);
1267 const uint8_t lengthL = (length & 0xff);
1268 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1269 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1270 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1271 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1272 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001273 }
1274
Dichen Zhang61ede362023-02-22 18:50:13 +00001275 // Prepare and write MPF
1276 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301277 const int length = 2 + calculateMpfSize();
1278 const uint8_t lengthH = ((length >> 8) & 0xff);
1279 const uint8_t lengthL = (length & 0xff);
1280 int primary_image_size = pos + length + primary_jpg_image_ptr->length;
1281 // between APP2 + package size + signature
1282 // ff e2 00 58 4d 50 46 00
1283 // 2 + 2 + 4 = 8 (bytes)
1284 // and ff d8 sign of the secondary image
1285 int secondary_image_offset = primary_image_size - pos - 8;
1286 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1287 secondary_image_size, secondary_image_offset);
1288 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1289 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1290 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1291 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1292 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001293 }
1294
1295 // Write primary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301296 JPEGR_CHECK(Write(dest, (uint8_t*)primary_jpg_image_ptr->data + 2,
1297 primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001298 // Finish primary image
1299
Dichen Zhang10959a42023-04-10 16:28:16 -07001300 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001301 // Write SOI
1302 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1303 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1304
1305 // Prepare and write XMP
1306 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001307 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001308 const uint8_t lengthH = ((length >> 8) & 0xff);
1309 const uint8_t lengthL = (length & 0xff);
1310 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1311 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1312 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1313 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1314 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1315 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1316 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001317
1318 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301319 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1320 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001321
1322 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001323 dest->length = pos;
1324
Dichen Zhangd18bc302022-12-16 20:55:24 +00001325 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001326 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001327}
1328
Dichen Zhang61ede362023-02-22 18:50:13 +00001329status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001330 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001331 return ERROR_JPEGR_INVALID_NULL_PTR;
1332 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301333 if (src->width != dest->width || src->height != dest->height) {
1334 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001335 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301336 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
Ram Mohanb2359cd2023-07-28 14:33:49 +05301337 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001338 for (size_t y = 0; y < src->height; ++y) {
Ram Mohaneca81942023-07-29 14:41:48 +05301339 uint16_t* src_y_row = src_y_data + y * src->luma_stride;
1340 uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001341 for (size_t x = 0; x < src->width; ++x) {
Ram Mohaneca81942023-07-29 14:41:48 +05301342 uint16_t y_uint = src_y_row[x] >> 6;
1343 dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1344 }
1345 if (dest->width != dest->luma_stride) {
1346 memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
1347 }
1348 }
1349 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1350 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1351 size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
1352 uint8_t* dst_v_data = dst_u_data + dst_v_offset;
1353 for (size_t y = 0; y < src->height / 2; ++y) {
1354 uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
1355 uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
1356 uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
1357 for (size_t x = 0; x < src->width / 2; ++x) {
1358 uint16_t u_uint = src_uv_row[x << 1] >> 6;
1359 uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
1360 dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1361 dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1362 }
1363 if (dest->width / 2 != dest->chroma_stride) {
1364 memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
1365 memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001366 }
1367 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001368 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001369 return NO_ERROR;
1370}
1371
Ram Mohanb2359cd2023-07-28 14:33:49 +05301372status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001373 ultrahdr_color_gamut dest_encoding) {
1374 if (image == nullptr) {
1375 return ERROR_JPEGR_INVALID_NULL_PTR;
1376 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301377 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1378 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001379 return ERROR_JPEGR_INVALID_COLORGAMUT;
1380 }
1381
1382 ColorTransformFn conversionFn = nullptr;
1383 switch (src_encoding) {
1384 case ULTRAHDR_COLORGAMUT_BT709:
1385 switch (dest_encoding) {
1386 case ULTRAHDR_COLORGAMUT_BT709:
1387 return NO_ERROR;
1388 case ULTRAHDR_COLORGAMUT_P3:
1389 conversionFn = yuv709To601;
1390 break;
1391 case ULTRAHDR_COLORGAMUT_BT2100:
1392 conversionFn = yuv709To2100;
1393 break;
1394 default:
1395 // Should be impossible to hit after input validation
1396 return ERROR_JPEGR_INVALID_COLORGAMUT;
1397 }
1398 break;
1399 case ULTRAHDR_COLORGAMUT_P3:
1400 switch (dest_encoding) {
1401 case ULTRAHDR_COLORGAMUT_BT709:
1402 conversionFn = yuv601To709;
1403 break;
1404 case ULTRAHDR_COLORGAMUT_P3:
1405 return NO_ERROR;
1406 case ULTRAHDR_COLORGAMUT_BT2100:
1407 conversionFn = yuv601To2100;
1408 break;
1409 default:
1410 // Should be impossible to hit after input validation
1411 return ERROR_JPEGR_INVALID_COLORGAMUT;
1412 }
1413 break;
1414 case ULTRAHDR_COLORGAMUT_BT2100:
1415 switch (dest_encoding) {
1416 case ULTRAHDR_COLORGAMUT_BT709:
1417 conversionFn = yuv2100To709;
1418 break;
1419 case ULTRAHDR_COLORGAMUT_P3:
1420 conversionFn = yuv2100To601;
1421 break;
1422 case ULTRAHDR_COLORGAMUT_BT2100:
1423 return NO_ERROR;
1424 default:
1425 // Should be impossible to hit after input validation
1426 return ERROR_JPEGR_INVALID_COLORGAMUT;
1427 }
1428 break;
1429 default:
1430 // Should be impossible to hit after input validation
1431 return ERROR_JPEGR_INVALID_COLORGAMUT;
1432 }
1433
1434 if (conversionFn == nullptr) {
1435 // Should be impossible to hit after input validation
1436 return ERROR_JPEGR_INVALID_COLORGAMUT;
1437 }
1438
1439 for (size_t y = 0; y < image->height / 2; ++y) {
1440 for (size_t x = 0; x < image->width / 2; ++x) {
1441 transformYuv420(image, x, y, conversionFn);
1442 }
1443 }
1444
1445 return NO_ERROR;
1446}
1447
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001448} // namespace android::ultrahdr