blob: 3d70fcea71e9c22381f1468c41d5daa5bb47eba5 [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
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +000075/*
76 * Helper function copies the JPEG image from without EXIF.
77 *
78 * @param pDest destination of the data to be written.
79 * @param pSource source of data being written.
80 * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
81 * (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
82 * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
83 */
84static void copyJpegWithoutExif(jr_compressed_ptr pDest,
85 jr_compressed_ptr pSource,
86 size_t exif_pos,
87 size_t exif_size) {
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +000088 const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
89 pDest->length = pSource->length - exif_size - exif_offset;
90 pDest->data = new uint8_t[pDest->length];
Ram Mohan880c5a32023-09-08 20:12:11 +053091 pDest->maxLength = pDest->length;
92 pDest->colorGamut = pSource->colorGamut;
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +000093 memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
94 memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
95 (uint8_t*)pSource->data + exif_pos + exif_size,
96 pSource->length - exif_pos - exif_size);
97}
98
Ram Mohanb2359cd2023-07-28 14:33:49 +053099status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
100 jr_uncompressed_ptr yuv420_image_ptr,
Ram Mohanac1cfec2023-05-18 14:41:15 +0530101 ultrahdr_transfer_function hdr_tf,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530102 jr_compressed_ptr dest_ptr) {
103 if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
104 ALOGE("Received nullptr for input p010 image");
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700105 return ERROR_JPEGR_INVALID_NULL_PTR;
106 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530107 if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
108 ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
109 p010_image_ptr->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530110 return ERROR_JPEGR_INVALID_INPUT_TYPE;
111 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530112 if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
113 ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
114 kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530115 return ERROR_JPEGR_INVALID_INPUT_TYPE;
116 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530117 if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
118 ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
119 kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
Ram Mohand136b8a2023-06-02 09:06:40 +0530120 return ERROR_JPEGR_INVALID_INPUT_TYPE;
121 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530122 if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
123 p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
124 ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530125 return ERROR_JPEGR_INVALID_INPUT_TYPE;
126 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530127 if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
128 ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
129 p010_image_ptr->luma_stride, p010_image_ptr->width);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700130 return ERROR_JPEGR_INVALID_INPUT_TYPE;
131 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530132 if (p010_image_ptr->chroma_data != nullptr &&
133 p010_image_ptr->chroma_stride < p010_image_ptr->width) {
134 ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
135 p010_image_ptr->chroma_stride, p010_image_ptr->width);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530136 return ERROR_JPEGR_INVALID_INPUT_TYPE;
137 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530138 if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
139 ALOGE("Received nullptr for destination");
Ram Mohanac1cfec2023-05-18 14:41:15 +0530140 return ERROR_JPEGR_INVALID_NULL_PTR;
141 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530142 if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530143 ALOGE("Invalid hdr transfer function %d", hdr_tf);
144 return ERROR_JPEGR_INVALID_INPUT_TYPE;
145 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530146 if (yuv420_image_ptr == nullptr) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700147 return NO_ERROR;
148 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530149 if (yuv420_image_ptr->data == nullptr) {
150 ALOGE("Received nullptr for uncompressed 420 image");
Ram Mohanac1cfec2023-05-18 14:41:15 +0530151 return ERROR_JPEGR_INVALID_NULL_PTR;
152 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530153 if (yuv420_image_ptr->luma_stride != 0 &&
154 yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
155 ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
156 yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
Ram Mohan43c3a802023-07-24 18:33:49 +0530157 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700158 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530159 if (yuv420_image_ptr->chroma_data != nullptr &&
160 yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
161 ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
162 yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
Ram Mohan43c3a802023-07-24 18:33:49 +0530163 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700164 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530165 if (p010_image_ptr->width != yuv420_image_ptr->width ||
166 p010_image_ptr->height != yuv420_image_ptr->height) {
167 ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
168 p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700169 return ERROR_JPEGR_RESOLUTION_MISMATCH;
170 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530171 if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
172 yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
173 ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530174 return ERROR_JPEGR_INVALID_INPUT_TYPE;
175 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530176 return NO_ERROR;
177}
178
Ram Mohanb2359cd2023-07-28 14:33:49 +0530179status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
180 jr_uncompressed_ptr yuv420_image_ptr,
Ram Mohanac1cfec2023-05-18 14:41:15 +0530181 ultrahdr_transfer_function hdr_tf,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530182 jr_compressed_ptr dest_ptr, int quality) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530183 if (quality < 0 || quality > 100) {
184 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
185 return ERROR_JPEGR_INVALID_INPUT_TYPE;
186 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530187 return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700188}
189
Dichen Zhang636f5242022-12-07 20:25:44 +0000190/* Encode API-0 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530191status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
192 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
193 // validate input arguments
194 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
195 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700196 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800197 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530198 if (exif != nullptr && exif->data == nullptr) {
199 ALOGE("received nullptr for exif metadata");
200 return ERROR_JPEGR_INVALID_NULL_PTR;
201 }
202
Ram Mohanb2359cd2023-07-28 14:33:49 +0530203 // clean up input structure for later usage
204 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
205 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
206 if (!p010_image.chroma_data) {
207 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
208 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
209 p010_image.chroma_stride = p010_image.luma_stride;
210 }
Dichen Zhang636f5242022-12-07 20:25:44 +0000211
Ram Mohaneca81942023-07-29 14:41:48 +0530212 const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530213 unique_ptr<uint8_t[]> yuv420_image_data =
Ram Mohaneca81942023-07-29 14:41:48 +0530214 make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530215 jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
216 .width = p010_image.width,
217 .height = p010_image.height,
218 .colorGamut = p010_image.colorGamut,
Ram Mohaneca81942023-07-29 14:41:48 +0530219 .luma_stride = yu420_luma_stride,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530220 .chroma_data = nullptr,
Ram Mohaneca81942023-07-29 14:41:48 +0530221 .chroma_stride = yu420_luma_stride >> 1};
222 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
223 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
Dichen Zhang636f5242022-12-07 20:25:44 +0000224
Ram Mohanb2359cd2023-07-28 14:33:49 +0530225 // tone map
226 JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
227
228 // gain map
229 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
230 jpegr_uncompressed_struct gainmap_image;
231 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
Dichen Zhang636f5242022-12-07 20:25:44 +0000232 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530233 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Dichen Zhang636f5242022-12-07 20:25:44 +0000234
Ram Mohanb2359cd2023-07-28 14:33:49 +0530235 // compress gain map
236 JpegEncoderHelper jpeg_enc_obj_gm;
237 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
238 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
239 .length = static_cast<int>(
240 jpeg_enc_obj_gm.getCompressedImageSize()),
241 .maxLength = static_cast<int>(
242 jpeg_enc_obj_gm.getCompressedImageSize()),
243 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Dichen Zhang636f5242022-12-07 20:25:44 +0000244
Ram Mohanb2359cd2023-07-28 14:33:49 +0530245 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000246
Ram Mohanb2359cd2023-07-28 14:33:49 +0530247 // convert to Bt601 YUV encoding for JPEG encode
248 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
249 JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
250 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400251
Ram Mohanb2359cd2023-07-28 14:33:49 +0530252 // compress 420 image
253 JpegEncoderHelper jpeg_enc_obj_yuv420;
Ram Mohaneca81942023-07-29 14:41:48 +0530254 if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
255 reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
256 yuv420_image.width, yuv420_image.height,
257 yuv420_image.luma_stride, yuv420_image.chroma_stride,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530258 quality, icc->getData(), icc->getLength())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000259 return ERROR_JPEGR_ENCODE_ERROR;
260 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530261 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
262 .length = static_cast<int>(
263 jpeg_enc_obj_yuv420.getCompressedImageSize()),
264 .maxLength = static_cast<int>(
265 jpeg_enc_obj_yuv420.getCompressedImageSize()),
266 .colorGamut = yuv420_image.colorGamut};
Dichen Zhang636f5242022-12-07 20:25:44 +0000267
Ram Mohanb2359cd2023-07-28 14:33:49 +0530268 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400269 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
270 &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000271
272 return NO_ERROR;
273}
274
275/* Encode API-1 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530276status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
277 jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
278 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
279 // validate input arguments
280 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530281 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000282 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000283 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530284 if (exif != nullptr && exif->data == nullptr) {
285 ALOGE("received nullptr for exif metadata");
286 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhangffa34012022-11-03 23:21:13 +0000287 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530288 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
289 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700290 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800291 }
292
Ram Mohanb2359cd2023-07-28 14:33:49 +0530293 // clean up input structure for later usage
294 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
295 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
296 if (!p010_image.chroma_data) {
297 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
298 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
299 p010_image.chroma_stride = p010_image.luma_stride;
300 }
301 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
302 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
303 if (!yuv420_image.chroma_data) {
304 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
305 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
306 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Ram Mohan43c3a802023-07-24 18:33:49 +0530307 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400308
Ram Mohanb2359cd2023-07-28 14:33:49 +0530309 // gain map
310 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
311 jpegr_uncompressed_struct gainmap_image;
312 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
313 std::unique_ptr<uint8_t[]> map_data;
314 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400315
Ram Mohanb2359cd2023-07-28 14:33:49 +0530316 // compress gain map
317 JpegEncoderHelper jpeg_enc_obj_gm;
318 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
319 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
320 .length = static_cast<int>(
321 jpeg_enc_obj_gm.getCompressedImageSize()),
322 .maxLength = static_cast<int>(
323 jpeg_enc_obj_gm.getCompressedImageSize()),
324 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
325
326 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
327
328 jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
329 unique_ptr<uint8_t[]> yuv_420_bt601_data;
330 // Convert to bt601 YUV encoding for JPEG encode
331 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
Ram Mohaneca81942023-07-29 14:41:48 +0530332 const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
333 yuv_420_bt601_data =
334 make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530335 yuv420_bt601_image.data = yuv_420_bt601_data.get();
336 yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
Ram Mohaneca81942023-07-29 14:41:48 +0530337 yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530338 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
Ram Mohaneca81942023-07-29 14:41:48 +0530339 yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
340 yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530341
342 {
343 // copy luma
344 uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
345 uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
346 if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
347 memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
348 } else {
349 for (size_t i = 0; i < yuv420_image.height; i++) {
350 memcpy(y_dst, y_src, yuv420_image.width);
Ram Mohaneca81942023-07-29 14:41:48 +0530351 if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
352 memset(y_dst + yuv420_image.width, 0,
353 yuv420_bt601_image.luma_stride - yuv420_image.width);
354 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530355 y_dst += yuv420_bt601_image.luma_stride;
356 y_src += yuv420_image.luma_stride;
357 }
358 }
359 }
360
361 if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
362 // copy luma
363 uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
364 uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
365 memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
366 } else {
367 // copy cb & cr
368 uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
369 uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
370 uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
371 uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
372 for (size_t i = 0; i < yuv420_image.height / 2; i++) {
373 memcpy(cb_dst, cb_src, yuv420_image.width / 2);
374 memcpy(cr_dst, cr_src, yuv420_image.width / 2);
Ram Mohaneca81942023-07-29 14:41:48 +0530375 if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
376 memset(cb_dst + yuv420_image.width / 2, 0,
377 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
378 memset(cr_dst + yuv420_image.width / 2, 0,
379 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
380 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530381 cb_dst += yuv420_bt601_image.chroma_stride;
382 cb_src += yuv420_image.chroma_stride;
383 cr_dst += yuv420_bt601_image.chroma_stride;
384 cr_src += yuv420_image.chroma_stride;
385 }
386 }
387 JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
388 }
389
390 // compress 420 image
391 JpegEncoderHelper jpeg_enc_obj_yuv420;
Ram Mohaneca81942023-07-29 14:41:48 +0530392 if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
393 reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
394 yuv420_bt601_image.width, yuv420_bt601_image.height,
395 yuv420_bt601_image.luma_stride,
396 yuv420_bt601_image.chroma_stride, quality, icc->getData(),
Ram Mohanb2359cd2023-07-28 14:33:49 +0530397 icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400398 return ERROR_JPEGR_ENCODE_ERROR;
399 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400400
Ram Mohanb2359cd2023-07-28 14:33:49 +0530401 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
402 .length = static_cast<int>(
403 jpeg_enc_obj_yuv420.getCompressedImageSize()),
404 .maxLength = static_cast<int>(
405 jpeg_enc_obj_yuv420.getCompressedImageSize()),
406 .colorGamut = yuv420_image.colorGamut};
407
408 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400409 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
410 &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000411 return NO_ERROR;
412}
413
Dichen Zhang636f5242022-12-07 20:25:44 +0000414/* Encode API-2 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530415status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
416 jr_uncompressed_ptr yuv420_image_ptr,
417 jr_compressed_ptr yuv420jpg_image_ptr,
418 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
419 // validate input arguments
420 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530421 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000422 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000423 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530424 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530425 ALOGE("received nullptr for compressed jpeg image");
426 return ERROR_JPEGR_INVALID_NULL_PTR;
427 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530428 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
429 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700430 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800431 }
432
Ram Mohanb2359cd2023-07-28 14:33:49 +0530433 // clean up input structure for later usage
434 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
435 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
436 if (!p010_image.chroma_data) {
437 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
438 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
439 p010_image.chroma_stride = p010_image.luma_stride;
440 }
441 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
442 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
443 if (!yuv420_image.chroma_data) {
444 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
445 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
446 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400447 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400448
Ram Mohanb2359cd2023-07-28 14:33:49 +0530449 // gain map
450 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
451 jpegr_uncompressed_struct gainmap_image;
452 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
453 std::unique_ptr<uint8_t[]> map_data;
454 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
455
456 // compress gain map
457 JpegEncoderHelper jpeg_enc_obj_gm;
458 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
459 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
460 .length = static_cast<int>(
461 jpeg_enc_obj_gm.getCompressedImageSize()),
462 .maxLength = static_cast<int>(
463 jpeg_enc_obj_gm.getCompressedImageSize()),
464 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
465
466 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000467}
468
Dichen Zhang636f5242022-12-07 20:25:44 +0000469/* Encode API-3 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530470status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
471 jr_compressed_ptr yuv420jpg_image_ptr,
472 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
473 // validate input arguments
474 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530475 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000476 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000477 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530478 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700479 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800480 }
481
Ram Mohanb2359cd2023-07-28 14:33:49 +0530482 // clean up input structure for later usage
483 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
484 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
485 if (!p010_image.chroma_data) {
486 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
487 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
488 p010_image.chroma_stride = p010_image.luma_stride;
489 }
490
491 // decode input jpeg, gamut is going to be bt601.
492 JpegDecoderHelper jpeg_dec_obj_yuv420;
493 if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
494 yuv420jpg_image_ptr->length)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400495 return ERROR_JPEGR_DECODE_ERROR;
496 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530497 jpegr_uncompressed_struct yuv420_image{};
498 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
499 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
500 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
501 yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
502 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
503 if (!yuv420_image.chroma_data) {
504 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
505 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
506 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
507 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400508
Ram Mohanb2359cd2023-07-28 14:33:49 +0530509 if (p010_image_ptr->width != yuv420_image.width ||
510 p010_image_ptr->height != yuv420_image.height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400511 return ERROR_JPEGR_RESOLUTION_MISMATCH;
512 }
513
Ram Mohanb2359cd2023-07-28 14:33:49 +0530514 // gain map
515 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
516 jpegr_uncompressed_struct gainmap_image;
517 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
518 true /* sdr_is_601 */));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400519 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530520 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400521
Ram Mohanb2359cd2023-07-28 14:33:49 +0530522 // compress gain map
523 JpegEncoderHelper jpeg_enc_obj_gm;
524 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
525 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
526 .length = static_cast<int>(
527 jpeg_enc_obj_gm.getCompressedImageSize()),
528 .maxLength = static_cast<int>(
529 jpeg_enc_obj_gm.getCompressedImageSize()),
530 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400531
Ram Mohanb2359cd2023-07-28 14:33:49 +0530532 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000533}
534
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000535/* Encode API-4 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530536status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
537 jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000538 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530539 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530540 ALOGE("received nullptr for compressed jpeg image");
541 return ERROR_JPEGR_INVALID_NULL_PTR;
542 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530543 if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530544 ALOGE("received nullptr for compressed gain map");
545 return ERROR_JPEGR_INVALID_NULL_PTR;
546 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530547 if (dest == nullptr || dest->data == nullptr) {
548 ALOGE("received nullptr for destination");
549 return ERROR_JPEGR_INVALID_NULL_PTR;
550 }
551
Nick Deakin0db53ee2023-05-19 17:14:45 -0400552 // We just want to check if ICC is present, so don't do a full decode. Note,
553 // this doesn't verify that the ICC is valid.
554 JpegDecoderHelper decoder;
555 std::vector<uint8_t> icc;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530556 decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
557 /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
558 /* exifData */ nullptr);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400559
560 // Add ICC if not already present.
561 if (icc.size() > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530562 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
563 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400564 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530565 sp<DataStruct> newIcc =
566 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
567 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
568 newIcc->getData(), newIcc->getLength(), metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400569 }
570
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000571 return NO_ERROR;
572}
573
Ram Mohanb2359cd2023-07-28 14:33:49 +0530574status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
575 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530576 ALOGE("received nullptr for compressed jpegr image");
577 return ERROR_JPEGR_INVALID_NULL_PTR;
578 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530579 if (jpeg_image_info_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530580 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000581 return ERROR_JPEGR_INVALID_NULL_PTR;
582 }
583
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530584 jpegr_compressed_struct primary_image, gainmap_image;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530585 status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530586 if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
587 return status;
588 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000589
Ram Mohanb2359cd2023-07-28 14:33:49 +0530590 JpegDecoderHelper jpeg_dec_obj_hdr;
591 if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
592 &jpeg_image_info_ptr->width,
593 &jpeg_image_info_ptr->height,
594 jpeg_image_info_ptr->iccData,
595 jpeg_image_info_ptr->exifData)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000596 return ERROR_JPEGR_DECODE_ERROR;
597 }
598
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530599 return status;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000600}
601
Dichen Zhang636f5242022-12-07 20:25:44 +0000602/* Decode API */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530603status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
604 float max_display_boost, jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000605 ultrahdr_output_format output_format,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530606 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
607 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanb0375052023-05-20 04:03:48 +0530608 ALOGE("received nullptr for compressed jpegr image");
609 return ERROR_JPEGR_INVALID_NULL_PTR;
610 }
Ram Mohanb0375052023-05-20 04:03:48 +0530611 if (dest == nullptr || dest->data == nullptr) {
612 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000613 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000614 }
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000615 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530616 ALOGE("received bad value for max_display_boost %f", max_display_boost);
617 return ERROR_JPEGR_INVALID_INPUT_TYPE;
618 }
Ram Mohanb0375052023-05-20 04:03:48 +0530619 if (exif != nullptr && exif->data == nullptr) {
620 ALOGE("received nullptr address for exif data");
621 return ERROR_JPEGR_INVALID_INPUT_TYPE;
622 }
Ram Mohanb0375052023-05-20 04:03:48 +0530623 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
624 ALOGE("received bad value for output format %d", output_format);
625 return ERROR_JPEGR_INVALID_INPUT_TYPE;
626 }
627
Ram Mohanb2359cd2023-07-28 14:33:49 +0530628 jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530629 status_t status =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530630 extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530631 if (status != NO_ERROR) {
632 if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
633 ALOGE("received invalid compressed jpegr image");
634 return status;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000635 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000636 }
637
Ram Mohanb2359cd2023-07-28 14:33:49 +0530638 JpegDecoderHelper jpeg_dec_obj_yuv420;
639 if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
640 (output_format == ULTRAHDR_OUTPUT_SDR))) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400641 return ERROR_JPEGR_DECODE_ERROR;
642 }
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530643
644 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530645 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
646 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
647 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530648 return ERROR_JPEGR_CALCULATION_ERROR;
649 }
650 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530651 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
652 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
653 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530654 return ERROR_JPEGR_CALCULATION_ERROR;
655 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530656 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400657
Dichen Zhangb80e2262023-03-08 06:59:51 +0000658 if (exif != nullptr) {
659 if (exif->data == nullptr) {
660 return ERROR_JPEGR_INVALID_NULL_PTR;
661 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530662 if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
Dichen Zhangb80e2262023-03-08 06:59:51 +0000663 return ERROR_JPEGR_BUFFER_TOO_SMALL;
664 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530665 memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
666 exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
Dichen Zhangb80e2262023-03-08 06:59:51 +0000667 }
668
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530669 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530670 dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
671 dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
672 memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
673 dest->width * dest->height * 4);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530674 return NO_ERROR;
675 }
676
Ram Mohanb2359cd2023-07-28 14:33:49 +0530677 JpegDecoderHelper jpeg_dec_obj_gm;
678 if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530679 return ERROR_JPEGR_DECODE_ERROR;
680 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530681 if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
682 jpeg_dec_obj_gm.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530683 return ERROR_JPEGR_CALCULATION_ERROR;
684 }
685
Ram Mohanb2359cd2023-07-28 14:33:49 +0530686 jpegr_uncompressed_struct gainmap_image;
687 gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
688 gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
689 gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000690
Ram Mohanb2359cd2023-07-28 14:33:49 +0530691 if (gainmap_image_ptr != nullptr) {
692 gainmap_image_ptr->width = gainmap_image.width;
693 gainmap_image_ptr->height = gainmap_image.height;
694 int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
695 gainmap_image_ptr->data = malloc(size);
696 memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530697 }
698
699 ultrahdr_metadata_struct uhdr_metadata;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530700 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
701 jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530702 return ERROR_JPEGR_INVALID_METADATA;
703 }
704
705 if (metadata != nullptr) {
706 metadata->version = uhdr_metadata.version;
707 metadata->minContentBoost = uhdr_metadata.minContentBoost;
708 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
709 metadata->gamma = uhdr_metadata.gamma;
710 metadata->offsetSdr = uhdr_metadata.offsetSdr;
711 metadata->offsetHdr = uhdr_metadata.offsetHdr;
712 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
713 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
714 }
715
Ram Mohanb2359cd2023-07-28 14:33:49 +0530716 jpegr_uncompressed_struct yuv420_image;
717 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
718 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
719 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
720 yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
721 jpeg_dec_obj_yuv420.getICCSize());
722 yuv420_image.luma_stride = yuv420_image.width;
723 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
724 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
725 yuv420_image.chroma_stride = yuv420_image.width >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400726
Ram Mohanb2359cd2023-07-28 14:33:49 +0530727 JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700728 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000729 return NO_ERROR;
730}
731
Ram Mohanb2359cd2023-07-28 14:33:49 +0530732status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
733 JpegEncoderHelper* jpeg_enc_obj_ptr) {
734 if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000735 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700736 }
737
Nick Deakin0db53ee2023-05-19 17:14:45 -0400738 // Don't need to convert YUV to Bt601 since single channel
Ram Mohaneca81942023-07-29 14:41:48 +0530739 if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
740 gainmap_image_ptr->width, gainmap_image_ptr->height,
741 gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
742 nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400743 return ERROR_JPEGR_ENCODE_ERROR;
744 }
745
Dichen Zhang6947d532022-10-22 02:16:21 +0000746 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700747}
748
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800749const int kJobSzInRows = 16;
750static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
751 "align job size to kMapDimensionScaleFactor");
752
753class JobQueue {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530754public:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800755 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
756 void enqueueJob(size_t rowStart, size_t rowEnd);
757 void markQueueForEnd();
758 void reset();
759
Ram Mohanb2359cd2023-07-28 14:33:49 +0530760private:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800761 bool mQueuedAllJobs = false;
762 std::deque<std::tuple<size_t, size_t>> mJobs;
763 std::mutex mMutex;
764 std::condition_variable mCv;
765};
766
767bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
768 std::unique_lock<std::mutex> lock{mMutex};
769 while (true) {
770 if (mJobs.empty()) {
771 if (mQueuedAllJobs) {
772 return false;
773 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000774 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800775 }
776 } else {
777 auto it = mJobs.begin();
778 rowStart = std::get<0>(*it);
779 rowEnd = std::get<1>(*it);
780 mJobs.erase(it);
781 return true;
782 }
783 }
784 return false;
785}
786
787void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
788 std::unique_lock<std::mutex> lock{mMutex};
789 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
790 lock.unlock();
791 mCv.notify_one();
792}
793
794void JobQueue::markQueueForEnd() {
795 std::unique_lock<std::mutex> lock{mMutex};
796 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000797 lock.unlock();
798 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800799}
800
801void JobQueue::reset() {
802 std::unique_lock<std::mutex> lock{mMutex};
803 mJobs.clear();
804 mQueuedAllJobs = false;
805}
806
Ram Mohanb2359cd2023-07-28 14:33:49 +0530807status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
808 jr_uncompressed_ptr p010_image_ptr,
809 ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
810 jr_uncompressed_ptr dest, bool sdr_is_601) {
811 if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530812 dest == nullptr || yuv420_image_ptr->data == nullptr ||
813 yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
814 p010_image_ptr->chroma_data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000815 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700816 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530817 if (yuv420_image_ptr->width != p010_image_ptr->width ||
818 yuv420_image_ptr->height != p010_image_ptr->height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400819 return ERROR_JPEGR_RESOLUTION_MISMATCH;
820 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530821 if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
822 p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500823 return ERROR_JPEGR_INVALID_COLORGAMUT;
824 }
825
Ram Mohanb2359cd2023-07-28 14:33:49 +0530826 size_t image_width = yuv420_image_ptr->width;
827 size_t image_height = yuv420_image_ptr->height;
Ram Mohan2c3d7fb2023-08-12 04:37:39 +0530828 size_t map_width = image_width / kMapDimensionScaleFactor;
829 size_t map_height = image_height / kMapDimensionScaleFactor;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400830
Ram Mohanb2359cd2023-07-28 14:33:49 +0530831 dest->data = new uint8_t[map_width * map_height];
Ram Mohan43c3a802023-07-24 18:33:49 +0530832 dest->width = map_width;
833 dest->height = map_height;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000834 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530835 dest->luma_stride = map_width;
836 dest->chroma_data = nullptr;
837 dest->chroma_stride = 0;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400838 std::unique_ptr<uint8_t[]> map_data;
839 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
840
Nick Deakin6bd90432022-11-20 16:26:37 -0500841 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohanebfb3732023-08-21 18:12:53 +0530842 float hdr_white_nits;
Nick Deakin01759062023-02-02 18:21:43 -0500843 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000844 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000845 hdrInvOetf = identityConversion;
Ram Mohanebfb3732023-08-21 18:12:53 +0530846 // Note: this will produce clipping if the input exceeds kHlgMaxNits.
847 // TODO: TF LINEAR will be deprecated.
848 hdr_white_nits = kHlgMaxNits;
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000849 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000850 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800851#if USE_HLG_INVOETF_LUT
852 hdrInvOetf = hlgInvOetfLUT;
853#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500854 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800855#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500856 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500857 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000858 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800859#if USE_PQ_INVOETF_LUT
860 hdrInvOetf = pqInvOetfLUT;
861#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500862 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800863#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500864 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500865 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000866 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000867 // Should be impossible to hit after input validation.
868 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500869 }
870
Nick Deakina2215292023-02-14 21:40:06 -0500871 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
872 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400873 metadata->gamma = 1.0f;
874 metadata->offsetSdr = 0.0f;
875 metadata->offsetHdr = 0.0f;
876 metadata->hdrCapacityMin = 1.0f;
877 metadata->hdrCapacityMax = metadata->maxContentBoost;
878
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700879 float log2MinBoost = log2(metadata->minContentBoost);
880 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500881
Ram Mohanb2359cd2023-07-28 14:33:49 +0530882 ColorTransformFn hdrGamutConversionFn =
883 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500884
885 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400886 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530887 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000888 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500889 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400890 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500891 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000892 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500893 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400894 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500895 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000896 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500897 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400898 sdrYuvToRgbFn = bt2100YuvToRgb;
899 break;
900 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
901 // Should be impossible to hit after input validation.
902 return ERROR_JPEGR_INVALID_COLORGAMUT;
903 }
904 if (sdr_is_601) {
905 sdrYuvToRgbFn = p3YuvToRgb;
906 }
907
908 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530909 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400910 case ULTRAHDR_COLORGAMUT_BT709:
911 hdrYuvToRgbFn = srgbYuvToRgb;
912 break;
913 case ULTRAHDR_COLORGAMUT_P3:
914 hdrYuvToRgbFn = p3YuvToRgb;
915 break;
916 case ULTRAHDR_COLORGAMUT_BT2100:
917 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500918 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000919 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500920 // Should be impossible to hit after input validation.
921 return ERROR_JPEGR_INVALID_COLORGAMUT;
922 }
923
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800924 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800925 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
926 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
927 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500928
Ram Mohanb2359cd2023-07-28 14:33:49 +0530929 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
930 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
931 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
932 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800933 size_t rowStart, rowEnd;
934 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
935 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530936 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530937 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400938 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
939 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800940#if USE_SRGB_INVOETF_LUT
941 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
942#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800943 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800944#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800945 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
946
Ram Mohanb2359cd2023-07-28 14:33:49 +0530947 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400948 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800949 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
950 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
951 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
952
Ram Mohan43c3a802023-07-24 18:33:49 +0530953 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800954 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530955 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800956 }
957 }
958 }
959 };
960
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800961 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500962 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800963 for (int th = 0; th < threads - 1; th++) {
964 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400965 }
966
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800967 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
968 for (size_t rowStart = 0; rowStart < map_height;) {
969 size_t rowEnd = std::min(rowStart + rowStep, map_height);
970 jobQueue.enqueueJob(rowStart, rowEnd);
971 rowStart = rowEnd;
972 }
973 jobQueue.markQueueForEnd();
974 generateMap();
975 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
976
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400977 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000978 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700979}
980
Ram Mohanb2359cd2023-07-28 14:33:49 +0530981status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
982 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
983 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700984 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530985 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530986 dest == nullptr || yuv420_image_ptr->data == nullptr ||
987 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000988 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700989 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530990 if (metadata->version.compare(kJpegrVersion)) {
991 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
992 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400993 }
994 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530995 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
996 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400997 }
998 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530999 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
1000 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -04001001 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301002 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
1003 metadata->hdrCapacityMax != metadata->maxContentBoost) {
1004 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
1005 metadata->hdrCapacityMax);
1006 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -04001007 }
1008
Ram Mohan9b3d6852023-05-26 00:09:50 +05301009 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +05301010 size_t image_width = yuv420_image_ptr->width;
1011 size_t image_height = yuv420_image_ptr->height;
Ram Mohan2c3d7fb2023-08-12 04:37:39 +05301012 size_t map_width = image_width / kMapDimensionScaleFactor;
1013 size_t map_height = image_height / kMapDimensionScaleFactor;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301014 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohaneca81942023-07-29 14:41:48 +05301015 ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
1016 "resolution is %dx%d, received gain map resolution is %dx%d",
1017 (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
Ram Mohan9b3d6852023-05-26 00:09:50 +05301018 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1019 }
1020
Ram Mohanb2359cd2023-07-28 14:33:49 +05301021 dest->width = yuv420_image_ptr->width;
1022 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +05301023 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001024 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001025 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301026
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001027 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301028 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
1029 &jobQueue, &idwTable, output_format, &gainLUT,
1030 display_boost]() -> void {
1031 size_t width = yuv420_image_ptr->width;
1032 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001033
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001034 size_t rowStart, rowEnd;
1035 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1036 for (size_t y = rowStart; y < rowEnd; ++y) {
1037 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301038 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001039 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1040 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1041 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001042#if USE_SRGB_INVOETF_LUT
1043 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1044#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001045 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001046#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001047 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001048 // TODO: determine map scaling factor based on actual map dims
1049 size_t map_scale_factor = kMapDimensionScaleFactor;
1050 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1051 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1052 // later.
1053 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301054 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001055 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301056 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001057 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001058
Dichen Zhang10959a42023-04-10 16:28:16 -07001059#if USE_APPLY_GAIN_LUT
1060 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001061#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001062 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001063#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001064 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001065 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001066
1067 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301068 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001069 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1070 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1071 break;
1072 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301073 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001074#if USE_HLG_OETF_LUT
1075 ColorTransformFn hdrOetf = hlgOetfLUT;
1076#else
1077 ColorTransformFn hdrOetf = hlgOetf;
1078#endif
1079 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1080 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1081 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1082 break;
1083 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301084 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301085#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001086 ColorTransformFn hdrOetf = pqOetfLUT;
1087#else
1088 ColorTransformFn hdrOetf = pqOetf;
1089#endif
1090 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1091 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1092 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1093 break;
1094 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301095 default: {
1096 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001097 // Should be impossible to hit after input validation.
1098 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001099 }
1100 }
1101 }
1102 };
1103
1104 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1105 std::vector<std::thread> workers;
1106 for (int th = 0; th < threads - 1; th++) {
1107 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001108 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301109 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1110 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1111 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001112 jobQueue.enqueueJob(rowStart, rowEnd);
1113 rowStart = rowEnd;
1114 }
1115 jobQueue.markQueueForEnd();
1116 applyRecMap();
1117 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001118 return NO_ERROR;
1119}
1120
Ram Mohanb2359cd2023-07-28 14:33:49 +05301121status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1122 jr_compressed_ptr primary_jpg_image_ptr,
1123 jr_compressed_ptr gainmap_jpg_image_ptr) {
1124 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001125 return ERROR_JPEGR_INVALID_NULL_PTR;
1126 }
1127
1128 MessageHandler msg_handler;
1129 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301130 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1131 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1132 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001133 DataSegmentDataSource data_source(seg);
1134 JpegInfoBuilder jpeg_info_builder;
1135 jpeg_info_builder.SetImageLimit(2);
1136 JpegScanner jpeg_scanner(&msg_handler);
1137 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1138 data_source.Reset();
1139
1140 if (jpeg_scanner.HasError()) {
1141 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1142 }
1143
1144 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1145 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001146
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301147 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001148 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1149 }
1150
Ram Mohanb2359cd2023-07-28 14:33:49 +05301151 if (primary_jpg_image_ptr != nullptr) {
1152 primary_jpg_image_ptr->data =
1153 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1154 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001155 }
1156
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301157 if (image_ranges.size() == 1) {
1158 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1159 }
1160
Ram Mohanb2359cd2023-07-28 14:33:49 +05301161 if (gainmap_jpg_image_ptr != nullptr) {
1162 gainmap_jpg_image_ptr->data =
1163 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1164 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001165 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001166
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301167 // TODO: choose primary image and gain map image carefully
1168 if (image_ranges.size() > 2) {
1169 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1170 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001171 }
1172
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301173 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001174}
1175
Dichen Zhangd18bc302022-12-16 20:55:24 +00001176// JPEG/R structure:
1177// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001178//
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001179// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
1180// in the JPEG input (Encode API-2, API-3, API-4))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001181// APP1 (ff e1)
1182// 2 bytes of length (2 + length of exif package)
1183// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001184//
1185// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001186// 2 bytes of length (2 + 29 + length of xmp package)
1187// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001188// XMP
1189//
1190// (Required, MPF package) APP2 (ff e2)
1191// 2 bytes of length
1192// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001193//
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001194// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001195//
Dichen Zhang61ede362023-02-22 18:50:13 +00001196// SOI (ff d8)
1197//
1198// (Required, XMP package) APP1 (ff e1)
1199// 2 bytes of length (2 + 29 + length of xmp package)
1200// name space ("http://ns.adobe.com/xap/1.0/\0")
1201// XMP
1202//
Dichen Zhang10959a42023-04-10 16:28:16 -07001203// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001204//
1205// Metadata versions we are using:
1206// ECMA TR-98 for JFIF marker
1207// Exif 2.2 spec for EXIF marker
1208// Adobe XMP spec part 3 for XMP marker
1209// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301210status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001211 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
1212 void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001213 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301214 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1215 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001216 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001217 }
Nick Deakin094946b2023-06-09 11:58:41 -04001218 if (metadata->version.compare("1.0")) {
1219 ALOGE("received bad value for version: %s", metadata->version.c_str());
1220 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1221 }
1222 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301223 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301224 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301225 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1226 }
Nick Deakin094946b2023-06-09 11:58:41 -04001227 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1228 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301229 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001230 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1231 }
Nick Deakin094946b2023-06-09 11:58:41 -04001232 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301233 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
Nick Deakin094946b2023-06-09 11:58:41 -04001234 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1235 }
Nick Deakin094946b2023-06-09 11:58:41 -04001236 if (metadata->gamma <= 0.0f) {
1237 ALOGE("received bad value for gamma %f", metadata->gamma);
1238 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1239 }
1240
Dichen Zhang15345ea2023-02-23 23:54:32 +00001241 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301242 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001243
Dichen Zhang15345ea2023-02-23 23:54:32 +00001244 // calculate secondary image length first, because the length will be written into the primary
1245 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001246 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001247 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301248 + nameSpaceLength /* 29 bytes length of name space including \0 */
1249 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001250 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301251 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001252 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001253 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001254 // same as primary
1255 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001256
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001257 // Check if EXIF package presents in the JPEG input.
1258 // If so, extract and remove the EXIF package.
1259 JpegDecoderHelper decoder;
1260 if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
1261 return ERROR_JPEGR_DECODE_ERROR;
1262 }
Ram Mohan880c5a32023-09-08 20:12:11 +05301263 jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
1264 jpegr_compressed_struct new_jpg_image = {.data = nullptr,
1265 .length = 0,
1266 .maxLength = 0,
1267 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
1268 std::unique_ptr<uint8_t[]> dest_data;
Ram Mohanf3c29792023-09-08 20:00:11 +05301269 if (decoder.getEXIFPos() >= 0) {
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001270 if (pExif != nullptr) {
1271 ALOGE("received EXIF from outside while the primary image already contains EXIF");
1272 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1273 }
1274 copyJpegWithoutExif(&new_jpg_image,
1275 primary_jpg_image_ptr,
1276 decoder.getEXIFPos(),
1277 decoder.getEXIFSize());
Ram Mohan880c5a32023-09-08 20:12:11 +05301278 dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001279 exif_from_jpg.data = decoder.getEXIFPtr();
1280 exif_from_jpg.length = decoder.getEXIFSize();
1281 pExif = &exif_from_jpg;
1282 }
1283
1284 jr_compressed_ptr final_primary_jpg_image_ptr =
1285 new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
1286
Dichen Zhang15345ea2023-02-23 23:54:32 +00001287 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001288 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001289 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001290 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1291 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001292
1293 // Write EXIF
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001294 if (pExif != nullptr) {
1295 const int length = 2 + pExif->length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001296 const uint8_t lengthH = ((length >> 8) & 0xff);
1297 const uint8_t lengthL = (length & 0xff);
1298 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1299 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1300 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1301 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001302 JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001303 }
1304
1305 // Prepare and write XMP
1306 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001307 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +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));
Dichen Zhang61ede362023-02-22 18:50:13 +00001315 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1316 }
1317
Nick Deakin0db53ee2023-05-19 17:14:45 -04001318 // Write ICC
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001319 if (pIcc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301320 const int length = icc_size + 2;
1321 const uint8_t lengthH = ((length >> 8) & 0xff);
1322 const uint8_t lengthL = (length & 0xff);
1323 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1324 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1325 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1326 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001327 JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001328 }
1329
Dichen Zhang61ede362023-02-22 18:50:13 +00001330 // Prepare and write MPF
1331 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301332 const int length = 2 + calculateMpfSize();
1333 const uint8_t lengthH = ((length >> 8) & 0xff);
1334 const uint8_t lengthL = (length & 0xff);
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001335 int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301336 // between APP2 + package size + signature
1337 // ff e2 00 58 4d 50 46 00
1338 // 2 + 2 + 4 = 8 (bytes)
1339 // and ff d8 sign of the secondary image
1340 int secondary_image_offset = primary_image_size - pos - 8;
1341 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1342 secondary_image_size, secondary_image_offset);
1343 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1344 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1345 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1346 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1347 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001348 }
1349
1350 // Write primary image
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001351 JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
1352 final_primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001353 // Finish primary image
1354
Dichen Zhang10959a42023-04-10 16:28:16 -07001355 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001356 // Write SOI
1357 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1358 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1359
1360 // Prepare and write XMP
1361 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001362 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001363 const uint8_t lengthH = ((length >> 8) & 0xff);
1364 const uint8_t lengthL = (length & 0xff);
1365 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1366 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1367 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1368 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1369 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1370 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1371 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001372
1373 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301374 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1375 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001376
1377 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001378 dest->length = pos;
1379
Dichen Zhangd18bc302022-12-16 20:55:24 +00001380 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001381 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001382}
1383
Dichen Zhang61ede362023-02-22 18:50:13 +00001384status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001385 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001386 return ERROR_JPEGR_INVALID_NULL_PTR;
1387 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301388 if (src->width != dest->width || src->height != dest->height) {
1389 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001390 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301391 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
Ram Mohanb2359cd2023-07-28 14:33:49 +05301392 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001393 for (size_t y = 0; y < src->height; ++y) {
Ram Mohaneca81942023-07-29 14:41:48 +05301394 uint16_t* src_y_row = src_y_data + y * src->luma_stride;
1395 uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001396 for (size_t x = 0; x < src->width; ++x) {
Ram Mohaneca81942023-07-29 14:41:48 +05301397 uint16_t y_uint = src_y_row[x] >> 6;
1398 dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1399 }
1400 if (dest->width != dest->luma_stride) {
1401 memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
1402 }
1403 }
1404 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1405 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1406 size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
1407 uint8_t* dst_v_data = dst_u_data + dst_v_offset;
1408 for (size_t y = 0; y < src->height / 2; ++y) {
1409 uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
1410 uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
1411 uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
1412 for (size_t x = 0; x < src->width / 2; ++x) {
1413 uint16_t u_uint = src_uv_row[x << 1] >> 6;
1414 uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
1415 dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1416 dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1417 }
1418 if (dest->width / 2 != dest->chroma_stride) {
1419 memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
1420 memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001421 }
1422 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001423 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001424 return NO_ERROR;
1425}
1426
Ram Mohanb2359cd2023-07-28 14:33:49 +05301427status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001428 ultrahdr_color_gamut dest_encoding) {
1429 if (image == nullptr) {
1430 return ERROR_JPEGR_INVALID_NULL_PTR;
1431 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301432 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1433 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001434 return ERROR_JPEGR_INVALID_COLORGAMUT;
1435 }
1436
1437 ColorTransformFn conversionFn = nullptr;
1438 switch (src_encoding) {
1439 case ULTRAHDR_COLORGAMUT_BT709:
1440 switch (dest_encoding) {
1441 case ULTRAHDR_COLORGAMUT_BT709:
1442 return NO_ERROR;
1443 case ULTRAHDR_COLORGAMUT_P3:
1444 conversionFn = yuv709To601;
1445 break;
1446 case ULTRAHDR_COLORGAMUT_BT2100:
1447 conversionFn = yuv709To2100;
1448 break;
1449 default:
1450 // Should be impossible to hit after input validation
1451 return ERROR_JPEGR_INVALID_COLORGAMUT;
1452 }
1453 break;
1454 case ULTRAHDR_COLORGAMUT_P3:
1455 switch (dest_encoding) {
1456 case ULTRAHDR_COLORGAMUT_BT709:
1457 conversionFn = yuv601To709;
1458 break;
1459 case ULTRAHDR_COLORGAMUT_P3:
1460 return NO_ERROR;
1461 case ULTRAHDR_COLORGAMUT_BT2100:
1462 conversionFn = yuv601To2100;
1463 break;
1464 default:
1465 // Should be impossible to hit after input validation
1466 return ERROR_JPEGR_INVALID_COLORGAMUT;
1467 }
1468 break;
1469 case ULTRAHDR_COLORGAMUT_BT2100:
1470 switch (dest_encoding) {
1471 case ULTRAHDR_COLORGAMUT_BT709:
1472 conversionFn = yuv2100To709;
1473 break;
1474 case ULTRAHDR_COLORGAMUT_P3:
1475 conversionFn = yuv2100To601;
1476 break;
1477 case ULTRAHDR_COLORGAMUT_BT2100:
1478 return NO_ERROR;
1479 default:
1480 // Should be impossible to hit after input validation
1481 return ERROR_JPEGR_INVALID_COLORGAMUT;
1482 }
1483 break;
1484 default:
1485 // Should be impossible to hit after input validation
1486 return ERROR_JPEGR_INVALID_COLORGAMUT;
1487 }
1488
1489 if (conversionFn == nullptr) {
1490 // Should be impossible to hit after input validation
1491 return ERROR_JPEGR_INVALID_COLORGAMUT;
1492 }
1493
1494 for (size_t y = 0; y < image->height / 2; ++y) {
1495 for (size_t x = 0; x < image->width / 2; ++x) {
1496 transformYuv420(image, x, y, conversionFn);
1497 }
1498 }
1499
1500 return NO_ERROR;
1501}
1502
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001503} // namespace android::ultrahdr