blob: 9c57f34c2a725d71b449e4fd6da71bce2e082f26 [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
Dichen Zhangdbceb0e2023-04-14 19:03:18 +000017#include <ultrahdr/jpegr.h>
18#include <ultrahdr/jpegencoderhelper.h>
19#include <ultrahdr/jpegdecoderhelper.h>
20#include <ultrahdr/gainmapmath.h>
21#include <ultrahdr/jpegrutils.h>
22#include <ultrahdr/multipictureformat.h>
23#include <ultrahdr/icc.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040024
Dichen Zhanga8766262022-11-07 23:48:24 +000025#include <image_io/jpeg/jpeg_marker.h>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000026#include <image_io/jpeg/jpeg_info.h>
27#include <image_io/jpeg/jpeg_scanner.h>
28#include <image_io/jpeg/jpeg_info_builder.h>
29#include <image_io/base/data_segment_data_source.h>
Dichen Zhang53751272023-01-17 19:09:01 -080030#include <utils/Log.h>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040031
Dichen Zhang6438a192023-01-29 07:51:15 +000032#include <map>
Nick Deakinf6bca5a2022-11-04 10:43:43 -040033#include <memory>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000034#include <sstream>
35#include <string>
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000036#include <cmath>
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080037#include <condition_variable>
38#include <deque>
39#include <mutex>
40#include <thread>
41#include <unistd.h>
Dichen Zhang72fd2b12022-11-01 06:11:50 +000042
43using namespace std;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +000044using namespace photos_editing_formats::image_io;
Dichen Zhang85b37562022-10-11 11:08:28 -070045
Dichen Zhangdbceb0e2023-04-14 19:03:18 +000046namespace android::ultrahdr {
Dichen Zhang85b37562022-10-11 11:08:28 -070047
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080048#define USE_SRGB_INVOETF_LUT 1
49#define USE_HLG_OETF_LUT 1
50#define USE_PQ_OETF_LUT 1
51#define USE_HLG_INVOETF_LUT 1
52#define USE_PQ_INVOETF_LUT 1
Dichen Zhang10959a42023-04-10 16:28:16 -070053#define USE_APPLY_GAIN_LUT 1
Harish Mahendrakar555a06b2022-12-14 09:37:27 -080054
Nick Deakinf6bca5a2022-11-04 10:43:43 -040055#define JPEGR_CHECK(x) \
56 { \
57 status_t status = (x); \
58 if ((status) != NO_ERROR) { \
59 return status; \
60 } \
61 }
62
Nick Deakin6bd90432022-11-20 16:26:37 -050063// The current JPEGR version that we encode to
Nick Deakin05ceebf2023-04-19 15:27:13 -040064static const char* const kJpegrVersion = "1.0";
Nick Deakin6bd90432022-11-20 16:26:37 -050065
Nick Deakinf6bca5a2022-11-04 10:43:43 -040066// Map is quarter res / sixteenth size
67static const size_t kMapDimensionScaleFactor = 4;
Ram Mohan033270a2023-05-18 15:26:33 +053068
69// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
70// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
71// 1 sample is sufficient. We are using 2 here anyways
72static const int kMinWidth = 2 * kMapDimensionScaleFactor;
73static const int kMinHeight = 2 * kMapDimensionScaleFactor;
74
Dichen Zhang53751272023-01-17 19:09:01 -080075// JPEG block size.
Dichen Zhang56a7d592023-04-14 16:57:34 +000076// JPEG encoding / decoding will require block based DCT transform 16 x 16 for luma,
77// and 8 x 8 for chroma.
78// Width must be 16 dividable for luma, and 8 dividable for chroma.
Ram Mohan4a72c192023-05-22 19:05:06 +053079// If this criteria is not facilitated, we will pad zeros based to each line on the
80// required block size.
Dichen Zhang56a7d592023-04-14 16:57:34 +000081static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
Dichen Zhang10959a42023-04-10 16:28:16 -070082// JPEG compress quality (0 ~ 100) for gain map
Dichen Zhang0b9f7de2022-11-18 06:52:46 +000083static const int kMapCompressQuality = 85;
Nick Deakinf6bca5a2022-11-04 10:43:43 -040084
Harish Mahendrakar72b6f302022-12-16 10:39:15 -080085#define CONFIG_MULTITHREAD 1
86int GetCPUCoreCount() {
87 int cpuCoreCount = 1;
88#if CONFIG_MULTITHREAD
89#if defined(_SC_NPROCESSORS_ONLN)
90 cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
91#else
92 // _SC_NPROC_ONLN must be defined...
93 cpuCoreCount = sysconf(_SC_NPROC_ONLN);
94#endif
95#endif
96 return cpuCoreCount;
97}
98
Ram Mohanac1cfec2023-05-18 14:41:15 +053099status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
100 jr_uncompressed_ptr uncompressed_yuv_420_image,
101 ultrahdr_transfer_function hdr_tf,
102 jr_compressed_ptr dest) {
103 if (uncompressed_p010_image == nullptr || uncompressed_p010_image->data == nullptr) {
104 ALOGE("received nullptr for uncompressed p010 image");
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700105 return ERROR_JPEGR_INVALID_NULL_PTR;
106 }
107
Ram Mohanac1cfec2023-05-18 14:41:15 +0530108 if (uncompressed_p010_image->width % 2 != 0
109 || uncompressed_p010_image->height % 2 != 0) {
110 ALOGE("Image dimensions cannot be odd, image dimensions %dx%d",
111 uncompressed_p010_image->width, uncompressed_p010_image->height);
112 return ERROR_JPEGR_INVALID_INPUT_TYPE;
113 }
114
Ram Mohan033270a2023-05-18 15:26:33 +0530115 if (uncompressed_p010_image->width < kMinWidth
116 || uncompressed_p010_image->height < kMinHeight) {
117 ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d",
118 kMinWidth, kMinHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
Ram Mohanac1cfec2023-05-18 14:41:15 +0530119 return ERROR_JPEGR_INVALID_INPUT_TYPE;
120 }
121
Ram Mohand136b8a2023-06-02 09:06:40 +0530122 if (uncompressed_p010_image->width > kMaxWidth
123 || uncompressed_p010_image->height > kMaxHeight) {
124 ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d",
125 kMaxWidth, kMaxHeight, uncompressed_p010_image->width, uncompressed_p010_image->height);
126 return ERROR_JPEGR_INVALID_INPUT_TYPE;
127 }
128
Ram Mohanac1cfec2023-05-18 14:41:15 +0530129 if (uncompressed_p010_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
130 || uncompressed_p010_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
131 ALOGE("Unrecognized p010 color gamut %d", uncompressed_p010_image->colorGamut);
132 return ERROR_JPEGR_INVALID_INPUT_TYPE;
133 }
134
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700135 if (uncompressed_p010_image->luma_stride != 0
136 && uncompressed_p010_image->luma_stride < uncompressed_p010_image->width) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530137 ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d",
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700138 uncompressed_p010_image->luma_stride, uncompressed_p010_image->width);
139 return ERROR_JPEGR_INVALID_INPUT_TYPE;
140 }
141
Ram Mohanac1cfec2023-05-18 14:41:15 +0530142 if (uncompressed_p010_image->chroma_data != nullptr
143 && uncompressed_p010_image->chroma_stride < uncompressed_p010_image->width) {
144 ALOGE("Chroma stride can not be smaller than width, stride=%d, width=%d",
145 uncompressed_p010_image->chroma_stride,
146 uncompressed_p010_image->width);
147 return ERROR_JPEGR_INVALID_INPUT_TYPE;
148 }
149
150 if (dest == nullptr || dest->data == nullptr) {
151 ALOGE("received nullptr for destination");
152 return ERROR_JPEGR_INVALID_NULL_PTR;
153 }
154
Ram Mohan9b3d6852023-05-26 00:09:50 +0530155 if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX
156 || hdr_tf == ULTRAHDR_TF_SRGB) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530157 ALOGE("Invalid hdr transfer function %d", hdr_tf);
158 return ERROR_JPEGR_INVALID_INPUT_TYPE;
159 }
160
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700161 if (uncompressed_yuv_420_image == nullptr) {
162 return NO_ERROR;
163 }
164
Ram Mohanac1cfec2023-05-18 14:41:15 +0530165 if (uncompressed_yuv_420_image->data == nullptr) {
166 ALOGE("received nullptr for uncompressed 420 image");
167 return ERROR_JPEGR_INVALID_NULL_PTR;
168 }
169
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700170 if (uncompressed_yuv_420_image->luma_stride != 0) {
171 ALOGE("Stride is not supported for YUV420 image");
172 return ERROR_JPEGR_UNSUPPORTED_FEATURE;
173 }
174
175 if (uncompressed_yuv_420_image->chroma_data != nullptr) {
176 ALOGE("Pointer to chroma plane is not supported for YUV420 image, chroma data must"
177 "be immediately after the luma data.");
178 return ERROR_JPEGR_UNSUPPORTED_FEATURE;
179 }
180
181 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
182 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
183 ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
184 uncompressed_p010_image->width,
185 uncompressed_p010_image->height,
186 uncompressed_yuv_420_image->width,
187 uncompressed_yuv_420_image->height);
188 return ERROR_JPEGR_RESOLUTION_MISMATCH;
189 }
190
Ram Mohanac1cfec2023-05-18 14:41:15 +0530191 if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
192 || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
193 ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut);
194 return ERROR_JPEGR_INVALID_INPUT_TYPE;
195 }
196
197 return NO_ERROR;
198}
199
200status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
201 jr_uncompressed_ptr uncompressed_yuv_420_image,
202 ultrahdr_transfer_function hdr_tf,
203 jr_compressed_ptr dest,
204 int quality) {
205 if (status_t ret = areInputArgumentsValid(
206 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
207 return ret;
208 }
209
210 if (quality < 0 || quality > 100) {
211 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
212 return ERROR_JPEGR_INVALID_INPUT_TYPE;
213 }
214
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700215 return NO_ERROR;
216}
217
Dichen Zhang636f5242022-12-07 20:25:44 +0000218/* Encode API-0 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000219status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000220 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000221 jr_compressed_ptr dest,
222 int quality,
223 jr_exif_ptr exif) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530224 if (status_t ret = areInputArgumentsValid(
225 uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
226 hdr_tf, dest, quality) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700227 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800228 }
229
Ram Mohanac1cfec2023-05-18 14:41:15 +0530230 if (exif != nullptr && exif->data == nullptr) {
231 ALOGE("received nullptr for exif metadata");
232 return ERROR_JPEGR_INVALID_NULL_PTR;
233 }
234
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000235 ultrahdr_metadata_struct metadata;
Dichen Zhang636f5242022-12-07 20:25:44 +0000236 metadata.version = kJpegrVersion;
Dichen Zhang636f5242022-12-07 20:25:44 +0000237
238 jpegr_uncompressed_struct uncompressed_yuv_420_image;
Ram Mohan4a72c192023-05-22 19:05:06 +0530239 unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
240 uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800241 uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
Dichen Zhang636f5242022-12-07 20:25:44 +0000242 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
243
244 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700245 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500246 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Dichen Zhang636f5242022-12-07 20:25:44 +0000247 std::unique_ptr<uint8_t[]> map_data;
248 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
249
Ram Mohanf9540402023-05-18 20:08:55 +0530250 JpegEncoderHelper jpeg_encoder_gainmap;
251 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Dichen Zhang636f5242022-12-07 20:25:44 +0000252 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530253 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
254 compressed_map.length = compressed_map.maxLength;
255 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
256 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Dichen Zhang636f5242022-12-07 20:25:44 +0000257
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000258 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000259 uncompressed_yuv_420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000260
Nick Deakin0db53ee2023-05-19 17:14:45 -0400261 // Convert to Bt601 YUV encoding for JPEG encode
262 JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut,
263 ULTRAHDR_COLORGAMUT_P3));
264
Dichen Zhang02dd0592023-02-10 20:16:57 +0000265 JpegEncoderHelper jpeg_encoder;
Dichen Zhang636f5242022-12-07 20:25:44 +0000266 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
267 uncompressed_yuv_420_image.width,
Dichen Zhang6438a192023-01-29 07:51:15 +0000268 uncompressed_yuv_420_image.height, quality,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000269 icc->getData(), icc->getLength())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000270 return ERROR_JPEGR_ENCODE_ERROR;
271 }
272 jpegr_compressed_struct jpeg;
273 jpeg.data = jpeg_encoder.getCompressedImagePtr();
274 jpeg.length = jpeg_encoder.getCompressedImageSize();
275
Nick Deakin0db53ee2023-05-19 17:14:45 -0400276 // No ICC since JPEG encode already did it
277 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
278 &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000279
280 return NO_ERROR;
281}
282
283/* Encode API-1 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000284status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000285 jr_uncompressed_ptr uncompressed_yuv_420_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000286 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000287 jr_compressed_ptr dest,
288 int quality,
289 jr_exif_ptr exif) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530290 if (uncompressed_yuv_420_image == nullptr) {
291 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000292 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000293 }
294
Ram Mohanac1cfec2023-05-18 14:41:15 +0530295 if (exif != nullptr && exif->data == nullptr) {
296 ALOGE("received nullptr for exif metadata");
297 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhangffa34012022-11-03 23:21:13 +0000298 }
299
Ram Mohanac1cfec2023-05-18 14:41:15 +0530300 if (status_t ret = areInputArgumentsValid(
301 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf,
302 dest, quality) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700303 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800304 }
305
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000306 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500307 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500308
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400309 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700310 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500311 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400312 std::unique_ptr<uint8_t[]> map_data;
313 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
314
Ram Mohanf9540402023-05-18 20:08:55 +0530315 JpegEncoderHelper jpeg_encoder_gainmap;
316 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400317 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530318 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
319 compressed_map.length = compressed_map.maxLength;
320 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
321 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400322
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000323 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000324 uncompressed_yuv_420_image->colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000325
Nick Deakin0db53ee2023-05-19 17:14:45 -0400326 // Convert to Bt601 YUV encoding for JPEG encode; make a copy so as to no clobber client data
327 unique_ptr<uint8_t[]> yuv_420_bt601_data = make_unique<uint8_t[]>(
328 uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
329 memcpy(yuv_420_bt601_data.get(), uncompressed_yuv_420_image->data,
330 uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
331
332 jpegr_uncompressed_struct yuv_420_bt601_image = {
333 yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height,
334 uncompressed_yuv_420_image->colorGamut };
335 JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut,
336 ULTRAHDR_COLORGAMUT_P3));
337
Dichen Zhang02dd0592023-02-10 20:16:57 +0000338 JpegEncoderHelper jpeg_encoder;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400339 if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data,
340 yuv_420_bt601_image.width,
341 yuv_420_bt601_image.height, quality,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000342 icc->getData(), icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400343 return ERROR_JPEGR_ENCODE_ERROR;
344 }
345 jpegr_compressed_struct jpeg;
346 jpeg.data = jpeg_encoder.getCompressedImagePtr();
347 jpeg.length = jpeg_encoder.getCompressedImageSize();
348
Nick Deakin0db53ee2023-05-19 17:14:45 -0400349 // No ICC since jpeg encode already did it
350 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
351 &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400352
Dichen Zhang6947d532022-10-22 02:16:21 +0000353 return NO_ERROR;
354}
355
Dichen Zhang636f5242022-12-07 20:25:44 +0000356/* Encode API-2 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000357status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000358 jr_uncompressed_ptr uncompressed_yuv_420_image,
359 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000360 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000361 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530362 if (uncompressed_yuv_420_image == nullptr) {
363 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000364 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000365 }
366
Ram Mohanac1cfec2023-05-18 14:41:15 +0530367 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
368 ALOGE("received nullptr for compressed jpeg image");
369 return ERROR_JPEGR_INVALID_NULL_PTR;
370 }
371
372 if (status_t ret = areInputArgumentsValid(
373 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700374 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800375 }
376
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000377 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500378 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500379
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400380 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700381 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500382 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400383 std::unique_ptr<uint8_t[]> map_data;
384 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
385
Ram Mohanf9540402023-05-18 20:08:55 +0530386 JpegEncoderHelper jpeg_encoder_gainmap;
387 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400388 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530389 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
390 compressed_map.length = compressed_map.maxLength;
391 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
392 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400393
Nick Deakin0db53ee2023-05-19 17:14:45 -0400394 // We just want to check if ICC is present, so don't do a full decode. Note,
395 // this doesn't verify that the ICC is valid.
396 JpegDecoderHelper decoder;
397 std::vector<uint8_t> icc;
398 decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
399 /* pWidth */ nullptr, /* pHeight */ nullptr,
400 &icc, /* exifData */ nullptr);
401
402 // Add ICC if not already present.
403 if (icc.size() > 0) {
404 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
405 /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
406 } else {
407 sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
408 uncompressed_yuv_420_image->colorGamut);
409 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
410 newIcc->getData(), newIcc->getLength(), &metadata, dest));
411 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400412
Dichen Zhang6947d532022-10-22 02:16:21 +0000413 return NO_ERROR;
414}
415
Dichen Zhang636f5242022-12-07 20:25:44 +0000416/* Encode API-3 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000417status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000418 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000419 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000420 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530421 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
422 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000423 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000424 }
425
Ram Mohanac1cfec2023-05-18 14:41:15 +0530426 if (status_t ret = areInputArgumentsValid(
427 uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
428 hdr_tf, dest) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700429 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800430 }
431
Nick Deakin0db53ee2023-05-19 17:14:45 -0400432 // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode.
Dichen Zhang02dd0592023-02-10 20:16:57 +0000433 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400434 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
435 return ERROR_JPEGR_DECODE_ERROR;
436 }
437 jpegr_uncompressed_struct uncompressed_yuv_420_image;
438 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
439 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
440 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500441 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400442
443 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
444 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
445 return ERROR_JPEGR_RESOLUTION_MISMATCH;
446 }
447
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000448 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500449 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500450
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400451 jpegr_uncompressed_struct map;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400452 // Indicate that the SDR image is Bt.601 YUV encoded.
Dichen Zhang10959a42023-04-10 16:28:16 -0700453 JPEGR_CHECK(generateGainMap(
Nick Deakin0db53ee2023-05-19 17:14:45 -0400454 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map,
455 true /* sdr_is_601 */ ));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400456 std::unique_ptr<uint8_t[]> map_data;
457 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
458
Ram Mohanf9540402023-05-18 20:08:55 +0530459 JpegEncoderHelper jpeg_encoder_gainmap;
460 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400461 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530462 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
463 compressed_map.length = compressed_map.maxLength;
464 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
465 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400466
Nick Deakin0db53ee2023-05-19 17:14:45 -0400467 // We just want to check if ICC is present, so don't do a full decode. Note,
468 // this doesn't verify that the ICC is valid.
469 JpegDecoderHelper decoder;
470 std::vector<uint8_t> icc;
471 decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
472 /* pWidth */ nullptr, /* pHeight */ nullptr,
473 &icc, /* exifData */ nullptr);
474
475 // Add ICC if not already present.
476 if (icc.size() > 0) {
477 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
478 /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
479 } else {
480 sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
481 uncompressed_yuv_420_image.colorGamut);
482 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
483 newIcc->getData(), newIcc->getLength(), &metadata, dest));
484 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400485
Dichen Zhang6947d532022-10-22 02:16:21 +0000486 return NO_ERROR;
487}
488
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000489/* Encode API-4 */
490status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
491 jr_compressed_ptr compressed_gainmap,
492 ultrahdr_metadata_ptr metadata,
493 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530494 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
495 ALOGE("received nullptr for compressed jpeg image");
496 return ERROR_JPEGR_INVALID_NULL_PTR;
497 }
498
499 if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) {
500 ALOGE("received nullptr for compressed gain map");
501 return ERROR_JPEGR_INVALID_NULL_PTR;
502 }
503
504 if (dest == nullptr || dest->data == nullptr) {
505 ALOGE("received nullptr for destination");
506 return ERROR_JPEGR_INVALID_NULL_PTR;
507 }
508
Nick Deakin0db53ee2023-05-19 17:14:45 -0400509 // We just want to check if ICC is present, so don't do a full decode. Note,
510 // this doesn't verify that the ICC is valid.
511 JpegDecoderHelper decoder;
512 std::vector<uint8_t> icc;
513 decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
514 /* pWidth */ nullptr, /* pHeight */ nullptr,
515 &icc, /* exifData */ nullptr);
516
517 // Add ICC if not already present.
518 if (icc.size() > 0) {
519 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
520 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
521 } else {
522 sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
523 compressed_jpeg_image->colorGamut);
524 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
525 newIcc->getData(), newIcc->getLength(), metadata, dest));
526 }
527
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000528 return NO_ERROR;
529}
530
Dichen Zhang61ede362023-02-22 18:50:13 +0000531status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530532 if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
533 ALOGE("received nullptr for compressed jpegr image");
534 return ERROR_JPEGR_INVALID_NULL_PTR;
535 }
536
537 if (jpegr_info == nullptr) {
538 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000539 return ERROR_JPEGR_INVALID_NULL_PTR;
540 }
541
Dichen Zhang10959a42023-04-10 16:28:16 -0700542 jpegr_compressed_struct primary_image, gain_map;
543 JPEGR_CHECK(extractPrimaryImageAndGainMap(compressed_jpegr_image,
544 &primary_image, &gain_map));
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000545
Dichen Zhang02dd0592023-02-10 20:16:57 +0000546 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000547 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
548 &jpegr_info->width, &jpegr_info->height,
549 jpegr_info->iccData, jpegr_info->exifData)) {
550 return ERROR_JPEGR_DECODE_ERROR;
551 }
552
553 return NO_ERROR;
554}
555
Dichen Zhang636f5242022-12-07 20:25:44 +0000556/* Decode API */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000557status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000558 jr_uncompressed_ptr dest,
Dichen Zhangc6605702023-03-15 18:40:55 -0700559 float max_display_boost,
Dichen Zhang61ede362023-02-22 18:50:13 +0000560 jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000561 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700562 jr_uncompressed_ptr gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000563 ultrahdr_metadata_ptr metadata) {
Ram Mohanb0375052023-05-20 04:03:48 +0530564 if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
565 ALOGE("received nullptr for compressed jpegr image");
566 return ERROR_JPEGR_INVALID_NULL_PTR;
567 }
568
569 if (dest == nullptr || dest->data == nullptr) {
570 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000571 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000572 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000573
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000574 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530575 ALOGE("received bad value for max_display_boost %f", max_display_boost);
576 return ERROR_JPEGR_INVALID_INPUT_TYPE;
577 }
578
579 if (exif != nullptr && exif->data == nullptr) {
580 ALOGE("received nullptr address for exif data");
581 return ERROR_JPEGR_INVALID_INPUT_TYPE;
582 }
583
584 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
585 ALOGE("received bad value for output format %d", output_format);
586 return ERROR_JPEGR_INVALID_INPUT_TYPE;
587 }
588
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000589 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Dichen Zhang02dd0592023-02-10 20:16:57 +0000590 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000591 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length,
592 true)) {
593 return ERROR_JPEGR_DECODE_ERROR;
594 }
595 jpegr_uncompressed_struct uncompressed_rgba_image;
596 uncompressed_rgba_image.data = jpeg_decoder.getDecompressedImagePtr();
597 uncompressed_rgba_image.width = jpeg_decoder.getDecompressedImageWidth();
598 uncompressed_rgba_image.height = jpeg_decoder.getDecompressedImageHeight();
599 memcpy(dest->data, uncompressed_rgba_image.data,
600 uncompressed_rgba_image.width * uncompressed_rgba_image.height * 4);
601 dest->width = uncompressed_rgba_image.width;
602 dest->height = uncompressed_rgba_image.height;
Dichen Zhang14f3c472023-03-08 07:24:48 +0000603
Dichen Zhang10959a42023-04-10 16:28:16 -0700604 if (gain_map == nullptr && exif == nullptr) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000605 return NO_ERROR;
606 }
607
608 if (exif != nullptr) {
609 if (exif->data == nullptr) {
610 return ERROR_JPEGR_INVALID_NULL_PTR;
611 }
612 if (exif->length < jpeg_decoder.getEXIFSize()) {
613 return ERROR_JPEGR_BUFFER_TOO_SMALL;
614 }
615 memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
616 exif->length = jpeg_decoder.getEXIFSize();
617 }
Dichen Zhang10959a42023-04-10 16:28:16 -0700618 if (gain_map == nullptr) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000619 return NO_ERROR;
620 }
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000621 }
622
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400623 jpegr_compressed_struct compressed_map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700624 JPEGR_CHECK(extractGainMap(compressed_jpegr_image, &compressed_map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400625
Dichen Zhang10959a42023-04-10 16:28:16 -0700626 JpegDecoderHelper gain_map_decoder;
627 if (!gain_map_decoder.decompressImage(compressed_map.data, compressed_map.length)) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000628 return ERROR_JPEGR_DECODE_ERROR;
629 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530630 if ((gain_map_decoder.getDecompressedImageWidth() *
631 gain_map_decoder.getDecompressedImageHeight()) >
632 gain_map_decoder.getDecompressedImageSize()) {
633 return ERROR_JPEGR_CALCULATION_ERROR;
634 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000635
Dichen Zhang10959a42023-04-10 16:28:16 -0700636 if (gain_map != nullptr) {
637 gain_map->width = gain_map_decoder.getDecompressedImageWidth();
638 gain_map->height = gain_map_decoder.getDecompressedImageHeight();
639 int size = gain_map->width * gain_map->height;
640 gain_map->data = malloc(size);
641 memcpy(gain_map->data, gain_map_decoder.getDecompressedImagePtr(), size);
Dichen Zhang14f3c472023-03-08 07:24:48 +0000642 }
643
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000644 ultrahdr_metadata_struct uhdr_metadata;
Dichen Zhang10959a42023-04-10 16:28:16 -0700645 if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000646 gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
Nick Deakin094946b2023-06-09 11:58:41 -0400647 return ERROR_JPEGR_INVALID_METADATA;
Dichen Zhange286f1c2023-03-14 00:22:00 +0000648 }
649
650 if (metadata != nullptr) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000651 metadata->version = uhdr_metadata.version;
652 metadata->minContentBoost = uhdr_metadata.minContentBoost;
653 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
Nick Deakin094946b2023-06-09 11:58:41 -0400654 metadata->gamma = uhdr_metadata.gamma;
655 metadata->offsetSdr = uhdr_metadata.offsetSdr;
656 metadata->offsetHdr = uhdr_metadata.offsetHdr;
657 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
658 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
Dichen Zhange286f1c2023-03-14 00:22:00 +0000659 }
660
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000661 if (output_format == ULTRAHDR_OUTPUT_SDR) {
Dichen Zhang14f3c472023-03-08 07:24:48 +0000662 return NO_ERROR;
663 }
664
Dichen Zhang02dd0592023-02-10 20:16:57 +0000665 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400666 if (!jpeg_decoder.decompressImage(compressed_jpegr_image->data, compressed_jpegr_image->length)) {
667 return ERROR_JPEGR_DECODE_ERROR;
668 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530669 if ((jpeg_decoder.getDecompressedImageWidth() *
670 jpeg_decoder.getDecompressedImageHeight() * 3 / 2) >
671 jpeg_decoder.getDecompressedImageSize()) {
672 return ERROR_JPEGR_CALCULATION_ERROR;
673 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400674
Dichen Zhangb80e2262023-03-08 06:59:51 +0000675 if (exif != nullptr) {
676 if (exif->data == nullptr) {
677 return ERROR_JPEGR_INVALID_NULL_PTR;
678 }
679 if (exif->length < jpeg_decoder.getEXIFSize()) {
680 return ERROR_JPEGR_BUFFER_TOO_SMALL;
681 }
682 memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
683 exif->length = jpeg_decoder.getEXIFSize();
684 }
685
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000686 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700687 map.data = gain_map_decoder.getDecompressedImagePtr();
688 map.width = gain_map_decoder.getDecompressedImageWidth();
689 map.height = gain_map_decoder.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000690
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400691 jpegr_uncompressed_struct uncompressed_yuv_420_image;
692 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
693 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
694 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin0db53ee2023-05-19 17:14:45 -0400695 uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut(
696 jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize());
697
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000698 JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700699 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000700 return NO_ERROR;
701}
702
Dichen Zhang10959a42023-04-10 16:28:16 -0700703status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
Ram Mohanf9540402023-05-18 20:08:55 +0530704 JpegEncoderHelper* jpeg_encoder) {
705 if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000706 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700707 }
708
Nick Deakin0db53ee2023-05-19 17:14:45 -0400709 // Don't need to convert YUV to Bt601 since single channel
Ram Mohanf9540402023-05-18 20:08:55 +0530710 if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
711 uncompressed_gain_map->width,
712 uncompressed_gain_map->height,
713 kMapCompressQuality,
714 nullptr,
715 0,
716 true /* isSingleChannel */)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400717 return ERROR_JPEGR_ENCODE_ERROR;
718 }
719
Dichen Zhang6947d532022-10-22 02:16:21 +0000720 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700721}
722
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800723const int kJobSzInRows = 16;
724static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
725 "align job size to kMapDimensionScaleFactor");
726
727class JobQueue {
728 public:
729 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
730 void enqueueJob(size_t rowStart, size_t rowEnd);
731 void markQueueForEnd();
732 void reset();
733
734 private:
735 bool mQueuedAllJobs = false;
736 std::deque<std::tuple<size_t, size_t>> mJobs;
737 std::mutex mMutex;
738 std::condition_variable mCv;
739};
740
741bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
742 std::unique_lock<std::mutex> lock{mMutex};
743 while (true) {
744 if (mJobs.empty()) {
745 if (mQueuedAllJobs) {
746 return false;
747 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000748 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800749 }
750 } else {
751 auto it = mJobs.begin();
752 rowStart = std::get<0>(*it);
753 rowEnd = std::get<1>(*it);
754 mJobs.erase(it);
755 return true;
756 }
757 }
758 return false;
759}
760
761void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
762 std::unique_lock<std::mutex> lock{mMutex};
763 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
764 lock.unlock();
765 mCv.notify_one();
766}
767
768void JobQueue::markQueueForEnd() {
769 std::unique_lock<std::mutex> lock{mMutex};
770 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000771 lock.unlock();
772 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800773}
774
775void JobQueue::reset() {
776 std::unique_lock<std::mutex> lock{mMutex};
777 mJobs.clear();
778 mQueuedAllJobs = false;
779}
780
Dichen Zhang10959a42023-04-10 16:28:16 -0700781status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
782 jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000783 ultrahdr_transfer_function hdr_tf,
784 ultrahdr_metadata_ptr metadata,
Nick Deakin0db53ee2023-05-19 17:14:45 -0400785 jr_uncompressed_ptr dest,
786 bool sdr_is_601) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700787 if (uncompressed_yuv_420_image == nullptr
788 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500789 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700790 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000791 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700792 }
793
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400794 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
795 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
796 return ERROR_JPEGR_RESOLUTION_MISMATCH;
797 }
798
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000799 if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
800 || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500801 return ERROR_JPEGR_INVALID_COLORGAMUT;
802 }
803
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400804 size_t image_width = uncompressed_yuv_420_image->width;
805 size_t image_height = uncompressed_yuv_420_image->height;
806 size_t map_width = image_width / kMapDimensionScaleFactor;
807 size_t map_height = image_height / kMapDimensionScaleFactor;
Dichen Zhang53751272023-01-17 19:09:01 -0800808 size_t map_stride = static_cast<size_t>(
809 floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
810 size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400811
Dichen Zhang53751272023-01-17 19:09:01 -0800812 dest->width = map_stride;
813 dest->height = map_height_aligned;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000814 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Dichen Zhang53751272023-01-17 19:09:01 -0800815 dest->data = new uint8_t[map_stride * map_height_aligned];
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400816 std::unique_ptr<uint8_t[]> map_data;
817 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
818
Nick Deakin6bd90432022-11-20 16:26:37 -0500819 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohancd3f6372023-06-02 15:16:15 +0530820 float hdr_white_nits = kSdrWhiteNits;
Nick Deakin01759062023-02-02 18:21:43 -0500821 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000822 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000823 hdrInvOetf = identityConversion;
824 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000825 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800826#if USE_HLG_INVOETF_LUT
827 hdrInvOetf = hlgInvOetfLUT;
828#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500829 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800830#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500831 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500832 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000833 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800834#if USE_PQ_INVOETF_LUT
835 hdrInvOetf = pqInvOetfLUT;
836#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500837 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800838#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500839 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500840 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000841 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000842 // Should be impossible to hit after input validation.
843 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500844 }
845
Nick Deakina2215292023-02-14 21:40:06 -0500846 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
847 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400848 metadata->gamma = 1.0f;
849 metadata->offsetSdr = 0.0f;
850 metadata->offsetHdr = 0.0f;
851 metadata->hdrCapacityMin = 1.0f;
852 metadata->hdrCapacityMax = metadata->maxContentBoost;
853
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700854 float log2MinBoost = log2(metadata->minContentBoost);
855 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500856
Nick Deakin6bd90432022-11-20 16:26:37 -0500857 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
858 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
859
860 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400861 ColorTransformFn sdrYuvToRgbFn = nullptr;
Nick Deakin6bd90432022-11-20 16:26:37 -0500862 switch (uncompressed_yuv_420_image->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000863 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500864 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400865 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500866 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000867 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500868 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400869 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500870 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000871 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500872 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400873 sdrYuvToRgbFn = bt2100YuvToRgb;
874 break;
875 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
876 // Should be impossible to hit after input validation.
877 return ERROR_JPEGR_INVALID_COLORGAMUT;
878 }
879 if (sdr_is_601) {
880 sdrYuvToRgbFn = p3YuvToRgb;
881 }
882
883 ColorTransformFn hdrYuvToRgbFn = nullptr;
884 switch (uncompressed_p010_image->colorGamut) {
885 case ULTRAHDR_COLORGAMUT_BT709:
886 hdrYuvToRgbFn = srgbYuvToRgb;
887 break;
888 case ULTRAHDR_COLORGAMUT_P3:
889 hdrYuvToRgbFn = p3YuvToRgb;
890 break;
891 case ULTRAHDR_COLORGAMUT_BT2100:
892 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500893 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000894 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500895 // Should be impossible to hit after input validation.
896 return ERROR_JPEGR_INVALID_COLORGAMUT;
897 }
898
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800899 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800900 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
901 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
902 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500903
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800904 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
905 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
Nick Deakin0db53ee2023-05-19 17:14:45 -0400906 luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
907 log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800908 size_t rowStart, rowEnd;
Dichen Zhang24b4a392023-02-02 22:54:01 +0000909 size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
910 size_t dest_map_stride = dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800911 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
912 for (size_t y = rowStart; y < rowEnd; ++y) {
Dichen Zhang24b4a392023-02-02 22:54:01 +0000913 for (size_t x = 0; x < dest_map_width; ++x) {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800914 Color sdr_yuv_gamma =
915 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400916 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
917 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800918#if USE_SRGB_INVOETF_LUT
919 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
920#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800921 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800922#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800923 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
924
925 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400926 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800927 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
928 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
929 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
930
Dichen Zhang24b4a392023-02-02 22:54:01 +0000931 size_t pixel_idx = x + y * dest_map_stride;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800932 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Dichen Zhang10959a42023-04-10 16:28:16 -0700933 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800934 }
935 }
936 }
937 };
938
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800939 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500940 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800941 for (int th = 0; th < threads - 1; th++) {
942 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400943 }
944
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800945 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
946 for (size_t rowStart = 0; rowStart < map_height;) {
947 size_t rowEnd = std::min(rowStart + rowStep, map_height);
948 jobQueue.enqueueJob(rowStart, rowEnd);
949 rowStart = rowEnd;
950 }
951 jobQueue.markQueueForEnd();
952 generateMap();
953 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
954
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400955 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000956 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700957}
958
Dichen Zhang10959a42023-04-10 16:28:16 -0700959status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
960 jr_uncompressed_ptr uncompressed_gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000961 ultrahdr_metadata_ptr metadata,
962 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700963 float max_display_boost,
964 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700965 if (uncompressed_yuv_420_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -0700966 || uncompressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500967 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700968 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000969 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700970 }
971
Nick Deakin094946b2023-06-09 11:58:41 -0400972 if (metadata->version.compare("1.0")) {
973 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
974 return ERROR_JPEGR_UNSUPPORTED_METADATA;
975 }
976 if (metadata->gamma != 1.0f) {
977 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
978 return ERROR_JPEGR_UNSUPPORTED_METADATA;
979 }
980 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
981 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr,
982 metadata->offsetHdr);
983 return ERROR_JPEGR_UNSUPPORTED_METADATA;
984 }
985 if (metadata->hdrCapacityMin != metadata->minContentBoost
986 || metadata->hdrCapacityMax != metadata->maxContentBoost) {
987 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
988 metadata->hdrCapacityMax);
989 return ERROR_JPEGR_UNSUPPORTED_METADATA;
990 }
991
Ram Mohan9b3d6852023-05-26 00:09:50 +0530992 // TODO: remove once map scaling factor is computed based on actual map dims
993 size_t image_width = uncompressed_yuv_420_image->width;
994 size_t image_height = uncompressed_yuv_420_image->height;
995 size_t map_width = image_width / kMapDimensionScaleFactor;
996 size_t map_height = image_height / kMapDimensionScaleFactor;
997 map_width = static_cast<size_t>(
998 floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
999 map_height = ((map_height + 1) >> 1) << 1;
1000 if (map_width != uncompressed_gain_map->width
1001 || map_height != uncompressed_gain_map->height) {
1002 ALOGE("gain map dimensions and primary image dimensions are not to scale");
1003 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1004 }
1005
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001006 dest->width = uncompressed_yuv_420_image->width;
1007 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +05301008 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001009 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001010 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301011
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001012 JobQueue jobQueue;
Dichen Zhang10959a42023-04-10 16:28:16 -07001013 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001014 metadata, dest, &jobQueue, &idwTable, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -07001015 &gainLUT, display_boost]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001016 size_t width = uncompressed_yuv_420_image->width;
1017 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001018
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001019 size_t rowStart, rowEnd;
1020 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1021 for (size_t y = rowStart; y < rowEnd; ++y) {
1022 for (size_t x = 0; x < width; ++x) {
1023 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001024 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1025 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1026 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001027#if USE_SRGB_INVOETF_LUT
1028 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1029#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001030 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001031#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001032 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001033 // TODO: determine map scaling factor based on actual map dims
1034 size_t map_scale_factor = kMapDimensionScaleFactor;
1035 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1036 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1037 // later.
1038 if (map_scale_factor != floorf(map_scale_factor)) {
Dichen Zhang10959a42023-04-10 16:28:16 -07001039 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001040 } else {
Dichen Zhang10959a42023-04-10 16:28:16 -07001041 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001042 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001043
Dichen Zhang10959a42023-04-10 16:28:16 -07001044#if USE_APPLY_GAIN_LUT
1045 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001046#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001047 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001048#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001049 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001050 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001051
1052 switch (output_format) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001053 case ULTRAHDR_OUTPUT_HDR_LINEAR:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001054 {
1055 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1056 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1057 break;
1058 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001059 case ULTRAHDR_OUTPUT_HDR_HLG:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001060 {
1061#if USE_HLG_OETF_LUT
1062 ColorTransformFn hdrOetf = hlgOetfLUT;
1063#else
1064 ColorTransformFn hdrOetf = hlgOetf;
1065#endif
1066 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1067 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1068 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1069 break;
1070 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001071 case ULTRAHDR_OUTPUT_HDR_PQ:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001072 {
1073#if USE_HLG_OETF_LUT
1074 ColorTransformFn hdrOetf = pqOetfLUT;
1075#else
1076 ColorTransformFn hdrOetf = pqOetf;
1077#endif
1078 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1079 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1080 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1081 break;
1082 }
1083 default:
1084 {}
1085 // Should be impossible to hit after input validation.
1086 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001087 }
1088 }
1089 }
1090 };
1091
1092 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1093 std::vector<std::thread> workers;
1094 for (int th = 0; th < threads - 1; th++) {
1095 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001096 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001097 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
1098 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
1099 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
1100 jobQueue.enqueueJob(rowStart, rowEnd);
1101 rowStart = rowEnd;
1102 }
1103 jobQueue.markQueueForEnd();
1104 applyRecMap();
1105 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001106 return NO_ERROR;
1107}
1108
Dichen Zhang10959a42023-04-10 16:28:16 -07001109status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
1110 jr_compressed_ptr primary_image,
1111 jr_compressed_ptr gain_map) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001112 if (compressed_jpegr_image == nullptr) {
1113 return ERROR_JPEGR_INVALID_NULL_PTR;
1114 }
1115
1116 MessageHandler msg_handler;
1117 std::shared_ptr<DataSegment> seg =
1118 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
1119 static_cast<const uint8_t*>(compressed_jpegr_image->data),
1120 DataSegment::BufferDispositionPolicy::kDontDelete);
1121 DataSegmentDataSource data_source(seg);
1122 JpegInfoBuilder jpeg_info_builder;
1123 jpeg_info_builder.SetImageLimit(2);
1124 JpegScanner jpeg_scanner(&msg_handler);
1125 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1126 data_source.Reset();
1127
1128 if (jpeg_scanner.HasError()) {
1129 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1130 }
1131
1132 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1133 const auto& image_ranges = jpeg_info.GetImageRanges();
1134 if (image_ranges.empty()) {
1135 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1136 }
1137
1138 if (image_ranges.size() != 2) {
1139 // Must be 2 JPEG Images
1140 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1141 }
1142
1143 if (primary_image != nullptr) {
1144 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
1145 image_ranges[0].GetBegin();
1146 primary_image->length = image_ranges[0].GetLength();
1147 }
1148
Dichen Zhang10959a42023-04-10 16:28:16 -07001149 if (gain_map != nullptr) {
1150 gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001151 image_ranges[1].GetBegin();
Dichen Zhang10959a42023-04-10 16:28:16 -07001152 gain_map->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001153 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001154
Dichen Zhang6947d532022-10-22 02:16:21 +00001155 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -07001156}
1157
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001158
Dichen Zhang10959a42023-04-10 16:28:16 -07001159status_t JpegR::extractGainMap(jr_compressed_ptr compressed_jpegr_image,
1160 jr_compressed_ptr dest) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001161 if (compressed_jpegr_image == nullptr || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001162 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001163 }
1164
Dichen Zhang10959a42023-04-10 16:28:16 -07001165 return extractPrimaryImageAndGainMap(compressed_jpegr_image, nullptr, dest);
Dichen Zhang85b37562022-10-11 11:08:28 -07001166}
1167
Dichen Zhangd18bc302022-12-16 20:55:24 +00001168// JPEG/R structure:
1169// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001170//
1171// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001172// APP1 (ff e1)
1173// 2 bytes of length (2 + length of exif package)
1174// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001175//
1176// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001177// 2 bytes of length (2 + 29 + length of xmp package)
1178// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001179// XMP
1180//
1181// (Required, MPF package) APP2 (ff e2)
1182// 2 bytes of length
1183// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001184//
1185// (Required) primary image (without the first two bytes (SOI), may have other packages)
1186//
Dichen Zhang61ede362023-02-22 18:50:13 +00001187// SOI (ff d8)
1188//
1189// (Required, XMP package) APP1 (ff e1)
1190// 2 bytes of length (2 + 29 + length of xmp package)
1191// name space ("http://ns.adobe.com/xap/1.0/\0")
1192// XMP
1193//
Dichen Zhang10959a42023-04-10 16:28:16 -07001194// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001195//
1196// Metadata versions we are using:
1197// ECMA TR-98 for JFIF marker
1198// Exif 2.2 spec for EXIF marker
1199// Adobe XMP spec part 3 for XMP marker
1200// ICC v4.3 spec for ICC
Dichen Zhang10959a42023-04-10 16:28:16 -07001201status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
1202 jr_compressed_ptr compressed_gain_map,
1203 jr_exif_ptr exif,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001204 void* icc, size_t icc_size,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001205 ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001206 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +00001207 if (compressed_jpeg_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -07001208 || compressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -05001209 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +00001210 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001211 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001212 }
1213
Nick Deakin094946b2023-06-09 11:58:41 -04001214 if (metadata->version.compare("1.0")) {
1215 ALOGE("received bad value for version: %s", metadata->version.c_str());
1216 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1217 }
1218 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301219 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
1220 metadata->maxContentBoost);
1221 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1222 }
1223
Nick Deakin094946b2023-06-09 11:58:41 -04001224 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1225 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
1226 metadata->hdrCapacityMax);
1227 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1228 }
1229
1230 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
1231 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr,
1232 metadata->offsetHdr);
1233 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1234 }
1235
1236 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/";
1242 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 */
1248 + nameSpaceLength /* 29 bytes length of name space including \0 */
1249 + xmp_secondary.size(); /* length of xmp packet */
1250 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
1251 + xmp_secondary_length
Dichen Zhang10959a42023-04-10 16:28:16 -07001252 + compressed_gain_map->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001253 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001254 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001255 // same as primary
1256 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001257
Dichen Zhang15345ea2023-02-23 23:54:32 +00001258 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001259 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001260 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001261 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1262 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001263
1264 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001265 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001266 const int length = 2 + exif->length;
1267 const uint8_t lengthH = ((length >> 8) & 0xff);
1268 const uint8_t lengthL = (length & 0xff);
1269 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1270 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1271 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1272 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1273 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1274 }
1275
1276 // Prepare and write XMP
1277 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001278 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001279 const uint8_t lengthH = ((length >> 8) & 0xff);
1280 const uint8_t lengthL = (length & 0xff);
1281 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1282 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1283 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1284 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1285 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001286 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1287 }
1288
Nick Deakin0db53ee2023-05-19 17:14:45 -04001289 // Write ICC
1290 if (icc != nullptr && icc_size > 0) {
1291 const int length = icc_size + 2;
1292 const uint8_t lengthH = ((length >> 8) & 0xff);
1293 const uint8_t lengthL = (length & 0xff);
1294 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1295 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1296 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1297 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1298 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
1299 }
1300
Dichen Zhang61ede362023-02-22 18:50:13 +00001301 // Prepare and write MPF
1302 {
1303 const int length = 2 + calculateMpfSize();
1304 const uint8_t lengthH = ((length >> 8) & 0xff);
1305 const uint8_t lengthL = (length & 0xff);
1306 int primary_image_size = pos + length + compressed_jpeg_image->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001307 // between APP2 + package size + signature
1308 // ff e2 00 58 4d 50 46 00
1309 // 2 + 2 + 4 = 8 (bytes)
1310 // and ff d8 sign of the secondary image
1311 int secondary_image_offset = primary_image_size - pos - 8;
1312 sp<DataStruct> mpf = generateMpf(primary_image_size,
1313 0, /* primary_image_offset */
1314 secondary_image_size,
1315 secondary_image_offset);
Dichen Zhang61ede362023-02-22 18:50:13 +00001316 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1317 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1318 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1319 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1320 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001321 }
1322
1323 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +00001324 JPEGR_CHECK(Write(dest,
1325 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001326 // Finish primary image
1327
Dichen Zhang10959a42023-04-10 16:28:16 -07001328 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001329 // Write SOI
1330 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1331 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1332
1333 // Prepare and write XMP
1334 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001335 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001336 const uint8_t lengthH = ((length >> 8) & 0xff);
1337 const uint8_t lengthL = (length & 0xff);
1338 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1339 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1340 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1341 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1342 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1343 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1344 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001345
1346 // Write secondary image
Dichen Zhang61ede362023-02-22 18:50:13 +00001347 JPEGR_CHECK(Write(dest,
Dichen Zhang10959a42023-04-10 16:28:16 -07001348 (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001349
1350 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001351 dest->length = pos;
1352
Dichen Zhangd18bc302022-12-16 20:55:24 +00001353 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001354 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001355}
1356
Dichen Zhang61ede362023-02-22 18:50:13 +00001357status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001358 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001359 return ERROR_JPEGR_INVALID_NULL_PTR;
1360 }
1361
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001362 uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
Dichen Zhang98b06852023-05-17 16:56:51 +00001363 size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001364
Dichen Zhang98b06852023-05-17 16:56:51 +00001365 uint16_t* src_chroma_data;
1366 size_t src_chroma_stride;
1367 if (src->chroma_data == nullptr) {
1368 src_chroma_stride = src_luma_stride;
1369 src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
1370 } else {
1371 src_chroma_stride = src->chroma_stride;
1372 src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001373 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001374 dest->width = src->width;
1375 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +00001376
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001377 size_t dest_luma_pixel_count = dest->width * dest->height;
1378
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001379 for (size_t y = 0; y < src->height; ++y) {
1380 for (size_t x = 0; x < src->width; ++x) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001381 size_t src_y_idx = y * src_luma_stride + x;
1382 size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
1383 size_t src_v_idx = src_u_idx + 1;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001384
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001385 uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
1386 uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
1387 uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001388
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001389 size_t dest_y_idx = x + y * dest->width;
1390 size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
1391
1392 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
1393 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
1394 uint8_t* v = &reinterpret_cast<uint8_t*>(
1395 dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001396
1397 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1398 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1399 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1400 }
1401 }
1402
1403 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001404
1405 return NO_ERROR;
1406}
1407
Nick Deakin0db53ee2023-05-19 17:14:45 -04001408status_t JpegR::convertYuv(jr_uncompressed_ptr image,
1409 ultrahdr_color_gamut src_encoding,
1410 ultrahdr_color_gamut dest_encoding) {
1411 if (image == nullptr) {
1412 return ERROR_JPEGR_INVALID_NULL_PTR;
1413 }
1414
1415 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED
1416 || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
1417 return ERROR_JPEGR_INVALID_COLORGAMUT;
1418 }
1419
1420 ColorTransformFn conversionFn = nullptr;
1421 switch (src_encoding) {
1422 case ULTRAHDR_COLORGAMUT_BT709:
1423 switch (dest_encoding) {
1424 case ULTRAHDR_COLORGAMUT_BT709:
1425 return NO_ERROR;
1426 case ULTRAHDR_COLORGAMUT_P3:
1427 conversionFn = yuv709To601;
1428 break;
1429 case ULTRAHDR_COLORGAMUT_BT2100:
1430 conversionFn = yuv709To2100;
1431 break;
1432 default:
1433 // Should be impossible to hit after input validation
1434 return ERROR_JPEGR_INVALID_COLORGAMUT;
1435 }
1436 break;
1437 case ULTRAHDR_COLORGAMUT_P3:
1438 switch (dest_encoding) {
1439 case ULTRAHDR_COLORGAMUT_BT709:
1440 conversionFn = yuv601To709;
1441 break;
1442 case ULTRAHDR_COLORGAMUT_P3:
1443 return NO_ERROR;
1444 case ULTRAHDR_COLORGAMUT_BT2100:
1445 conversionFn = yuv601To2100;
1446 break;
1447 default:
1448 // Should be impossible to hit after input validation
1449 return ERROR_JPEGR_INVALID_COLORGAMUT;
1450 }
1451 break;
1452 case ULTRAHDR_COLORGAMUT_BT2100:
1453 switch (dest_encoding) {
1454 case ULTRAHDR_COLORGAMUT_BT709:
1455 conversionFn = yuv2100To709;
1456 break;
1457 case ULTRAHDR_COLORGAMUT_P3:
1458 conversionFn = yuv2100To601;
1459 break;
1460 case ULTRAHDR_COLORGAMUT_BT2100:
1461 return NO_ERROR;
1462 default:
1463 // Should be impossible to hit after input validation
1464 return ERROR_JPEGR_INVALID_COLORGAMUT;
1465 }
1466 break;
1467 default:
1468 // Should be impossible to hit after input validation
1469 return ERROR_JPEGR_INVALID_COLORGAMUT;
1470 }
1471
1472 if (conversionFn == nullptr) {
1473 // Should be impossible to hit after input validation
1474 return ERROR_JPEGR_INVALID_COLORGAMUT;
1475 }
1476
1477 for (size_t y = 0; y < image->height / 2; ++y) {
1478 for (size_t x = 0; x < image->width / 2; ++x) {
1479 transformYuv420(image, x, y, conversionFn);
1480 }
1481 }
1482
1483 return NO_ERROR;
1484}
1485
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001486} // namespace android::ultrahdr