blob: 74760d9b32cc18ca20ffee1ec72f3b03cb8ddb7f [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) {
88 memcpy(pDest, pSource, sizeof(jpegr_compressed_struct));
89
90 const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
91 pDest->length = pSource->length - exif_size - exif_offset;
92 pDest->data = new uint8_t[pDest->length];
93 std::unique_ptr<uint8_t[]> dest_data;
94 dest_data.reset(reinterpret_cast<uint8_t*>(pDest->data));
95 memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
96 memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
97 (uint8_t*)pSource->data + exif_pos + exif_size,
98 pSource->length - exif_pos - exif_size);
99}
100
Ram Mohanb2359cd2023-07-28 14:33:49 +0530101status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
102 jr_uncompressed_ptr yuv420_image_ptr,
Ram Mohanac1cfec2023-05-18 14:41:15 +0530103 ultrahdr_transfer_function hdr_tf,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530104 jr_compressed_ptr dest_ptr) {
105 if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
106 ALOGE("Received nullptr for input p010 image");
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700107 return ERROR_JPEGR_INVALID_NULL_PTR;
108 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530109 if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
110 ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
111 p010_image_ptr->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530112 return ERROR_JPEGR_INVALID_INPUT_TYPE;
113 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530114 if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
115 ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
116 kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530117 return ERROR_JPEGR_INVALID_INPUT_TYPE;
118 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530119 if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
120 ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
121 kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
Ram Mohand136b8a2023-06-02 09:06:40 +0530122 return ERROR_JPEGR_INVALID_INPUT_TYPE;
123 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530124 if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
125 p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
126 ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530127 return ERROR_JPEGR_INVALID_INPUT_TYPE;
128 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530129 if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
130 ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
131 p010_image_ptr->luma_stride, p010_image_ptr->width);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700132 return ERROR_JPEGR_INVALID_INPUT_TYPE;
133 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530134 if (p010_image_ptr->chroma_data != nullptr &&
135 p010_image_ptr->chroma_stride < p010_image_ptr->width) {
136 ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
137 p010_image_ptr->chroma_stride, p010_image_ptr->width);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530138 return ERROR_JPEGR_INVALID_INPUT_TYPE;
139 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530140 if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
141 ALOGE("Received nullptr for destination");
Ram Mohanac1cfec2023-05-18 14:41:15 +0530142 return ERROR_JPEGR_INVALID_NULL_PTR;
143 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530144 if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530145 ALOGE("Invalid hdr transfer function %d", hdr_tf);
146 return ERROR_JPEGR_INVALID_INPUT_TYPE;
147 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530148 if (yuv420_image_ptr == nullptr) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700149 return NO_ERROR;
150 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530151 if (yuv420_image_ptr->data == nullptr) {
152 ALOGE("Received nullptr for uncompressed 420 image");
Ram Mohanac1cfec2023-05-18 14:41:15 +0530153 return ERROR_JPEGR_INVALID_NULL_PTR;
154 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530155 if (yuv420_image_ptr->luma_stride != 0 &&
156 yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
157 ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
158 yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
Ram Mohan43c3a802023-07-24 18:33:49 +0530159 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700160 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530161 if (yuv420_image_ptr->chroma_data != nullptr &&
162 yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
163 ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
164 yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
Ram Mohan43c3a802023-07-24 18:33:49 +0530165 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700166 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530167 if (p010_image_ptr->width != yuv420_image_ptr->width ||
168 p010_image_ptr->height != yuv420_image_ptr->height) {
169 ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
170 p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700171 return ERROR_JPEGR_RESOLUTION_MISMATCH;
172 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530173 if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
174 yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
175 ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530176 return ERROR_JPEGR_INVALID_INPUT_TYPE;
177 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530178 return NO_ERROR;
179}
180
Ram Mohanb2359cd2023-07-28 14:33:49 +0530181status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
182 jr_uncompressed_ptr yuv420_image_ptr,
Ram Mohanac1cfec2023-05-18 14:41:15 +0530183 ultrahdr_transfer_function hdr_tf,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530184 jr_compressed_ptr dest_ptr, int quality) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530185 if (quality < 0 || quality > 100) {
186 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
187 return ERROR_JPEGR_INVALID_INPUT_TYPE;
188 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530189 return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700190}
191
Dichen Zhang636f5242022-12-07 20:25:44 +0000192/* Encode API-0 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530193status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
194 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
195 // validate input arguments
196 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
197 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700198 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800199 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530200 if (exif != nullptr && exif->data == nullptr) {
201 ALOGE("received nullptr for exif metadata");
202 return ERROR_JPEGR_INVALID_NULL_PTR;
203 }
204
Ram Mohanb2359cd2023-07-28 14:33:49 +0530205 // clean up input structure for later usage
206 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
207 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
208 if (!p010_image.chroma_data) {
209 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
210 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
211 p010_image.chroma_stride = p010_image.luma_stride;
212 }
Dichen Zhang636f5242022-12-07 20:25:44 +0000213
Ram Mohaneca81942023-07-29 14:41:48 +0530214 const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530215 unique_ptr<uint8_t[]> yuv420_image_data =
Ram Mohaneca81942023-07-29 14:41:48 +0530216 make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530217 jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
218 .width = p010_image.width,
219 .height = p010_image.height,
220 .colorGamut = p010_image.colorGamut,
Ram Mohaneca81942023-07-29 14:41:48 +0530221 .luma_stride = yu420_luma_stride,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530222 .chroma_data = nullptr,
Ram Mohaneca81942023-07-29 14:41:48 +0530223 .chroma_stride = yu420_luma_stride >> 1};
224 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
225 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
Dichen Zhang636f5242022-12-07 20:25:44 +0000226
Ram Mohanb2359cd2023-07-28 14:33:49 +0530227 // tone map
228 JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
229
230 // gain map
231 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
232 jpegr_uncompressed_struct gainmap_image;
233 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
Dichen Zhang636f5242022-12-07 20:25:44 +0000234 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530235 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Dichen Zhang636f5242022-12-07 20:25:44 +0000236
Ram Mohanb2359cd2023-07-28 14:33:49 +0530237 // compress gain map
238 JpegEncoderHelper jpeg_enc_obj_gm;
239 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
240 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
241 .length = static_cast<int>(
242 jpeg_enc_obj_gm.getCompressedImageSize()),
243 .maxLength = static_cast<int>(
244 jpeg_enc_obj_gm.getCompressedImageSize()),
245 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Dichen Zhang636f5242022-12-07 20:25:44 +0000246
Ram Mohanb2359cd2023-07-28 14:33:49 +0530247 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000248
Ram Mohanb2359cd2023-07-28 14:33:49 +0530249 // convert to Bt601 YUV encoding for JPEG encode
250 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
251 JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
252 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400253
Ram Mohanb2359cd2023-07-28 14:33:49 +0530254 // compress 420 image
255 JpegEncoderHelper jpeg_enc_obj_yuv420;
Ram Mohaneca81942023-07-29 14:41:48 +0530256 if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
257 reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
258 yuv420_image.width, yuv420_image.height,
259 yuv420_image.luma_stride, yuv420_image.chroma_stride,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530260 quality, icc->getData(), icc->getLength())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000261 return ERROR_JPEGR_ENCODE_ERROR;
262 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530263 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
264 .length = static_cast<int>(
265 jpeg_enc_obj_yuv420.getCompressedImageSize()),
266 .maxLength = static_cast<int>(
267 jpeg_enc_obj_yuv420.getCompressedImageSize()),
268 .colorGamut = yuv420_image.colorGamut};
Dichen Zhang636f5242022-12-07 20:25:44 +0000269
Ram Mohanb2359cd2023-07-28 14:33:49 +0530270 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400271 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
272 &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000273
274 return NO_ERROR;
275}
276
277/* Encode API-1 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530278status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
279 jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
280 jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
281 // validate input arguments
282 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530283 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000284 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000285 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530286 if (exif != nullptr && exif->data == nullptr) {
287 ALOGE("received nullptr for exif metadata");
288 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhangffa34012022-11-03 23:21:13 +0000289 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530290 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
291 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700292 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800293 }
294
Ram Mohanb2359cd2023-07-28 14:33:49 +0530295 // clean up input structure for later usage
296 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
297 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
298 if (!p010_image.chroma_data) {
299 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
300 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
301 p010_image.chroma_stride = p010_image.luma_stride;
302 }
303 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
304 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
305 if (!yuv420_image.chroma_data) {
306 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
307 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
308 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Ram Mohan43c3a802023-07-24 18:33:49 +0530309 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400310
Ram Mohanb2359cd2023-07-28 14:33:49 +0530311 // gain map
312 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
313 jpegr_uncompressed_struct gainmap_image;
314 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
315 std::unique_ptr<uint8_t[]> map_data;
316 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400317
Ram Mohanb2359cd2023-07-28 14:33:49 +0530318 // compress gain map
319 JpegEncoderHelper jpeg_enc_obj_gm;
320 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
321 jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
322 .length = static_cast<int>(
323 jpeg_enc_obj_gm.getCompressedImageSize()),
324 .maxLength = static_cast<int>(
325 jpeg_enc_obj_gm.getCompressedImageSize()),
326 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
327
328 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
329
330 jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
331 unique_ptr<uint8_t[]> yuv_420_bt601_data;
332 // Convert to bt601 YUV encoding for JPEG encode
333 if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
Ram Mohaneca81942023-07-29 14:41:48 +0530334 const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
335 yuv_420_bt601_data =
336 make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
Ram Mohanb2359cd2023-07-28 14:33:49 +0530337 yuv420_bt601_image.data = yuv_420_bt601_data.get();
338 yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
Ram Mohaneca81942023-07-29 14:41:48 +0530339 yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530340 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
Ram Mohaneca81942023-07-29 14:41:48 +0530341 yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
342 yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530343
344 {
345 // copy luma
346 uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
347 uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
348 if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
349 memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
350 } else {
351 for (size_t i = 0; i < yuv420_image.height; i++) {
352 memcpy(y_dst, y_src, yuv420_image.width);
Ram Mohaneca81942023-07-29 14:41:48 +0530353 if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
354 memset(y_dst + yuv420_image.width, 0,
355 yuv420_bt601_image.luma_stride - yuv420_image.width);
356 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530357 y_dst += yuv420_bt601_image.luma_stride;
358 y_src += yuv420_image.luma_stride;
359 }
360 }
361 }
362
363 if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
364 // copy luma
365 uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
366 uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
367 memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
368 } else {
369 // copy cb & cr
370 uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
371 uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
372 uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
373 uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
374 for (size_t i = 0; i < yuv420_image.height / 2; i++) {
375 memcpy(cb_dst, cb_src, yuv420_image.width / 2);
376 memcpy(cr_dst, cr_src, yuv420_image.width / 2);
Ram Mohaneca81942023-07-29 14:41:48 +0530377 if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
378 memset(cb_dst + yuv420_image.width / 2, 0,
379 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
380 memset(cr_dst + yuv420_image.width / 2, 0,
381 yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
382 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530383 cb_dst += yuv420_bt601_image.chroma_stride;
384 cb_src += yuv420_image.chroma_stride;
385 cr_dst += yuv420_bt601_image.chroma_stride;
386 cr_src += yuv420_image.chroma_stride;
387 }
388 }
389 JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
390 }
391
392 // compress 420 image
393 JpegEncoderHelper jpeg_enc_obj_yuv420;
Ram Mohaneca81942023-07-29 14:41:48 +0530394 if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
395 reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
396 yuv420_bt601_image.width, yuv420_bt601_image.height,
397 yuv420_bt601_image.luma_stride,
398 yuv420_bt601_image.chroma_stride, quality, icc->getData(),
Ram Mohanb2359cd2023-07-28 14:33:49 +0530399 icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400400 return ERROR_JPEGR_ENCODE_ERROR;
401 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400402
Ram Mohanb2359cd2023-07-28 14:33:49 +0530403 jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
404 .length = static_cast<int>(
405 jpeg_enc_obj_yuv420.getCompressedImageSize()),
406 .maxLength = static_cast<int>(
407 jpeg_enc_obj_yuv420.getCompressedImageSize()),
408 .colorGamut = yuv420_image.colorGamut};
409
410 // append gain map, no ICC since JPEG encode already did it
Nick Deakin0db53ee2023-05-19 17:14:45 -0400411 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
412 &metadata, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000413 return NO_ERROR;
414}
415
Dichen Zhang636f5242022-12-07 20:25:44 +0000416/* Encode API-2 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530417status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
418 jr_uncompressed_ptr yuv420_image_ptr,
419 jr_compressed_ptr yuv420jpg_image_ptr,
420 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
421 // validate input arguments
422 if (yuv420_image_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530423 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000424 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000425 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530426 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530427 ALOGE("received nullptr for compressed jpeg image");
428 return ERROR_JPEGR_INVALID_NULL_PTR;
429 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530430 if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
431 ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700432 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800433 }
434
Ram Mohanb2359cd2023-07-28 14:33:49 +0530435 // clean up input structure for later usage
436 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
437 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
438 if (!p010_image.chroma_data) {
439 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
440 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
441 p010_image.chroma_stride = p010_image.luma_stride;
442 }
443 jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
444 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
445 if (!yuv420_image.chroma_data) {
446 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
447 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
448 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400449 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400450
Ram Mohanb2359cd2023-07-28 14:33:49 +0530451 // gain map
452 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
453 jpegr_uncompressed_struct gainmap_image;
454 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
455 std::unique_ptr<uint8_t[]> map_data;
456 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
457
458 // compress gain map
459 JpegEncoderHelper jpeg_enc_obj_gm;
460 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
461 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
462 .length = static_cast<int>(
463 jpeg_enc_obj_gm.getCompressedImageSize()),
464 .maxLength = static_cast<int>(
465 jpeg_enc_obj_gm.getCompressedImageSize()),
466 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
467
468 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000469}
470
Dichen Zhang636f5242022-12-07 20:25:44 +0000471/* Encode API-3 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530472status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
473 jr_compressed_ptr yuv420jpg_image_ptr,
474 ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
475 // validate input arguments
476 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530477 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000478 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000479 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530480 if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700481 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800482 }
483
Ram Mohanb2359cd2023-07-28 14:33:49 +0530484 // clean up input structure for later usage
485 jpegr_uncompressed_struct p010_image = *p010_image_ptr;
486 if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
487 if (!p010_image.chroma_data) {
488 uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
489 p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
490 p010_image.chroma_stride = p010_image.luma_stride;
491 }
492
493 // decode input jpeg, gamut is going to be bt601.
494 JpegDecoderHelper jpeg_dec_obj_yuv420;
495 if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
496 yuv420jpg_image_ptr->length)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400497 return ERROR_JPEGR_DECODE_ERROR;
498 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530499 jpegr_uncompressed_struct yuv420_image{};
500 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
501 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
502 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
503 yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
504 if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
505 if (!yuv420_image.chroma_data) {
506 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
507 yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
508 yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
509 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400510
Ram Mohanb2359cd2023-07-28 14:33:49 +0530511 if (p010_image_ptr->width != yuv420_image.width ||
512 p010_image_ptr->height != yuv420_image.height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400513 return ERROR_JPEGR_RESOLUTION_MISMATCH;
514 }
515
Ram Mohanb2359cd2023-07-28 14:33:49 +0530516 // gain map
517 ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
518 jpegr_uncompressed_struct gainmap_image;
519 JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
520 true /* sdr_is_601 */));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400521 std::unique_ptr<uint8_t[]> map_data;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530522 map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400523
Ram Mohanb2359cd2023-07-28 14:33:49 +0530524 // compress gain map
525 JpegEncoderHelper jpeg_enc_obj_gm;
526 JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
527 jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
528 .length = static_cast<int>(
529 jpeg_enc_obj_gm.getCompressedImageSize()),
530 .maxLength = static_cast<int>(
531 jpeg_enc_obj_gm.getCompressedImageSize()),
532 .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400533
Ram Mohanb2359cd2023-07-28 14:33:49 +0530534 return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
Dichen Zhang6947d532022-10-22 02:16:21 +0000535}
536
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000537/* Encode API-4 */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530538status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
539 jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000540 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530541 if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530542 ALOGE("received nullptr for compressed jpeg image");
543 return ERROR_JPEGR_INVALID_NULL_PTR;
544 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530545 if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530546 ALOGE("received nullptr for compressed gain map");
547 return ERROR_JPEGR_INVALID_NULL_PTR;
548 }
Ram Mohanac1cfec2023-05-18 14:41:15 +0530549 if (dest == nullptr || dest->data == nullptr) {
550 ALOGE("received nullptr for destination");
551 return ERROR_JPEGR_INVALID_NULL_PTR;
552 }
553
Nick Deakin0db53ee2023-05-19 17:14:45 -0400554 // We just want to check if ICC is present, so don't do a full decode. Note,
555 // this doesn't verify that the ICC is valid.
556 JpegDecoderHelper decoder;
557 std::vector<uint8_t> icc;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530558 decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
559 /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
560 /* exifData */ nullptr);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400561
562 // Add ICC if not already present.
563 if (icc.size() > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530564 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
565 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400566 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530567 sp<DataStruct> newIcc =
568 IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
569 JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
570 newIcc->getData(), newIcc->getLength(), metadata, dest));
Nick Deakin0db53ee2023-05-19 17:14:45 -0400571 }
572
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000573 return NO_ERROR;
574}
575
Ram Mohanb2359cd2023-07-28 14:33:49 +0530576status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
577 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530578 ALOGE("received nullptr for compressed jpegr image");
579 return ERROR_JPEGR_INVALID_NULL_PTR;
580 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530581 if (jpeg_image_info_ptr == nullptr) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530582 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000583 return ERROR_JPEGR_INVALID_NULL_PTR;
584 }
585
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530586 jpegr_compressed_struct primary_image, gainmap_image;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530587 status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530588 if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
589 return status;
590 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000591
Ram Mohanb2359cd2023-07-28 14:33:49 +0530592 JpegDecoderHelper jpeg_dec_obj_hdr;
593 if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
594 &jpeg_image_info_ptr->width,
595 &jpeg_image_info_ptr->height,
596 jpeg_image_info_ptr->iccData,
597 jpeg_image_info_ptr->exifData)) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000598 return ERROR_JPEGR_DECODE_ERROR;
599 }
600
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530601 return status;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000602}
603
Dichen Zhang636f5242022-12-07 20:25:44 +0000604/* Decode API */
Ram Mohanb2359cd2023-07-28 14:33:49 +0530605status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
606 float max_display_boost, jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000607 ultrahdr_output_format output_format,
Ram Mohanb2359cd2023-07-28 14:33:49 +0530608 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
609 if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
Ram Mohanb0375052023-05-20 04:03:48 +0530610 ALOGE("received nullptr for compressed jpegr image");
611 return ERROR_JPEGR_INVALID_NULL_PTR;
612 }
Ram Mohanb0375052023-05-20 04:03:48 +0530613 if (dest == nullptr || dest->data == nullptr) {
614 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000615 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000616 }
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000617 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530618 ALOGE("received bad value for max_display_boost %f", max_display_boost);
619 return ERROR_JPEGR_INVALID_INPUT_TYPE;
620 }
Ram Mohanb0375052023-05-20 04:03:48 +0530621 if (exif != nullptr && exif->data == nullptr) {
622 ALOGE("received nullptr address for exif data");
623 return ERROR_JPEGR_INVALID_INPUT_TYPE;
624 }
Ram Mohanb0375052023-05-20 04:03:48 +0530625 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
626 ALOGE("received bad value for output format %d", output_format);
627 return ERROR_JPEGR_INVALID_INPUT_TYPE;
628 }
629
Ram Mohanb2359cd2023-07-28 14:33:49 +0530630 jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530631 status_t status =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530632 extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530633 if (status != NO_ERROR) {
634 if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
635 ALOGE("received invalid compressed jpegr image");
636 return status;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000637 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000638 }
639
Ram Mohanb2359cd2023-07-28 14:33:49 +0530640 JpegDecoderHelper jpeg_dec_obj_yuv420;
641 if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
642 (output_format == ULTRAHDR_OUTPUT_SDR))) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400643 return ERROR_JPEGR_DECODE_ERROR;
644 }
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530645
646 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530647 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
648 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
649 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530650 return ERROR_JPEGR_CALCULATION_ERROR;
651 }
652 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530653 if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
654 jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
655 jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530656 return ERROR_JPEGR_CALCULATION_ERROR;
657 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530658 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400659
Dichen Zhangb80e2262023-03-08 06:59:51 +0000660 if (exif != nullptr) {
661 if (exif->data == nullptr) {
662 return ERROR_JPEGR_INVALID_NULL_PTR;
663 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530664 if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
Dichen Zhangb80e2262023-03-08 06:59:51 +0000665 return ERROR_JPEGR_BUFFER_TOO_SMALL;
666 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530667 memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
668 exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
Dichen Zhangb80e2262023-03-08 06:59:51 +0000669 }
670
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530671 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530672 dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
673 dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
674 memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
675 dest->width * dest->height * 4);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530676 return NO_ERROR;
677 }
678
Ram Mohanb2359cd2023-07-28 14:33:49 +0530679 JpegDecoderHelper jpeg_dec_obj_gm;
680 if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530681 return ERROR_JPEGR_DECODE_ERROR;
682 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530683 if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
684 jpeg_dec_obj_gm.getDecompressedImageSize()) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530685 return ERROR_JPEGR_CALCULATION_ERROR;
686 }
687
Ram Mohanb2359cd2023-07-28 14:33:49 +0530688 jpegr_uncompressed_struct gainmap_image;
689 gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
690 gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
691 gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000692
Ram Mohanb2359cd2023-07-28 14:33:49 +0530693 if (gainmap_image_ptr != nullptr) {
694 gainmap_image_ptr->width = gainmap_image.width;
695 gainmap_image_ptr->height = gainmap_image.height;
696 int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
697 gainmap_image_ptr->data = malloc(size);
698 memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530699 }
700
701 ultrahdr_metadata_struct uhdr_metadata;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530702 if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
703 jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530704 return ERROR_JPEGR_INVALID_METADATA;
705 }
706
707 if (metadata != nullptr) {
708 metadata->version = uhdr_metadata.version;
709 metadata->minContentBoost = uhdr_metadata.minContentBoost;
710 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
711 metadata->gamma = uhdr_metadata.gamma;
712 metadata->offsetSdr = uhdr_metadata.offsetSdr;
713 metadata->offsetHdr = uhdr_metadata.offsetHdr;
714 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
715 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
716 }
717
Ram Mohanb2359cd2023-07-28 14:33:49 +0530718 jpegr_uncompressed_struct yuv420_image;
719 yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
720 yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
721 yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
722 yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
723 jpeg_dec_obj_yuv420.getICCSize());
724 yuv420_image.luma_stride = yuv420_image.width;
725 uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
726 yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
727 yuv420_image.chroma_stride = yuv420_image.width >> 1;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400728
Ram Mohanb2359cd2023-07-28 14:33:49 +0530729 JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700730 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000731 return NO_ERROR;
732}
733
Ram Mohanb2359cd2023-07-28 14:33:49 +0530734status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
735 JpegEncoderHelper* jpeg_enc_obj_ptr) {
736 if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000737 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700738 }
739
Nick Deakin0db53ee2023-05-19 17:14:45 -0400740 // Don't need to convert YUV to Bt601 since single channel
Ram Mohaneca81942023-07-29 14:41:48 +0530741 if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
742 gainmap_image_ptr->width, gainmap_image_ptr->height,
743 gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
744 nullptr, 0)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400745 return ERROR_JPEGR_ENCODE_ERROR;
746 }
747
Dichen Zhang6947d532022-10-22 02:16:21 +0000748 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700749}
750
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800751const int kJobSzInRows = 16;
752static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
753 "align job size to kMapDimensionScaleFactor");
754
755class JobQueue {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530756public:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800757 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
758 void enqueueJob(size_t rowStart, size_t rowEnd);
759 void markQueueForEnd();
760 void reset();
761
Ram Mohanb2359cd2023-07-28 14:33:49 +0530762private:
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800763 bool mQueuedAllJobs = false;
764 std::deque<std::tuple<size_t, size_t>> mJobs;
765 std::mutex mMutex;
766 std::condition_variable mCv;
767};
768
769bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
770 std::unique_lock<std::mutex> lock{mMutex};
771 while (true) {
772 if (mJobs.empty()) {
773 if (mQueuedAllJobs) {
774 return false;
775 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000776 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800777 }
778 } else {
779 auto it = mJobs.begin();
780 rowStart = std::get<0>(*it);
781 rowEnd = std::get<1>(*it);
782 mJobs.erase(it);
783 return true;
784 }
785 }
786 return false;
787}
788
789void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
790 std::unique_lock<std::mutex> lock{mMutex};
791 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
792 lock.unlock();
793 mCv.notify_one();
794}
795
796void JobQueue::markQueueForEnd() {
797 std::unique_lock<std::mutex> lock{mMutex};
798 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000799 lock.unlock();
800 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800801}
802
803void JobQueue::reset() {
804 std::unique_lock<std::mutex> lock{mMutex};
805 mJobs.clear();
806 mQueuedAllJobs = false;
807}
808
Ram Mohanb2359cd2023-07-28 14:33:49 +0530809status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
810 jr_uncompressed_ptr p010_image_ptr,
811 ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
812 jr_uncompressed_ptr dest, bool sdr_is_601) {
813 if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530814 dest == nullptr || yuv420_image_ptr->data == nullptr ||
815 yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
816 p010_image_ptr->chroma_data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000817 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700818 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530819 if (yuv420_image_ptr->width != p010_image_ptr->width ||
820 yuv420_image_ptr->height != p010_image_ptr->height) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400821 return ERROR_JPEGR_RESOLUTION_MISMATCH;
822 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530823 if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
824 p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500825 return ERROR_JPEGR_INVALID_COLORGAMUT;
826 }
827
Ram Mohanb2359cd2023-07-28 14:33:49 +0530828 size_t image_width = yuv420_image_ptr->width;
829 size_t image_height = yuv420_image_ptr->height;
Ram Mohan2c3d7fb2023-08-12 04:37:39 +0530830 size_t map_width = image_width / kMapDimensionScaleFactor;
831 size_t map_height = image_height / kMapDimensionScaleFactor;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400832
Ram Mohanb2359cd2023-07-28 14:33:49 +0530833 dest->data = new uint8_t[map_width * map_height];
Ram Mohan43c3a802023-07-24 18:33:49 +0530834 dest->width = map_width;
835 dest->height = map_height;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000836 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530837 dest->luma_stride = map_width;
838 dest->chroma_data = nullptr;
839 dest->chroma_stride = 0;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400840 std::unique_ptr<uint8_t[]> map_data;
841 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
842
Nick Deakin6bd90432022-11-20 16:26:37 -0500843 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohanebfb3732023-08-21 18:12:53 +0530844 float hdr_white_nits;
Nick Deakin01759062023-02-02 18:21:43 -0500845 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000846 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000847 hdrInvOetf = identityConversion;
Ram Mohanebfb3732023-08-21 18:12:53 +0530848 // Note: this will produce clipping if the input exceeds kHlgMaxNits.
849 // TODO: TF LINEAR will be deprecated.
850 hdr_white_nits = kHlgMaxNits;
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000851 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000852 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800853#if USE_HLG_INVOETF_LUT
854 hdrInvOetf = hlgInvOetfLUT;
855#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500856 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800857#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500858 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500859 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000860 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800861#if USE_PQ_INVOETF_LUT
862 hdrInvOetf = pqInvOetfLUT;
863#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500864 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800865#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500866 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500867 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000868 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000869 // Should be impossible to hit after input validation.
870 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500871 }
872
Nick Deakina2215292023-02-14 21:40:06 -0500873 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
874 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400875 metadata->gamma = 1.0f;
876 metadata->offsetSdr = 0.0f;
877 metadata->offsetHdr = 0.0f;
878 metadata->hdrCapacityMin = 1.0f;
879 metadata->hdrCapacityMax = metadata->maxContentBoost;
880
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700881 float log2MinBoost = log2(metadata->minContentBoost);
882 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500883
Ram Mohanb2359cd2023-07-28 14:33:49 +0530884 ColorTransformFn hdrGamutConversionFn =
885 getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
Nick Deakin6bd90432022-11-20 16:26:37 -0500886
887 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400888 ColorTransformFn sdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530889 switch (yuv420_image_ptr->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000890 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500891 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400892 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500893 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000894 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500895 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400896 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500897 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000898 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500899 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400900 sdrYuvToRgbFn = bt2100YuvToRgb;
901 break;
902 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
903 // Should be impossible to hit after input validation.
904 return ERROR_JPEGR_INVALID_COLORGAMUT;
905 }
906 if (sdr_is_601) {
907 sdrYuvToRgbFn = p3YuvToRgb;
908 }
909
910 ColorTransformFn hdrYuvToRgbFn = nullptr;
Ram Mohanb2359cd2023-07-28 14:33:49 +0530911 switch (p010_image_ptr->colorGamut) {
Nick Deakin0db53ee2023-05-19 17:14:45 -0400912 case ULTRAHDR_COLORGAMUT_BT709:
913 hdrYuvToRgbFn = srgbYuvToRgb;
914 break;
915 case ULTRAHDR_COLORGAMUT_P3:
916 hdrYuvToRgbFn = p3YuvToRgb;
917 break;
918 case ULTRAHDR_COLORGAMUT_BT2100:
919 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500920 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000921 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500922 // Should be impossible to hit after input validation.
923 return ERROR_JPEGR_INVALID_COLORGAMUT;
924 }
925
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800926 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800927 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
928 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
929 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500930
Ram Mohanb2359cd2023-07-28 14:33:49 +0530931 std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
932 hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
933 hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
934 &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800935 size_t rowStart, rowEnd;
936 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
937 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530938 for (size_t x = 0; x < dest->width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530939 Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400940 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
941 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800942#if USE_SRGB_INVOETF_LUT
943 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
944#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800945 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800946#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800947 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
948
Ram Mohanb2359cd2023-07-28 14:33:49 +0530949 Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400950 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800951 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
952 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
953 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
954
Ram Mohan43c3a802023-07-24 18:33:49 +0530955 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800956 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Ram Mohanb2359cd2023-07-28 14:33:49 +0530957 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800958 }
959 }
960 }
961 };
962
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800963 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500964 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800965 for (int th = 0; th < threads - 1; th++) {
966 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400967 }
968
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800969 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
970 for (size_t rowStart = 0; rowStart < map_height;) {
971 size_t rowEnd = std::min(rowStart + rowStep, map_height);
972 jobQueue.enqueueJob(rowStart, rowEnd);
973 rowStart = rowEnd;
974 }
975 jobQueue.markQueueForEnd();
976 generateMap();
977 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
978
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400979 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000980 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700981}
982
Ram Mohanb2359cd2023-07-28 14:33:49 +0530983status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
984 jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
985 ultrahdr_output_format output_format, float max_display_boost,
Dichen Zhang10959a42023-04-10 16:28:16 -0700986 jr_uncompressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530987 if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
Ram Mohaneca81942023-07-29 14:41:48 +0530988 dest == nullptr || yuv420_image_ptr->data == nullptr ||
989 yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000990 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700991 }
Ram Mohanb2359cd2023-07-28 14:33:49 +0530992 if (metadata->version.compare(kJpegrVersion)) {
993 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
994 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400995 }
996 if (metadata->gamma != 1.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +0530997 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
998 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -0400999 }
1000 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301001 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
1002 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -04001003 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301004 if (metadata->hdrCapacityMin != metadata->minContentBoost ||
1005 metadata->hdrCapacityMax != metadata->maxContentBoost) {
1006 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
1007 metadata->hdrCapacityMax);
1008 return ERROR_JPEGR_UNSUPPORTED_METADATA;
Nick Deakin094946b2023-06-09 11:58:41 -04001009 }
1010
Ram Mohan9b3d6852023-05-26 00:09:50 +05301011 // TODO: remove once map scaling factor is computed based on actual map dims
Ram Mohanb2359cd2023-07-28 14:33:49 +05301012 size_t image_width = yuv420_image_ptr->width;
1013 size_t image_height = yuv420_image_ptr->height;
Ram Mohan2c3d7fb2023-08-12 04:37:39 +05301014 size_t map_width = image_width / kMapDimensionScaleFactor;
1015 size_t map_height = image_height / kMapDimensionScaleFactor;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301016 if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
Ram Mohaneca81942023-07-29 14:41:48 +05301017 ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
1018 "resolution is %dx%d, received gain map resolution is %dx%d",
1019 (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
Ram Mohan9b3d6852023-05-26 00:09:50 +05301020 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1021 }
1022
Ram Mohanb2359cd2023-07-28 14:33:49 +05301023 dest->width = yuv420_image_ptr->width;
1024 dest->height = yuv420_image_ptr->height;
Ram Mohanfe723d62022-12-15 00:59:11 +05301025 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001026 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001027 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301028
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001029 JobQueue jobQueue;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301030 std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
1031 &jobQueue, &idwTable, output_format, &gainLUT,
1032 display_boost]() -> void {
1033 size_t width = yuv420_image_ptr->width;
1034 size_t height = yuv420_image_ptr->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001035
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001036 size_t rowStart, rowEnd;
1037 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1038 for (size_t y = rowStart; y < rowEnd; ++y) {
1039 for (size_t x = 0; x < width; ++x) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301040 Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001041 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1042 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1043 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001044#if USE_SRGB_INVOETF_LUT
1045 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1046#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001047 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001048#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001049 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001050 // TODO: determine map scaling factor based on actual map dims
1051 size_t map_scale_factor = kMapDimensionScaleFactor;
1052 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1053 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1054 // later.
1055 if (map_scale_factor != floorf(map_scale_factor)) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301056 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001057 } else {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301058 gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001059 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001060
Dichen Zhang10959a42023-04-10 16:28:16 -07001061#if USE_APPLY_GAIN_LUT
1062 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001063#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001064 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001065#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001066 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001067 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001068
1069 switch (output_format) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301070 case ULTRAHDR_OUTPUT_HDR_LINEAR: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001071 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1072 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1073 break;
1074 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301075 case ULTRAHDR_OUTPUT_HDR_HLG: {
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001076#if USE_HLG_OETF_LUT
1077 ColorTransformFn hdrOetf = hlgOetfLUT;
1078#else
1079 ColorTransformFn hdrOetf = hlgOetf;
1080#endif
1081 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1082 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1083 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1084 break;
1085 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301086 case ULTRAHDR_OUTPUT_HDR_PQ: {
Ram Mohan67862992023-06-22 15:56:05 +05301087#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001088 ColorTransformFn hdrOetf = pqOetfLUT;
1089#else
1090 ColorTransformFn hdrOetf = pqOetf;
1091#endif
1092 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1093 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1094 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1095 break;
1096 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301097 default: {
1098 }
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001099 // Should be impossible to hit after input validation.
1100 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001101 }
1102 }
1103 }
1104 };
1105
1106 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1107 std::vector<std::thread> workers;
1108 for (int th = 0; th < threads - 1; th++) {
1109 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001110 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301111 const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
1112 for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
1113 int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001114 jobQueue.enqueueJob(rowStart, rowEnd);
1115 rowStart = rowEnd;
1116 }
1117 jobQueue.markQueueForEnd();
1118 applyRecMap();
1119 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001120 return NO_ERROR;
1121}
1122
Ram Mohanb2359cd2023-07-28 14:33:49 +05301123status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
1124 jr_compressed_ptr primary_jpg_image_ptr,
1125 jr_compressed_ptr gainmap_jpg_image_ptr) {
1126 if (jpegr_image_ptr == nullptr) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001127 return ERROR_JPEGR_INVALID_NULL_PTR;
1128 }
1129
1130 MessageHandler msg_handler;
1131 std::shared_ptr<DataSegment> seg =
Ram Mohanb2359cd2023-07-28 14:33:49 +05301132 DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
1133 static_cast<const uint8_t*>(jpegr_image_ptr->data),
1134 DataSegment::BufferDispositionPolicy::kDontDelete);
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001135 DataSegmentDataSource data_source(seg);
1136 JpegInfoBuilder jpeg_info_builder;
1137 jpeg_info_builder.SetImageLimit(2);
1138 JpegScanner jpeg_scanner(&msg_handler);
1139 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1140 data_source.Reset();
1141
1142 if (jpeg_scanner.HasError()) {
1143 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1144 }
1145
1146 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1147 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001148
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301149 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001150 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1151 }
1152
Ram Mohanb2359cd2023-07-28 14:33:49 +05301153 if (primary_jpg_image_ptr != nullptr) {
1154 primary_jpg_image_ptr->data =
1155 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
1156 primary_jpg_image_ptr->length = image_ranges[0].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001157 }
1158
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301159 if (image_ranges.size() == 1) {
1160 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1161 }
1162
Ram Mohanb2359cd2023-07-28 14:33:49 +05301163 if (gainmap_jpg_image_ptr != nullptr) {
1164 gainmap_jpg_image_ptr->data =
1165 static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
1166 gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001167 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001168
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301169 // TODO: choose primary image and gain map image carefully
1170 if (image_ranges.size() > 2) {
1171 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1172 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001173 }
1174
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301175 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001176}
1177
Dichen Zhangd18bc302022-12-16 20:55:24 +00001178// JPEG/R structure:
1179// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001180//
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001181// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
1182// in the JPEG input (Encode API-2, API-3, API-4))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001183// APP1 (ff e1)
1184// 2 bytes of length (2 + length of exif package)
1185// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001186//
1187// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001188// 2 bytes of length (2 + 29 + length of xmp package)
1189// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001190// XMP
1191//
1192// (Required, MPF package) APP2 (ff e2)
1193// 2 bytes of length
1194// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001195//
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001196// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001197//
Dichen Zhang61ede362023-02-22 18:50:13 +00001198// SOI (ff d8)
1199//
1200// (Required, XMP package) APP1 (ff e1)
1201// 2 bytes of length (2 + 29 + length of xmp package)
1202// name space ("http://ns.adobe.com/xap/1.0/\0")
1203// XMP
1204//
Dichen Zhang10959a42023-04-10 16:28:16 -07001205// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001206//
1207// Metadata versions we are using:
1208// ECMA TR-98 for JFIF marker
1209// Exif 2.2 spec for EXIF marker
1210// Adobe XMP spec part 3 for XMP marker
1211// ICC v4.3 spec for ICC
Ram Mohanb2359cd2023-07-28 14:33:49 +05301212status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001213 jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
1214 void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001215 jr_compressed_ptr dest) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301216 if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
1217 dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001218 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001219 }
Nick Deakin094946b2023-06-09 11:58:41 -04001220 if (metadata->version.compare("1.0")) {
1221 ALOGE("received bad value for version: %s", metadata->version.c_str());
1222 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1223 }
1224 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301225 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301226 metadata->maxContentBoost);
Ram Mohancd3f6372023-06-02 15:16:15 +05301227 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1228 }
Nick Deakin094946b2023-06-09 11:58:41 -04001229 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1230 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
Ram Mohanb2359cd2023-07-28 14:33:49 +05301231 metadata->hdrCapacityMax);
Nick Deakin094946b2023-06-09 11:58:41 -04001232 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1233 }
Nick Deakin094946b2023-06-09 11:58:41 -04001234 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301235 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
Nick Deakin094946b2023-06-09 11:58:41 -04001236 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1237 }
Nick Deakin094946b2023-06-09 11:58:41 -04001238 if (metadata->gamma <= 0.0f) {
1239 ALOGE("received bad value for gamma %f", metadata->gamma);
1240 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1241 }
1242
Dichen Zhang15345ea2023-02-23 23:54:32 +00001243 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
Ram Mohanb2359cd2023-07-28 14:33:49 +05301244 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001245
Dichen Zhang15345ea2023-02-23 23:54:32 +00001246 // calculate secondary image length first, because the length will be written into the primary
1247 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001248 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001249 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301250 + nameSpaceLength /* 29 bytes length of name space including \0 */
1251 + xmp_secondary.size(); /* length of xmp packet */
Dichen Zhang15345ea2023-02-23 23:54:32 +00001252 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
Ram Mohanb2359cd2023-07-28 14:33:49 +05301253 + xmp_secondary_length + gainmap_jpg_image_ptr->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001254 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001255 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001256 // same as primary
1257 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001258
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001259 // Check if EXIF package presents in the JPEG input.
1260 // If so, extract and remove the EXIF package.
1261 JpegDecoderHelper decoder;
1262 if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
1263 return ERROR_JPEGR_DECODE_ERROR;
1264 }
1265 jpegr_exif_struct exif_from_jpg;
1266 exif_from_jpg.data = nullptr;
1267 exif_from_jpg.length = 0;
1268 jpegr_compressed_struct new_jpg_image;
1269 new_jpg_image.data = nullptr;
1270 new_jpg_image.length = 0;
1271 if (decoder.getEXIFPos() != 0) {
1272 if (pExif != nullptr) {
1273 ALOGE("received EXIF from outside while the primary image already contains EXIF");
1274 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1275 }
1276 copyJpegWithoutExif(&new_jpg_image,
1277 primary_jpg_image_ptr,
1278 decoder.getEXIFPos(),
1279 decoder.getEXIFSize());
1280 exif_from_jpg.data = decoder.getEXIFPtr();
1281 exif_from_jpg.length = decoder.getEXIFSize();
1282 pExif = &exif_from_jpg;
1283 }
1284
1285 jr_compressed_ptr final_primary_jpg_image_ptr =
1286 new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
1287
Dichen Zhang15345ea2023-02-23 23:54:32 +00001288 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001289 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001290 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001291 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1292 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001293
1294 // Write EXIF
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001295 if (pExif != nullptr) {
1296 const int length = 2 + pExif->length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001297 const uint8_t lengthH = ((length >> 8) & 0xff);
1298 const uint8_t lengthL = (length & 0xff);
1299 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1300 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1301 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1302 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001303 JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001304 }
1305
1306 // Prepare and write XMP
1307 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001308 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001309 const uint8_t lengthH = ((length >> 8) & 0xff);
1310 const uint8_t lengthL = (length & 0xff);
1311 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1312 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1313 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1314 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1315 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001316 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1317 }
1318
Nick Deakin0db53ee2023-05-19 17:14:45 -04001319 // Write ICC
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001320 if (pIcc != nullptr && icc_size > 0) {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301321 const int length = icc_size + 2;
1322 const uint8_t lengthH = ((length >> 8) & 0xff);
1323 const uint8_t lengthL = (length & 0xff);
1324 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1325 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1326 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1327 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001328 JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
Nick Deakin0db53ee2023-05-19 17:14:45 -04001329 }
1330
Dichen Zhang61ede362023-02-22 18:50:13 +00001331 // Prepare and write MPF
1332 {
Ram Mohanb2359cd2023-07-28 14:33:49 +05301333 const int length = 2 + calculateMpfSize();
1334 const uint8_t lengthH = ((length >> 8) & 0xff);
1335 const uint8_t lengthL = (length & 0xff);
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001336 int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
Ram Mohanb2359cd2023-07-28 14:33:49 +05301337 // between APP2 + package size + signature
1338 // ff e2 00 58 4d 50 46 00
1339 // 2 + 2 + 4 = 8 (bytes)
1340 // and ff d8 sign of the secondary image
1341 int secondary_image_offset = primary_image_size - pos - 8;
1342 sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
1343 secondary_image_size, secondary_image_offset);
1344 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1345 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1346 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1347 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1348 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001349 }
1350
1351 // Write primary image
Dichen Zhang6b3cd9a2023-08-18 01:48:10 +00001352 JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
1353 final_primary_jpg_image_ptr->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001354 // Finish primary image
1355
Dichen Zhang10959a42023-04-10 16:28:16 -07001356 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001357 // Write SOI
1358 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1359 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1360
1361 // Prepare and write XMP
1362 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001363 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001364 const uint8_t lengthH = ((length >> 8) & 0xff);
1365 const uint8_t lengthL = (length & 0xff);
1366 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1367 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1368 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1369 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1370 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1371 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1372 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001373
1374 // Write secondary image
Ram Mohanb2359cd2023-07-28 14:33:49 +05301375 JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
1376 gainmap_jpg_image_ptr->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001377
1378 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001379 dest->length = pos;
1380
Dichen Zhangd18bc302022-12-16 20:55:24 +00001381 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001382 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001383}
1384
Dichen Zhang61ede362023-02-22 18:50:13 +00001385status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001386 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001387 return ERROR_JPEGR_INVALID_NULL_PTR;
1388 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301389 if (src->width != dest->width || src->height != dest->height) {
1390 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001391 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301392 uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
Ram Mohanb2359cd2023-07-28 14:33:49 +05301393 uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001394 for (size_t y = 0; y < src->height; ++y) {
Ram Mohaneca81942023-07-29 14:41:48 +05301395 uint16_t* src_y_row = src_y_data + y * src->luma_stride;
1396 uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001397 for (size_t x = 0; x < src->width; ++x) {
Ram Mohaneca81942023-07-29 14:41:48 +05301398 uint16_t y_uint = src_y_row[x] >> 6;
1399 dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1400 }
1401 if (dest->width != dest->luma_stride) {
1402 memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
1403 }
1404 }
1405 uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
1406 uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
1407 size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
1408 uint8_t* dst_v_data = dst_u_data + dst_v_offset;
1409 for (size_t y = 0; y < src->height / 2; ++y) {
1410 uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
1411 uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
1412 uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
1413 for (size_t x = 0; x < src->width / 2; ++x) {
1414 uint16_t u_uint = src_uv_row[x << 1] >> 6;
1415 uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
1416 dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1417 dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1418 }
1419 if (dest->width / 2 != dest->chroma_stride) {
1420 memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
1421 memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001422 }
1423 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001424 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001425 return NO_ERROR;
1426}
1427
Ram Mohanb2359cd2023-07-28 14:33:49 +05301428status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001429 ultrahdr_color_gamut dest_encoding) {
1430 if (image == nullptr) {
1431 return ERROR_JPEGR_INVALID_NULL_PTR;
1432 }
Ram Mohanb2359cd2023-07-28 14:33:49 +05301433 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
1434 dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin0db53ee2023-05-19 17:14:45 -04001435 return ERROR_JPEGR_INVALID_COLORGAMUT;
1436 }
1437
1438 ColorTransformFn conversionFn = nullptr;
1439 switch (src_encoding) {
1440 case ULTRAHDR_COLORGAMUT_BT709:
1441 switch (dest_encoding) {
1442 case ULTRAHDR_COLORGAMUT_BT709:
1443 return NO_ERROR;
1444 case ULTRAHDR_COLORGAMUT_P3:
1445 conversionFn = yuv709To601;
1446 break;
1447 case ULTRAHDR_COLORGAMUT_BT2100:
1448 conversionFn = yuv709To2100;
1449 break;
1450 default:
1451 // Should be impossible to hit after input validation
1452 return ERROR_JPEGR_INVALID_COLORGAMUT;
1453 }
1454 break;
1455 case ULTRAHDR_COLORGAMUT_P3:
1456 switch (dest_encoding) {
1457 case ULTRAHDR_COLORGAMUT_BT709:
1458 conversionFn = yuv601To709;
1459 break;
1460 case ULTRAHDR_COLORGAMUT_P3:
1461 return NO_ERROR;
1462 case ULTRAHDR_COLORGAMUT_BT2100:
1463 conversionFn = yuv601To2100;
1464 break;
1465 default:
1466 // Should be impossible to hit after input validation
1467 return ERROR_JPEGR_INVALID_COLORGAMUT;
1468 }
1469 break;
1470 case ULTRAHDR_COLORGAMUT_BT2100:
1471 switch (dest_encoding) {
1472 case ULTRAHDR_COLORGAMUT_BT709:
1473 conversionFn = yuv2100To709;
1474 break;
1475 case ULTRAHDR_COLORGAMUT_P3:
1476 conversionFn = yuv2100To601;
1477 break;
1478 case ULTRAHDR_COLORGAMUT_BT2100:
1479 return NO_ERROR;
1480 default:
1481 // Should be impossible to hit after input validation
1482 return ERROR_JPEGR_INVALID_COLORGAMUT;
1483 }
1484 break;
1485 default:
1486 // Should be impossible to hit after input validation
1487 return ERROR_JPEGR_INVALID_COLORGAMUT;
1488 }
1489
1490 if (conversionFn == nullptr) {
1491 // Should be impossible to hit after input validation
1492 return ERROR_JPEGR_INVALID_COLORGAMUT;
1493 }
1494
1495 for (size_t y = 0; y < image->height / 2; ++y) {
1496 for (size_t x = 0; x < image->width / 2; ++x) {
1497 transformYuv420(image, x, y, conversionFn);
1498 }
1499 }
1500
1501 return NO_ERROR;
1502}
1503
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001504} // namespace android::ultrahdr