blob: 5a601bd6b9d45519108c1b936d60d1472d9c6f6a [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
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530542 jpegr_compressed_struct primary_image, gainmap_image;
543 status_t status =
544 extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
545 if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
546 return status;
547 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000548
Dichen Zhang02dd0592023-02-10 20:16:57 +0000549 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000550 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
551 &jpegr_info->width, &jpegr_info->height,
552 jpegr_info->iccData, jpegr_info->exifData)) {
553 return ERROR_JPEGR_DECODE_ERROR;
554 }
555
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530556 return status;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000557}
558
Dichen Zhang636f5242022-12-07 20:25:44 +0000559/* Decode API */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000560status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000561 jr_uncompressed_ptr dest,
Dichen Zhangc6605702023-03-15 18:40:55 -0700562 float max_display_boost,
Dichen Zhang61ede362023-02-22 18:50:13 +0000563 jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000564 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700565 jr_uncompressed_ptr gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000566 ultrahdr_metadata_ptr metadata) {
Ram Mohanb0375052023-05-20 04:03:48 +0530567 if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
568 ALOGE("received nullptr for compressed jpegr image");
569 return ERROR_JPEGR_INVALID_NULL_PTR;
570 }
571
572 if (dest == nullptr || dest->data == nullptr) {
573 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000574 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000575 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000576
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000577 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530578 ALOGE("received bad value for max_display_boost %f", max_display_boost);
579 return ERROR_JPEGR_INVALID_INPUT_TYPE;
580 }
581
582 if (exif != nullptr && exif->data == nullptr) {
583 ALOGE("received nullptr address for exif data");
584 return ERROR_JPEGR_INVALID_INPUT_TYPE;
585 }
586
587 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
588 ALOGE("received bad value for output format %d", output_format);
589 return ERROR_JPEGR_INVALID_INPUT_TYPE;
590 }
591
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530592 jpegr_compressed_struct primary_image, gainmap_image;
593 status_t status =
594 extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
595 if (status != NO_ERROR) {
596 if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
597 ALOGE("received invalid compressed jpegr image");
598 return status;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000599 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000600 }
601
Dichen Zhang02dd0592023-02-10 20:16:57 +0000602 JpegDecoderHelper jpeg_decoder;
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530603 if (!jpeg_decoder.decompressImage(primary_image.data, primary_image.length,
604 (output_format == ULTRAHDR_OUTPUT_SDR))) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400605 return ERROR_JPEGR_DECODE_ERROR;
606 }
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530607
608 if (output_format == ULTRAHDR_OUTPUT_SDR) {
609 if ((jpeg_decoder.getDecompressedImageWidth() *
610 jpeg_decoder.getDecompressedImageHeight() * 4) >
611 jpeg_decoder.getDecompressedImageSize()) {
612 return ERROR_JPEGR_CALCULATION_ERROR;
613 }
614 } else {
615 if ((jpeg_decoder.getDecompressedImageWidth() *
616 jpeg_decoder.getDecompressedImageHeight() * 3 / 2) >
617 jpeg_decoder.getDecompressedImageSize()) {
618 return ERROR_JPEGR_CALCULATION_ERROR;
619 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530620 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400621
Dichen Zhangb80e2262023-03-08 06:59:51 +0000622 if (exif != nullptr) {
623 if (exif->data == nullptr) {
624 return ERROR_JPEGR_INVALID_NULL_PTR;
625 }
626 if (exif->length < jpeg_decoder.getEXIFSize()) {
627 return ERROR_JPEGR_BUFFER_TOO_SMALL;
628 }
629 memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
630 exif->length = jpeg_decoder.getEXIFSize();
631 }
632
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530633 if (output_format == ULTRAHDR_OUTPUT_SDR) {
634 dest->width = jpeg_decoder.getDecompressedImageWidth();
635 dest->height = jpeg_decoder.getDecompressedImageHeight();
636 memcpy(dest->data, jpeg_decoder.getDecompressedImagePtr(), dest->width * dest->height * 4);
637 return NO_ERROR;
638 }
639
640 JpegDecoderHelper gain_map_decoder;
641 if (!gain_map_decoder.decompressImage(gainmap_image.data, gainmap_image.length)) {
642 return ERROR_JPEGR_DECODE_ERROR;
643 }
644 if ((gain_map_decoder.getDecompressedImageWidth() *
645 gain_map_decoder.getDecompressedImageHeight()) >
646 gain_map_decoder.getDecompressedImageSize()) {
647 return ERROR_JPEGR_CALCULATION_ERROR;
648 }
649
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000650 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700651 map.data = gain_map_decoder.getDecompressedImagePtr();
652 map.width = gain_map_decoder.getDecompressedImageWidth();
653 map.height = gain_map_decoder.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000654
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530655 if (gain_map != nullptr) {
656 gain_map->width = map.width;
657 gain_map->height = map.height;
658 int size = gain_map->width * gain_map->height;
659 gain_map->data = malloc(size);
660 memcpy(gain_map->data, map.data, size);
661 }
662
663 ultrahdr_metadata_struct uhdr_metadata;
664 if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
665 gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
666 return ERROR_JPEGR_INVALID_METADATA;
667 }
668
669 if (metadata != nullptr) {
670 metadata->version = uhdr_metadata.version;
671 metadata->minContentBoost = uhdr_metadata.minContentBoost;
672 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
673 metadata->gamma = uhdr_metadata.gamma;
674 metadata->offsetSdr = uhdr_metadata.offsetSdr;
675 metadata->offsetHdr = uhdr_metadata.offsetHdr;
676 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
677 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
678 }
679
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400680 jpegr_uncompressed_struct uncompressed_yuv_420_image;
681 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
682 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
683 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin0db53ee2023-05-19 17:14:45 -0400684 uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut(
685 jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize());
686
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000687 JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700688 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000689 return NO_ERROR;
690}
691
Dichen Zhang10959a42023-04-10 16:28:16 -0700692status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
Ram Mohanf9540402023-05-18 20:08:55 +0530693 JpegEncoderHelper* jpeg_encoder) {
694 if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000695 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700696 }
697
Nick Deakin0db53ee2023-05-19 17:14:45 -0400698 // Don't need to convert YUV to Bt601 since single channel
Ram Mohanf9540402023-05-18 20:08:55 +0530699 if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
700 uncompressed_gain_map->width,
701 uncompressed_gain_map->height,
702 kMapCompressQuality,
703 nullptr,
704 0,
705 true /* isSingleChannel */)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400706 return ERROR_JPEGR_ENCODE_ERROR;
707 }
708
Dichen Zhang6947d532022-10-22 02:16:21 +0000709 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700710}
711
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800712const int kJobSzInRows = 16;
713static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
714 "align job size to kMapDimensionScaleFactor");
715
716class JobQueue {
717 public:
718 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
719 void enqueueJob(size_t rowStart, size_t rowEnd);
720 void markQueueForEnd();
721 void reset();
722
723 private:
724 bool mQueuedAllJobs = false;
725 std::deque<std::tuple<size_t, size_t>> mJobs;
726 std::mutex mMutex;
727 std::condition_variable mCv;
728};
729
730bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
731 std::unique_lock<std::mutex> lock{mMutex};
732 while (true) {
733 if (mJobs.empty()) {
734 if (mQueuedAllJobs) {
735 return false;
736 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000737 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800738 }
739 } else {
740 auto it = mJobs.begin();
741 rowStart = std::get<0>(*it);
742 rowEnd = std::get<1>(*it);
743 mJobs.erase(it);
744 return true;
745 }
746 }
747 return false;
748}
749
750void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
751 std::unique_lock<std::mutex> lock{mMutex};
752 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
753 lock.unlock();
754 mCv.notify_one();
755}
756
757void JobQueue::markQueueForEnd() {
758 std::unique_lock<std::mutex> lock{mMutex};
759 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000760 lock.unlock();
761 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800762}
763
764void JobQueue::reset() {
765 std::unique_lock<std::mutex> lock{mMutex};
766 mJobs.clear();
767 mQueuedAllJobs = false;
768}
769
Dichen Zhang10959a42023-04-10 16:28:16 -0700770status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
771 jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000772 ultrahdr_transfer_function hdr_tf,
773 ultrahdr_metadata_ptr metadata,
Nick Deakin0db53ee2023-05-19 17:14:45 -0400774 jr_uncompressed_ptr dest,
775 bool sdr_is_601) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700776 if (uncompressed_yuv_420_image == nullptr
777 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500778 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700779 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000780 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700781 }
782
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400783 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
784 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
785 return ERROR_JPEGR_RESOLUTION_MISMATCH;
786 }
787
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000788 if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
789 || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500790 return ERROR_JPEGR_INVALID_COLORGAMUT;
791 }
792
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400793 size_t image_width = uncompressed_yuv_420_image->width;
794 size_t image_height = uncompressed_yuv_420_image->height;
795 size_t map_width = image_width / kMapDimensionScaleFactor;
796 size_t map_height = image_height / kMapDimensionScaleFactor;
Dichen Zhang53751272023-01-17 19:09:01 -0800797 size_t map_stride = static_cast<size_t>(
798 floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
799 size_t map_height_aligned = ((map_height + 1) >> 1) << 1;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400800
Dichen Zhang53751272023-01-17 19:09:01 -0800801 dest->width = map_stride;
802 dest->height = map_height_aligned;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000803 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Dichen Zhang53751272023-01-17 19:09:01 -0800804 dest->data = new uint8_t[map_stride * map_height_aligned];
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400805 std::unique_ptr<uint8_t[]> map_data;
806 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
807
Nick Deakin6bd90432022-11-20 16:26:37 -0500808 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohancd3f6372023-06-02 15:16:15 +0530809 float hdr_white_nits = kSdrWhiteNits;
Nick Deakin01759062023-02-02 18:21:43 -0500810 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000811 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000812 hdrInvOetf = identityConversion;
813 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000814 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800815#if USE_HLG_INVOETF_LUT
816 hdrInvOetf = hlgInvOetfLUT;
817#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500818 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800819#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500820 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500821 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000822 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800823#if USE_PQ_INVOETF_LUT
824 hdrInvOetf = pqInvOetfLUT;
825#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500826 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800827#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500828 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500829 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000830 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000831 // Should be impossible to hit after input validation.
832 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500833 }
834
Nick Deakina2215292023-02-14 21:40:06 -0500835 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
836 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400837 metadata->gamma = 1.0f;
838 metadata->offsetSdr = 0.0f;
839 metadata->offsetHdr = 0.0f;
840 metadata->hdrCapacityMin = 1.0f;
841 metadata->hdrCapacityMax = metadata->maxContentBoost;
842
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700843 float log2MinBoost = log2(metadata->minContentBoost);
844 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500845
Nick Deakin6bd90432022-11-20 16:26:37 -0500846 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
847 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
848
849 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400850 ColorTransformFn sdrYuvToRgbFn = nullptr;
Nick Deakin6bd90432022-11-20 16:26:37 -0500851 switch (uncompressed_yuv_420_image->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000852 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500853 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400854 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500855 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000856 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500857 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400858 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500859 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000860 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500861 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400862 sdrYuvToRgbFn = bt2100YuvToRgb;
863 break;
864 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
865 // Should be impossible to hit after input validation.
866 return ERROR_JPEGR_INVALID_COLORGAMUT;
867 }
868 if (sdr_is_601) {
869 sdrYuvToRgbFn = p3YuvToRgb;
870 }
871
872 ColorTransformFn hdrYuvToRgbFn = nullptr;
873 switch (uncompressed_p010_image->colorGamut) {
874 case ULTRAHDR_COLORGAMUT_BT709:
875 hdrYuvToRgbFn = srgbYuvToRgb;
876 break;
877 case ULTRAHDR_COLORGAMUT_P3:
878 hdrYuvToRgbFn = p3YuvToRgb;
879 break;
880 case ULTRAHDR_COLORGAMUT_BT2100:
881 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500882 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000883 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500884 // Should be impossible to hit after input validation.
885 return ERROR_JPEGR_INVALID_COLORGAMUT;
886 }
887
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800888 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800889 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
890 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
891 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500892
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800893 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
894 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
Nick Deakin0db53ee2023-05-19 17:14:45 -0400895 luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
896 log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800897 size_t rowStart, rowEnd;
Dichen Zhang24b4a392023-02-02 22:54:01 +0000898 size_t dest_map_width = uncompressed_yuv_420_image->width / kMapDimensionScaleFactor;
899 size_t dest_map_stride = dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800900 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
901 for (size_t y = rowStart; y < rowEnd; ++y) {
Dichen Zhang24b4a392023-02-02 22:54:01 +0000902 for (size_t x = 0; x < dest_map_width; ++x) {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800903 Color sdr_yuv_gamma =
904 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400905 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
906 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800907#if USE_SRGB_INVOETF_LUT
908 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
909#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800910 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800911#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800912 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
913
914 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400915 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800916 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
917 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
918 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
919
Dichen Zhang24b4a392023-02-02 22:54:01 +0000920 size_t pixel_idx = x + y * dest_map_stride;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800921 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Dichen Zhang10959a42023-04-10 16:28:16 -0700922 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800923 }
924 }
925 }
926 };
927
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800928 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500929 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800930 for (int th = 0; th < threads - 1; th++) {
931 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400932 }
933
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800934 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
935 for (size_t rowStart = 0; rowStart < map_height;) {
936 size_t rowEnd = std::min(rowStart + rowStep, map_height);
937 jobQueue.enqueueJob(rowStart, rowEnd);
938 rowStart = rowEnd;
939 }
940 jobQueue.markQueueForEnd();
941 generateMap();
942 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
943
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400944 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000945 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700946}
947
Dichen Zhang10959a42023-04-10 16:28:16 -0700948status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
949 jr_uncompressed_ptr uncompressed_gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000950 ultrahdr_metadata_ptr metadata,
951 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700952 float max_display_boost,
953 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700954 if (uncompressed_yuv_420_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -0700955 || uncompressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500956 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700957 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000958 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700959 }
960
Nick Deakin094946b2023-06-09 11:58:41 -0400961 if (metadata->version.compare("1.0")) {
962 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
963 return ERROR_JPEGR_UNSUPPORTED_METADATA;
964 }
965 if (metadata->gamma != 1.0f) {
966 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
967 return ERROR_JPEGR_UNSUPPORTED_METADATA;
968 }
969 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
970 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr,
971 metadata->offsetHdr);
972 return ERROR_JPEGR_UNSUPPORTED_METADATA;
973 }
974 if (metadata->hdrCapacityMin != metadata->minContentBoost
975 || metadata->hdrCapacityMax != metadata->maxContentBoost) {
976 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
977 metadata->hdrCapacityMax);
978 return ERROR_JPEGR_UNSUPPORTED_METADATA;
979 }
980
Ram Mohan9b3d6852023-05-26 00:09:50 +0530981 // TODO: remove once map scaling factor is computed based on actual map dims
982 size_t image_width = uncompressed_yuv_420_image->width;
983 size_t image_height = uncompressed_yuv_420_image->height;
984 size_t map_width = image_width / kMapDimensionScaleFactor;
985 size_t map_height = image_height / kMapDimensionScaleFactor;
986 map_width = static_cast<size_t>(
987 floor((map_width + kJpegBlock - 1) / kJpegBlock)) * kJpegBlock;
988 map_height = ((map_height + 1) >> 1) << 1;
989 if (map_width != uncompressed_gain_map->width
990 || map_height != uncompressed_gain_map->height) {
991 ALOGE("gain map dimensions and primary image dimensions are not to scale");
992 return ERROR_JPEGR_INVALID_INPUT_TYPE;
993 }
994
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800995 dest->width = uncompressed_yuv_420_image->width;
996 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +0530997 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000998 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -0700999 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301000
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001001 JobQueue jobQueue;
Dichen Zhang10959a42023-04-10 16:28:16 -07001002 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001003 metadata, dest, &jobQueue, &idwTable, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -07001004 &gainLUT, display_boost]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001005 size_t width = uncompressed_yuv_420_image->width;
1006 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001007
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001008 size_t rowStart, rowEnd;
1009 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1010 for (size_t y = rowStart; y < rowEnd; ++y) {
1011 for (size_t x = 0; x < width; ++x) {
1012 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001013 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1014 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1015 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001016#if USE_SRGB_INVOETF_LUT
1017 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1018#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001019 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001020#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001021 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001022 // TODO: determine map scaling factor based on actual map dims
1023 size_t map_scale_factor = kMapDimensionScaleFactor;
1024 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1025 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1026 // later.
1027 if (map_scale_factor != floorf(map_scale_factor)) {
Dichen Zhang10959a42023-04-10 16:28:16 -07001028 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001029 } else {
Dichen Zhang10959a42023-04-10 16:28:16 -07001030 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001031 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001032
Dichen Zhang10959a42023-04-10 16:28:16 -07001033#if USE_APPLY_GAIN_LUT
1034 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001035#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001036 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001037#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001038 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001039 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001040
1041 switch (output_format) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001042 case ULTRAHDR_OUTPUT_HDR_LINEAR:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001043 {
1044 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1045 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1046 break;
1047 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001048 case ULTRAHDR_OUTPUT_HDR_HLG:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001049 {
1050#if USE_HLG_OETF_LUT
1051 ColorTransformFn hdrOetf = hlgOetfLUT;
1052#else
1053 ColorTransformFn hdrOetf = hlgOetf;
1054#endif
1055 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1056 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1057 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1058 break;
1059 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001060 case ULTRAHDR_OUTPUT_HDR_PQ:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001061 {
Ram Mohan67862992023-06-22 15:56:05 +05301062#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001063 ColorTransformFn hdrOetf = pqOetfLUT;
1064#else
1065 ColorTransformFn hdrOetf = pqOetf;
1066#endif
1067 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1068 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1069 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1070 break;
1071 }
1072 default:
1073 {}
1074 // Should be impossible to hit after input validation.
1075 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001076 }
1077 }
1078 }
1079 };
1080
1081 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1082 std::vector<std::thread> workers;
1083 for (int th = 0; th < threads - 1; th++) {
1084 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001085 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001086 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
1087 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
1088 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
1089 jobQueue.enqueueJob(rowStart, rowEnd);
1090 rowStart = rowEnd;
1091 }
1092 jobQueue.markQueueForEnd();
1093 applyRecMap();
1094 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001095 return NO_ERROR;
1096}
1097
Dichen Zhang10959a42023-04-10 16:28:16 -07001098status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
1099 jr_compressed_ptr primary_image,
1100 jr_compressed_ptr gain_map) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001101 if (compressed_jpegr_image == nullptr) {
1102 return ERROR_JPEGR_INVALID_NULL_PTR;
1103 }
1104
1105 MessageHandler msg_handler;
1106 std::shared_ptr<DataSegment> seg =
1107 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
1108 static_cast<const uint8_t*>(compressed_jpegr_image->data),
1109 DataSegment::BufferDispositionPolicy::kDontDelete);
1110 DataSegmentDataSource data_source(seg);
1111 JpegInfoBuilder jpeg_info_builder;
1112 jpeg_info_builder.SetImageLimit(2);
1113 JpegScanner jpeg_scanner(&msg_handler);
1114 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1115 data_source.Reset();
1116
1117 if (jpeg_scanner.HasError()) {
1118 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1119 }
1120
1121 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1122 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001123
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301124 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001125 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1126 }
1127
1128 if (primary_image != nullptr) {
1129 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
1130 image_ranges[0].GetBegin();
1131 primary_image->length = image_ranges[0].GetLength();
1132 }
1133
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301134 if (image_ranges.size() == 1) {
1135 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1136 }
1137
Dichen Zhang10959a42023-04-10 16:28:16 -07001138 if (gain_map != nullptr) {
1139 gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001140 image_ranges[1].GetBegin();
Dichen Zhang10959a42023-04-10 16:28:16 -07001141 gain_map->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001142 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001143
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301144 // TODO: choose primary image and gain map image carefully
1145 if (image_ranges.size() > 2) {
1146 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1147 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001148 }
1149
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301150 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001151}
1152
Dichen Zhangd18bc302022-12-16 20:55:24 +00001153// JPEG/R structure:
1154// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001155//
1156// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001157// APP1 (ff e1)
1158// 2 bytes of length (2 + length of exif package)
1159// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001160//
1161// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001162// 2 bytes of length (2 + 29 + length of xmp package)
1163// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001164// XMP
1165//
1166// (Required, MPF package) APP2 (ff e2)
1167// 2 bytes of length
1168// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001169//
1170// (Required) primary image (without the first two bytes (SOI), may have other packages)
1171//
Dichen Zhang61ede362023-02-22 18:50:13 +00001172// SOI (ff d8)
1173//
1174// (Required, XMP package) APP1 (ff e1)
1175// 2 bytes of length (2 + 29 + length of xmp package)
1176// name space ("http://ns.adobe.com/xap/1.0/\0")
1177// XMP
1178//
Dichen Zhang10959a42023-04-10 16:28:16 -07001179// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001180//
1181// Metadata versions we are using:
1182// ECMA TR-98 for JFIF marker
1183// Exif 2.2 spec for EXIF marker
1184// Adobe XMP spec part 3 for XMP marker
1185// ICC v4.3 spec for ICC
Dichen Zhang10959a42023-04-10 16:28:16 -07001186status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
1187 jr_compressed_ptr compressed_gain_map,
1188 jr_exif_ptr exif,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001189 void* icc, size_t icc_size,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001190 ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001191 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +00001192 if (compressed_jpeg_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -07001193 || compressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -05001194 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +00001195 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001196 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001197 }
1198
Nick Deakin094946b2023-06-09 11:58:41 -04001199 if (metadata->version.compare("1.0")) {
1200 ALOGE("received bad value for version: %s", metadata->version.c_str());
1201 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1202 }
1203 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301204 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
1205 metadata->maxContentBoost);
1206 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1207 }
1208
Nick Deakin094946b2023-06-09 11:58:41 -04001209 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1210 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
1211 metadata->hdrCapacityMax);
1212 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1213 }
1214
1215 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
1216 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr,
1217 metadata->offsetHdr);
1218 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1219 }
1220
1221 if (metadata->gamma <= 0.0f) {
1222 ALOGE("received bad value for gamma %f", metadata->gamma);
1223 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1224 }
1225
Dichen Zhang15345ea2023-02-23 23:54:32 +00001226 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
1227 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001228
Dichen Zhang15345ea2023-02-23 23:54:32 +00001229 // calculate secondary image length first, because the length will be written into the primary
1230 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001231 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001232 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
1233 + nameSpaceLength /* 29 bytes length of name space including \0 */
1234 + xmp_secondary.size(); /* length of xmp packet */
1235 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
1236 + xmp_secondary_length
Dichen Zhang10959a42023-04-10 16:28:16 -07001237 + compressed_gain_map->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001238 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001239 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001240 // same as primary
1241 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001242
Dichen Zhang15345ea2023-02-23 23:54:32 +00001243 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001244 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001245 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001246 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1247 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001248
1249 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001250 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001251 const int length = 2 + exif->length;
1252 const uint8_t lengthH = ((length >> 8) & 0xff);
1253 const uint8_t lengthL = (length & 0xff);
1254 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1255 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1256 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1257 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1258 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1259 }
1260
1261 // Prepare and write XMP
1262 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001263 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001264 const uint8_t lengthH = ((length >> 8) & 0xff);
1265 const uint8_t lengthL = (length & 0xff);
1266 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1267 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1268 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1269 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1270 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001271 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1272 }
1273
Nick Deakin0db53ee2023-05-19 17:14:45 -04001274 // Write ICC
1275 if (icc != nullptr && icc_size > 0) {
1276 const int length = icc_size + 2;
1277 const uint8_t lengthH = ((length >> 8) & 0xff);
1278 const uint8_t lengthL = (length & 0xff);
1279 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1280 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1281 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1282 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1283 JPEGR_CHECK(Write(dest, icc, icc_size, pos));
1284 }
1285
Dichen Zhang61ede362023-02-22 18:50:13 +00001286 // Prepare and write MPF
1287 {
1288 const int length = 2 + calculateMpfSize();
1289 const uint8_t lengthH = ((length >> 8) & 0xff);
1290 const uint8_t lengthL = (length & 0xff);
1291 int primary_image_size = pos + length + compressed_jpeg_image->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001292 // between APP2 + package size + signature
1293 // ff e2 00 58 4d 50 46 00
1294 // 2 + 2 + 4 = 8 (bytes)
1295 // and ff d8 sign of the secondary image
1296 int secondary_image_offset = primary_image_size - pos - 8;
1297 sp<DataStruct> mpf = generateMpf(primary_image_size,
1298 0, /* primary_image_offset */
1299 secondary_image_size,
1300 secondary_image_offset);
Dichen Zhang61ede362023-02-22 18:50:13 +00001301 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1302 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1303 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1304 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1305 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001306 }
1307
1308 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +00001309 JPEGR_CHECK(Write(dest,
1310 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001311 // Finish primary image
1312
Dichen Zhang10959a42023-04-10 16:28:16 -07001313 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001314 // Write SOI
1315 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1316 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1317
1318 // Prepare and write XMP
1319 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001320 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001321 const uint8_t lengthH = ((length >> 8) & 0xff);
1322 const uint8_t lengthL = (length & 0xff);
1323 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1324 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1325 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1326 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1327 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1328 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1329 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001330
1331 // Write secondary image
Dichen Zhang61ede362023-02-22 18:50:13 +00001332 JPEGR_CHECK(Write(dest,
Dichen Zhang10959a42023-04-10 16:28:16 -07001333 (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001334
1335 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001336 dest->length = pos;
1337
Dichen Zhangd18bc302022-12-16 20:55:24 +00001338 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001339 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001340}
1341
Dichen Zhang61ede362023-02-22 18:50:13 +00001342status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001343 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001344 return ERROR_JPEGR_INVALID_NULL_PTR;
1345 }
1346
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001347 uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
Dichen Zhang98b06852023-05-17 16:56:51 +00001348 size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001349
Dichen Zhang98b06852023-05-17 16:56:51 +00001350 uint16_t* src_chroma_data;
1351 size_t src_chroma_stride;
1352 if (src->chroma_data == nullptr) {
1353 src_chroma_stride = src_luma_stride;
1354 src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
1355 } else {
1356 src_chroma_stride = src->chroma_stride;
1357 src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001358 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001359 dest->width = src->width;
1360 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +00001361
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001362 size_t dest_luma_pixel_count = dest->width * dest->height;
1363
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001364 for (size_t y = 0; y < src->height; ++y) {
1365 for (size_t x = 0; x < src->width; ++x) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001366 size_t src_y_idx = y * src_luma_stride + x;
1367 size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
1368 size_t src_v_idx = src_u_idx + 1;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001369
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001370 uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
1371 uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
1372 uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001373
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001374 size_t dest_y_idx = x + y * dest->width;
1375 size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
1376
1377 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
1378 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
1379 uint8_t* v = &reinterpret_cast<uint8_t*>(
1380 dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001381
1382 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1383 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1384 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1385 }
1386 }
1387
1388 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001389
1390 return NO_ERROR;
1391}
1392
Nick Deakin0db53ee2023-05-19 17:14:45 -04001393status_t JpegR::convertYuv(jr_uncompressed_ptr image,
1394 ultrahdr_color_gamut src_encoding,
1395 ultrahdr_color_gamut dest_encoding) {
1396 if (image == nullptr) {
1397 return ERROR_JPEGR_INVALID_NULL_PTR;
1398 }
1399
1400 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED
1401 || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
1402 return ERROR_JPEGR_INVALID_COLORGAMUT;
1403 }
1404
1405 ColorTransformFn conversionFn = nullptr;
1406 switch (src_encoding) {
1407 case ULTRAHDR_COLORGAMUT_BT709:
1408 switch (dest_encoding) {
1409 case ULTRAHDR_COLORGAMUT_BT709:
1410 return NO_ERROR;
1411 case ULTRAHDR_COLORGAMUT_P3:
1412 conversionFn = yuv709To601;
1413 break;
1414 case ULTRAHDR_COLORGAMUT_BT2100:
1415 conversionFn = yuv709To2100;
1416 break;
1417 default:
1418 // Should be impossible to hit after input validation
1419 return ERROR_JPEGR_INVALID_COLORGAMUT;
1420 }
1421 break;
1422 case ULTRAHDR_COLORGAMUT_P3:
1423 switch (dest_encoding) {
1424 case ULTRAHDR_COLORGAMUT_BT709:
1425 conversionFn = yuv601To709;
1426 break;
1427 case ULTRAHDR_COLORGAMUT_P3:
1428 return NO_ERROR;
1429 case ULTRAHDR_COLORGAMUT_BT2100:
1430 conversionFn = yuv601To2100;
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_BT2100:
1438 switch (dest_encoding) {
1439 case ULTRAHDR_COLORGAMUT_BT709:
1440 conversionFn = yuv2100To709;
1441 break;
1442 case ULTRAHDR_COLORGAMUT_P3:
1443 conversionFn = yuv2100To601;
1444 break;
1445 case ULTRAHDR_COLORGAMUT_BT2100:
1446 return NO_ERROR;
1447 default:
1448 // Should be impossible to hit after input validation
1449 return ERROR_JPEGR_INVALID_COLORGAMUT;
1450 }
1451 break;
1452 default:
1453 // Should be impossible to hit after input validation
1454 return ERROR_JPEGR_INVALID_COLORGAMUT;
1455 }
1456
1457 if (conversionFn == nullptr) {
1458 // Should be impossible to hit after input validation
1459 return ERROR_JPEGR_INVALID_COLORGAMUT;
1460 }
1461
1462 for (size_t y = 0; y < image->height / 2; ++y) {
1463 for (size_t x = 0; x < image->width / 2; ++x) {
1464 transformYuv420(image, x, y, conversionFn);
1465 }
1466 }
1467
1468 return NO_ERROR;
1469}
1470
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001471} // namespace android::ultrahdr