blob: 0191fea9e87d35ae09ce6cf8f2759fa7aadaa454 [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
Ram Mohan43c3a802023-07-24 18:33:49 +0530170 if (uncompressed_yuv_420_image->luma_stride != 0
171 && uncompressed_yuv_420_image->luma_stride < uncompressed_yuv_420_image->width) {
172 ALOGE("Luma stride can not be smaller than width, stride=%d, width=%d",
173 uncompressed_yuv_420_image->luma_stride, uncompressed_yuv_420_image->width);
174 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700175 }
176
Ram Mohan43c3a802023-07-24 18:33:49 +0530177 if (uncompressed_yuv_420_image->chroma_data != nullptr
178 && uncompressed_yuv_420_image->chroma_stride < uncompressed_yuv_420_image->width / 2) {
179 ALOGE("Chroma stride can not be smaller than 1/2 of the width, stride=%d, width=%d",
180 uncompressed_yuv_420_image->chroma_stride, uncompressed_yuv_420_image->width);
181 return ERROR_JPEGR_INVALID_INPUT_TYPE;
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700182 }
183
184 if (uncompressed_p010_image->width != uncompressed_yuv_420_image->width
185 || uncompressed_p010_image->height != uncompressed_yuv_420_image->height) {
186 ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d",
187 uncompressed_p010_image->width,
188 uncompressed_p010_image->height,
189 uncompressed_yuv_420_image->width,
190 uncompressed_yuv_420_image->height);
191 return ERROR_JPEGR_RESOLUTION_MISMATCH;
192 }
193
Ram Mohanac1cfec2023-05-18 14:41:15 +0530194 if (uncompressed_yuv_420_image->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED
195 || uncompressed_yuv_420_image->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
196 ALOGE("Unrecognized 420 color gamut %d", uncompressed_yuv_420_image->colorGamut);
197 return ERROR_JPEGR_INVALID_INPUT_TYPE;
198 }
199
200 return NO_ERROR;
201}
202
203status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr uncompressed_p010_image,
204 jr_uncompressed_ptr uncompressed_yuv_420_image,
205 ultrahdr_transfer_function hdr_tf,
206 jr_compressed_ptr dest,
207 int quality) {
208 if (status_t ret = areInputArgumentsValid(
209 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
210 return ret;
211 }
212
213 if (quality < 0 || quality > 100) {
214 ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
215 return ERROR_JPEGR_INVALID_INPUT_TYPE;
216 }
217
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700218 return NO_ERROR;
219}
220
Dichen Zhang636f5242022-12-07 20:25:44 +0000221/* Encode API-0 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000222status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000223 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000224 jr_compressed_ptr dest,
225 int quality,
226 jr_exif_ptr exif) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530227 if (status_t ret = areInputArgumentsValid(
228 uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
229 hdr_tf, dest, quality) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700230 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800231 }
232
Ram Mohanac1cfec2023-05-18 14:41:15 +0530233 if (exif != nullptr && exif->data == nullptr) {
234 ALOGE("received nullptr for exif metadata");
235 return ERROR_JPEGR_INVALID_NULL_PTR;
236 }
237
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000238 ultrahdr_metadata_struct metadata;
Dichen Zhang636f5242022-12-07 20:25:44 +0000239 metadata.version = kJpegrVersion;
Dichen Zhang636f5242022-12-07 20:25:44 +0000240
241 jpegr_uncompressed_struct uncompressed_yuv_420_image;
Ram Mohan4a72c192023-05-22 19:05:06 +0530242 unique_ptr<uint8_t[]> uncompressed_yuv_420_image_data = make_unique<uint8_t[]>(
243 uncompressed_p010_image->width * uncompressed_p010_image->height * 3 / 2);
Dichen Zhangc3437ca2023-01-04 14:00:08 -0800244 uncompressed_yuv_420_image.data = uncompressed_yuv_420_image_data.get();
Dichen Zhang636f5242022-12-07 20:25:44 +0000245 JPEGR_CHECK(toneMap(uncompressed_p010_image, &uncompressed_yuv_420_image));
246
247 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700248 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500249 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Dichen Zhang636f5242022-12-07 20:25:44 +0000250 std::unique_ptr<uint8_t[]> map_data;
251 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
252
Ram Mohanf9540402023-05-18 20:08:55 +0530253 JpegEncoderHelper jpeg_encoder_gainmap;
254 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Dichen Zhang636f5242022-12-07 20:25:44 +0000255 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530256 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
257 compressed_map.length = compressed_map.maxLength;
258 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
259 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Dichen Zhang636f5242022-12-07 20:25:44 +0000260
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000261 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000262 uncompressed_yuv_420_image.colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000263
Nick Deakin0db53ee2023-05-19 17:14:45 -0400264 // Convert to Bt601 YUV encoding for JPEG encode
265 JPEGR_CHECK(convertYuv(&uncompressed_yuv_420_image, uncompressed_yuv_420_image.colorGamut,
266 ULTRAHDR_COLORGAMUT_P3));
267
Dichen Zhang02dd0592023-02-10 20:16:57 +0000268 JpegEncoderHelper jpeg_encoder;
Dichen Zhang636f5242022-12-07 20:25:44 +0000269 if (!jpeg_encoder.compressImage(uncompressed_yuv_420_image.data,
270 uncompressed_yuv_420_image.width,
Dichen Zhang6438a192023-01-29 07:51:15 +0000271 uncompressed_yuv_420_image.height, quality,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000272 icc->getData(), icc->getLength())) {
Dichen Zhang636f5242022-12-07 20:25:44 +0000273 return ERROR_JPEGR_ENCODE_ERROR;
274 }
275 jpegr_compressed_struct jpeg;
276 jpeg.data = jpeg_encoder.getCompressedImagePtr();
277 jpeg.length = jpeg_encoder.getCompressedImageSize();
278
Nick Deakin0db53ee2023-05-19 17:14:45 -0400279 // No ICC since JPEG encode already did it
280 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
281 &metadata, dest));
Dichen Zhang636f5242022-12-07 20:25:44 +0000282
283 return NO_ERROR;
284}
285
286/* Encode API-1 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000287status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000288 jr_uncompressed_ptr uncompressed_yuv_420_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000289 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000290 jr_compressed_ptr dest,
291 int quality,
292 jr_exif_ptr exif) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530293 if (uncompressed_yuv_420_image == nullptr) {
294 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000295 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000296 }
297
Ram Mohanac1cfec2023-05-18 14:41:15 +0530298 if (exif != nullptr && exif->data == nullptr) {
299 ALOGE("received nullptr for exif metadata");
300 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhangffa34012022-11-03 23:21:13 +0000301 }
302
Ram Mohanac1cfec2023-05-18 14:41:15 +0530303 if (status_t ret = areInputArgumentsValid(
304 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf,
305 dest, quality) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700306 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800307 }
308
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000309 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500310 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500311
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400312 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700313 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500314 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400315 std::unique_ptr<uint8_t[]> map_data;
316 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
317
Ram Mohanf9540402023-05-18 20:08:55 +0530318 JpegEncoderHelper jpeg_encoder_gainmap;
319 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400320 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530321 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
322 compressed_map.length = compressed_map.maxLength;
323 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
324 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400325
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000326 sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000327 uncompressed_yuv_420_image->colorGamut);
Dichen Zhang6438a192023-01-29 07:51:15 +0000328
Ram Mohan43c3a802023-07-24 18:33:49 +0530329 // Convert to Bt601 YUV encoding for JPEG encode and remove stride if needed;
330 // make a copy so as to no clobber client data
Nick Deakin0db53ee2023-05-19 17:14:45 -0400331 unique_ptr<uint8_t[]> yuv_420_bt601_data = make_unique<uint8_t[]>(
332 uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 3 / 2);
Ram Mohan43c3a802023-07-24 18:33:49 +0530333 // copy data
334 {
335 uint8_t* src_luma_data = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data);
336 size_t src_luma_stride = uncompressed_yuv_420_image->luma_stride == 0
337 ? uncompressed_yuv_420_image->width : uncompressed_yuv_420_image->luma_stride;
338 uint8_t* src_chroma_data = reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->chroma_data);
339 size_t src_chroma_stride = uncompressed_yuv_420_image->chroma_stride;
340 if (uncompressed_yuv_420_image->chroma_data == nullptr) {
341 src_chroma_data =
342 &reinterpret_cast<uint8_t*>(uncompressed_yuv_420_image->data)[src_luma_stride
343 * uncompressed_yuv_420_image->height];
344 }
345 if (src_chroma_stride == 0) {
346 src_chroma_stride = src_luma_stride / 2;
347 }
348 // copy luma
349 for (size_t i = 0; i < uncompressed_yuv_420_image->height; i++) {
350 memcpy(yuv_420_bt601_data.get() + i * uncompressed_yuv_420_image->width,
351 src_luma_data + i * src_luma_stride,
352 uncompressed_yuv_420_image->width);
353 }
354 // copy cb
355 for (size_t i = 0; i < uncompressed_yuv_420_image->height / 2; i++) {
356 memcpy(yuv_420_bt601_data.get()
357 + uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height
358 + i * uncompressed_yuv_420_image->width / 2,
359 src_chroma_data + i * src_chroma_stride,
360 uncompressed_yuv_420_image->width / 2);
361 }
362 // copy cr
363 for (size_t i = 0; i < uncompressed_yuv_420_image->height / 2; i++) {
364 memcpy(yuv_420_bt601_data.get()
365 + uncompressed_yuv_420_image->width * uncompressed_yuv_420_image->height * 5 / 4
366 + i * uncompressed_yuv_420_image->width / 2,
367 src_chroma_data + src_chroma_stride * (uncompressed_yuv_420_image->height / 2)
368 + i * src_chroma_stride,
369 uncompressed_yuv_420_image->width / 2);
370 }
371 }
Nick Deakin0db53ee2023-05-19 17:14:45 -0400372
373 jpegr_uncompressed_struct yuv_420_bt601_image = {
374 yuv_420_bt601_data.get(), uncompressed_yuv_420_image->width, uncompressed_yuv_420_image->height,
375 uncompressed_yuv_420_image->colorGamut };
376 JPEGR_CHECK(convertYuv(&yuv_420_bt601_image, yuv_420_bt601_image.colorGamut,
377 ULTRAHDR_COLORGAMUT_P3));
378
Dichen Zhang02dd0592023-02-10 20:16:57 +0000379 JpegEncoderHelper jpeg_encoder;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400380 if (!jpeg_encoder.compressImage(yuv_420_bt601_image.data,
381 yuv_420_bt601_image.width,
382 yuv_420_bt601_image.height, quality,
Dichen Zhange46f9bb2023-02-23 19:34:53 +0000383 icc->getData(), icc->getLength())) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400384 return ERROR_JPEGR_ENCODE_ERROR;
385 }
386 jpegr_compressed_struct jpeg;
387 jpeg.data = jpeg_encoder.getCompressedImagePtr();
388 jpeg.length = jpeg_encoder.getCompressedImageSize();
389
Nick Deakin0db53ee2023-05-19 17:14:45 -0400390 // No ICC since jpeg encode already did it
391 JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
392 &metadata, dest));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400393
Dichen Zhang6947d532022-10-22 02:16:21 +0000394 return NO_ERROR;
395}
396
Dichen Zhang636f5242022-12-07 20:25:44 +0000397/* Encode API-2 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000398status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000399 jr_uncompressed_ptr uncompressed_yuv_420_image,
400 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000401 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000402 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530403 if (uncompressed_yuv_420_image == nullptr) {
404 ALOGE("received nullptr for uncompressed 420 image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000405 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000406 }
407
Ram Mohanac1cfec2023-05-18 14:41:15 +0530408 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
409 ALOGE("received nullptr for compressed jpeg image");
410 return ERROR_JPEGR_INVALID_NULL_PTR;
411 }
412
413 if (status_t ret = areInputArgumentsValid(
414 uncompressed_p010_image, uncompressed_yuv_420_image, hdr_tf, dest) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700415 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800416 }
417
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000418 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500419 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500420
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400421 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700422 JPEGR_CHECK(generateGainMap(
Nick Deakin01759062023-02-02 18:21:43 -0500423 uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400424 std::unique_ptr<uint8_t[]> map_data;
425 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
426
Ram Mohanf9540402023-05-18 20:08:55 +0530427 JpegEncoderHelper jpeg_encoder_gainmap;
428 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400429 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530430 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
431 compressed_map.length = compressed_map.maxLength;
432 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
433 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400434
Nick Deakin0db53ee2023-05-19 17:14:45 -0400435 // We just want to check if ICC is present, so don't do a full decode. Note,
436 // this doesn't verify that the ICC is valid.
437 JpegDecoderHelper decoder;
438 std::vector<uint8_t> icc;
439 decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
440 /* pWidth */ nullptr, /* pHeight */ nullptr,
441 &icc, /* exifData */ nullptr);
442
443 // Add ICC if not already present.
444 if (icc.size() > 0) {
445 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
446 /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
447 } else {
448 sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
449 uncompressed_yuv_420_image->colorGamut);
450 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
451 newIcc->getData(), newIcc->getLength(), &metadata, dest));
452 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400453
Dichen Zhang6947d532022-10-22 02:16:21 +0000454 return NO_ERROR;
455}
456
Dichen Zhang636f5242022-12-07 20:25:44 +0000457/* Encode API-3 */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000458status_t JpegR::encodeJPEGR(jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000459 jr_compressed_ptr compressed_jpeg_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000460 ultrahdr_transfer_function hdr_tf,
Dichen Zhang61ede362023-02-22 18:50:13 +0000461 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530462 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
463 ALOGE("received nullptr for compressed jpeg image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000464 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000465 }
466
Ram Mohanac1cfec2023-05-18 14:41:15 +0530467 if (status_t ret = areInputArgumentsValid(
468 uncompressed_p010_image, /* uncompressed_yuv_420_image */ nullptr,
469 hdr_tf, dest) != NO_ERROR) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -0700470 return ret;
Dichen Zhang53751272023-01-17 19:09:01 -0800471 }
472
Nick Deakin0db53ee2023-05-19 17:14:45 -0400473 // Note: output is Bt.601 YUV encoded regardless of gamut, due to jpeg decode.
Dichen Zhang02dd0592023-02-10 20:16:57 +0000474 JpegDecoderHelper jpeg_decoder;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400475 if (!jpeg_decoder.decompressImage(compressed_jpeg_image->data, compressed_jpeg_image->length)) {
476 return ERROR_JPEGR_DECODE_ERROR;
477 }
478 jpegr_uncompressed_struct uncompressed_yuv_420_image;
479 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
480 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
481 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin6bd90432022-11-20 16:26:37 -0500482 uncompressed_yuv_420_image.colorGamut = compressed_jpeg_image->colorGamut;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400483
484 if (uncompressed_p010_image->width != uncompressed_yuv_420_image.width
485 || uncompressed_p010_image->height != uncompressed_yuv_420_image.height) {
486 return ERROR_JPEGR_RESOLUTION_MISMATCH;
487 }
488
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000489 ultrahdr_metadata_struct metadata;
Nick Deakin6bd90432022-11-20 16:26:37 -0500490 metadata.version = kJpegrVersion;
Nick Deakin6bd90432022-11-20 16:26:37 -0500491
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400492 jpegr_uncompressed_struct map;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400493 // Indicate that the SDR image is Bt.601 YUV encoded.
Dichen Zhang10959a42023-04-10 16:28:16 -0700494 JPEGR_CHECK(generateGainMap(
Nick Deakin0db53ee2023-05-19 17:14:45 -0400495 &uncompressed_yuv_420_image, uncompressed_p010_image, hdr_tf, &metadata, &map,
496 true /* sdr_is_601 */ ));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400497 std::unique_ptr<uint8_t[]> map_data;
498 map_data.reset(reinterpret_cast<uint8_t*>(map.data));
499
Ram Mohanf9540402023-05-18 20:08:55 +0530500 JpegEncoderHelper jpeg_encoder_gainmap;
501 JPEGR_CHECK(compressGainMap(&map, &jpeg_encoder_gainmap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400502 jpegr_compressed_struct compressed_map;
Ram Mohanf9540402023-05-18 20:08:55 +0530503 compressed_map.maxLength = jpeg_encoder_gainmap.getCompressedImageSize();
504 compressed_map.length = compressed_map.maxLength;
505 compressed_map.data = jpeg_encoder_gainmap.getCompressedImagePtr();
506 compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400507
Nick Deakin0db53ee2023-05-19 17:14:45 -0400508 // We just want to check if ICC is present, so don't do a full decode. Note,
509 // this doesn't verify that the ICC is valid.
510 JpegDecoderHelper decoder;
511 std::vector<uint8_t> icc;
512 decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
513 /* pWidth */ nullptr, /* pHeight */ nullptr,
514 &icc, /* exifData */ nullptr);
515
516 // Add ICC if not already present.
517 if (icc.size() > 0) {
518 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
519 /* icc */ nullptr, /* icc size */ 0, &metadata, dest));
520 } else {
521 sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
522 uncompressed_yuv_420_image.colorGamut);
523 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, &compressed_map, /* exif */ nullptr,
524 newIcc->getData(), newIcc->getLength(), &metadata, dest));
525 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400526
Dichen Zhang6947d532022-10-22 02:16:21 +0000527 return NO_ERROR;
528}
529
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000530/* Encode API-4 */
531status_t JpegR::encodeJPEGR(jr_compressed_ptr compressed_jpeg_image,
532 jr_compressed_ptr compressed_gainmap,
533 ultrahdr_metadata_ptr metadata,
534 jr_compressed_ptr dest) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530535 if (compressed_jpeg_image == nullptr || compressed_jpeg_image->data == nullptr) {
536 ALOGE("received nullptr for compressed jpeg image");
537 return ERROR_JPEGR_INVALID_NULL_PTR;
538 }
539
540 if (compressed_gainmap == nullptr || compressed_gainmap->data == nullptr) {
541 ALOGE("received nullptr for compressed gain map");
542 return ERROR_JPEGR_INVALID_NULL_PTR;
543 }
544
545 if (dest == nullptr || dest->data == nullptr) {
546 ALOGE("received nullptr for destination");
547 return ERROR_JPEGR_INVALID_NULL_PTR;
548 }
549
Nick Deakin0db53ee2023-05-19 17:14:45 -0400550 // We just want to check if ICC is present, so don't do a full decode. Note,
551 // this doesn't verify that the ICC is valid.
552 JpegDecoderHelper decoder;
553 std::vector<uint8_t> icc;
554 decoder.getCompressedImageParameters(compressed_jpeg_image->data, compressed_jpeg_image->length,
555 /* pWidth */ nullptr, /* pHeight */ nullptr,
556 &icc, /* exifData */ nullptr);
557
558 // Add ICC if not already present.
559 if (icc.size() > 0) {
560 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
561 /* icc */ nullptr, /* icc size */ 0, metadata, dest));
562 } else {
563 sp<DataStruct> newIcc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB,
564 compressed_jpeg_image->colorGamut);
565 JPEGR_CHECK(appendGainMap(compressed_jpeg_image, compressed_gainmap, /* exif */ nullptr,
566 newIcc->getData(), newIcc->getLength(), metadata, dest));
567 }
568
Dichen Zhang92e6c6b2023-04-14 20:20:14 +0000569 return NO_ERROR;
570}
571
Dichen Zhang61ede362023-02-22 18:50:13 +0000572status_t JpegR::getJPEGRInfo(jr_compressed_ptr compressed_jpegr_image, jr_info_ptr jpegr_info) {
Ram Mohanac1cfec2023-05-18 14:41:15 +0530573 if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
574 ALOGE("received nullptr for compressed jpegr image");
575 return ERROR_JPEGR_INVALID_NULL_PTR;
576 }
577
578 if (jpegr_info == nullptr) {
579 ALOGE("received nullptr for compressed jpegr info struct");
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000580 return ERROR_JPEGR_INVALID_NULL_PTR;
581 }
582
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530583 jpegr_compressed_struct primary_image, gainmap_image;
584 status_t status =
585 extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
586 if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
587 return status;
588 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000589
Dichen Zhang02dd0592023-02-10 20:16:57 +0000590 JpegDecoderHelper jpeg_decoder;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000591 if (!jpeg_decoder.getCompressedImageParameters(primary_image.data, primary_image.length,
592 &jpegr_info->width, &jpegr_info->height,
593 jpegr_info->iccData, jpegr_info->exifData)) {
594 return ERROR_JPEGR_DECODE_ERROR;
595 }
596
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530597 return status;
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000598}
599
Dichen Zhang636f5242022-12-07 20:25:44 +0000600/* Decode API */
Dichen Zhang761b52d2023-02-10 22:38:38 +0000601status_t JpegR::decodeJPEGR(jr_compressed_ptr compressed_jpegr_image,
Dichen Zhang61ede362023-02-22 18:50:13 +0000602 jr_uncompressed_ptr dest,
Dichen Zhangc6605702023-03-15 18:40:55 -0700603 float max_display_boost,
Dichen Zhang61ede362023-02-22 18:50:13 +0000604 jr_exif_ptr exif,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000605 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700606 jr_uncompressed_ptr gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000607 ultrahdr_metadata_ptr metadata) {
Ram Mohanb0375052023-05-20 04:03:48 +0530608 if (compressed_jpegr_image == nullptr || compressed_jpegr_image->data == nullptr) {
609 ALOGE("received nullptr for compressed jpegr image");
610 return ERROR_JPEGR_INVALID_NULL_PTR;
611 }
612
613 if (dest == nullptr || dest->data == nullptr) {
614 ALOGE("received nullptr for dest image");
Dichen Zhang80b72482022-11-02 01:55:35 +0000615 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang6947d532022-10-22 02:16:21 +0000616 }
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +0000617
Dichen Zhangb96ba8f2023-03-21 23:33:41 +0000618 if (max_display_boost < 1.0f) {
Ram Mohanb0375052023-05-20 04:03:48 +0530619 ALOGE("received bad value for max_display_boost %f", max_display_boost);
620 return ERROR_JPEGR_INVALID_INPUT_TYPE;
621 }
622
623 if (exif != nullptr && exif->data == nullptr) {
624 ALOGE("received nullptr address for exif data");
625 return ERROR_JPEGR_INVALID_INPUT_TYPE;
626 }
627
628 if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
629 ALOGE("received bad value for output format %d", output_format);
630 return ERROR_JPEGR_INVALID_INPUT_TYPE;
631 }
632
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530633 jpegr_compressed_struct primary_image, gainmap_image;
634 status_t status =
635 extractPrimaryImageAndGainMap(compressed_jpegr_image, &primary_image, &gainmap_image);
636 if (status != NO_ERROR) {
637 if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
638 ALOGE("received invalid compressed jpegr image");
639 return status;
Fyodor Kyslovea9180f2023-01-06 01:11:43 +0000640 }
Dichen Zhang14f3c472023-03-08 07:24:48 +0000641 }
642
Dichen Zhang02dd0592023-02-10 20:16:57 +0000643 JpegDecoderHelper jpeg_decoder;
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530644 if (!jpeg_decoder.decompressImage(primary_image.data, primary_image.length,
645 (output_format == ULTRAHDR_OUTPUT_SDR))) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400646 return ERROR_JPEGR_DECODE_ERROR;
647 }
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530648
649 if (output_format == ULTRAHDR_OUTPUT_SDR) {
650 if ((jpeg_decoder.getDecompressedImageWidth() *
651 jpeg_decoder.getDecompressedImageHeight() * 4) >
652 jpeg_decoder.getDecompressedImageSize()) {
653 return ERROR_JPEGR_CALCULATION_ERROR;
654 }
655 } else {
656 if ((jpeg_decoder.getDecompressedImageWidth() *
657 jpeg_decoder.getDecompressedImageHeight() * 3 / 2) >
658 jpeg_decoder.getDecompressedImageSize()) {
659 return ERROR_JPEGR_CALCULATION_ERROR;
660 }
Ram Mohan9b3d6852023-05-26 00:09:50 +0530661 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400662
Dichen Zhangb80e2262023-03-08 06:59:51 +0000663 if (exif != nullptr) {
664 if (exif->data == nullptr) {
665 return ERROR_JPEGR_INVALID_NULL_PTR;
666 }
667 if (exif->length < jpeg_decoder.getEXIFSize()) {
668 return ERROR_JPEGR_BUFFER_TOO_SMALL;
669 }
670 memcpy(exif->data, jpeg_decoder.getEXIFPtr(), jpeg_decoder.getEXIFSize());
671 exif->length = jpeg_decoder.getEXIFSize();
672 }
673
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530674 if (output_format == ULTRAHDR_OUTPUT_SDR) {
675 dest->width = jpeg_decoder.getDecompressedImageWidth();
676 dest->height = jpeg_decoder.getDecompressedImageHeight();
677 memcpy(dest->data, jpeg_decoder.getDecompressedImagePtr(), dest->width * dest->height * 4);
678 return NO_ERROR;
679 }
680
681 JpegDecoderHelper gain_map_decoder;
682 if (!gain_map_decoder.decompressImage(gainmap_image.data, gainmap_image.length)) {
683 return ERROR_JPEGR_DECODE_ERROR;
684 }
685 if ((gain_map_decoder.getDecompressedImageWidth() *
686 gain_map_decoder.getDecompressedImageHeight()) >
687 gain_map_decoder.getDecompressedImageSize()) {
688 return ERROR_JPEGR_CALCULATION_ERROR;
689 }
690
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000691 jpegr_uncompressed_struct map;
Dichen Zhang10959a42023-04-10 16:28:16 -0700692 map.data = gain_map_decoder.getDecompressedImagePtr();
693 map.width = gain_map_decoder.getDecompressedImageWidth();
694 map.height = gain_map_decoder.getDecompressedImageHeight();
Fyodor Kyslovbf241572022-12-13 22:38:07 +0000695
Ram Mohancdc0f1f2023-06-20 16:45:09 +0530696 if (gain_map != nullptr) {
697 gain_map->width = map.width;
698 gain_map->height = map.height;
699 int size = gain_map->width * gain_map->height;
700 gain_map->data = malloc(size);
701 memcpy(gain_map->data, map.data, size);
702 }
703
704 ultrahdr_metadata_struct uhdr_metadata;
705 if (!getMetadataFromXMP(static_cast<uint8_t*>(gain_map_decoder.getXMPPtr()),
706 gain_map_decoder.getXMPSize(), &uhdr_metadata)) {
707 return ERROR_JPEGR_INVALID_METADATA;
708 }
709
710 if (metadata != nullptr) {
711 metadata->version = uhdr_metadata.version;
712 metadata->minContentBoost = uhdr_metadata.minContentBoost;
713 metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
714 metadata->gamma = uhdr_metadata.gamma;
715 metadata->offsetSdr = uhdr_metadata.offsetSdr;
716 metadata->offsetHdr = uhdr_metadata.offsetHdr;
717 metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
718 metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
719 }
720
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400721 jpegr_uncompressed_struct uncompressed_yuv_420_image;
722 uncompressed_yuv_420_image.data = jpeg_decoder.getDecompressedImagePtr();
723 uncompressed_yuv_420_image.width = jpeg_decoder.getDecompressedImageWidth();
724 uncompressed_yuv_420_image.height = jpeg_decoder.getDecompressedImageHeight();
Nick Deakin0db53ee2023-05-19 17:14:45 -0400725 uncompressed_yuv_420_image.colorGamut = IccHelper::readIccColorGamut(
726 jpeg_decoder.getICCPtr(), jpeg_decoder.getICCSize());
727
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000728 JPEGR_CHECK(applyGainMap(&uncompressed_yuv_420_image, &map, &uhdr_metadata, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700729 max_display_boost, dest));
Dichen Zhang6947d532022-10-22 02:16:21 +0000730 return NO_ERROR;
731}
732
Dichen Zhang10959a42023-04-10 16:28:16 -0700733status_t JpegR::compressGainMap(jr_uncompressed_ptr uncompressed_gain_map,
Ram Mohanf9540402023-05-18 20:08:55 +0530734 JpegEncoderHelper* jpeg_encoder) {
735 if (uncompressed_gain_map == nullptr || jpeg_encoder == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000736 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700737 }
738
Nick Deakin0db53ee2023-05-19 17:14:45 -0400739 // Don't need to convert YUV to Bt601 since single channel
Ram Mohanf9540402023-05-18 20:08:55 +0530740 if (!jpeg_encoder->compressImage(uncompressed_gain_map->data,
741 uncompressed_gain_map->width,
742 uncompressed_gain_map->height,
743 kMapCompressQuality,
744 nullptr,
745 0,
746 true /* isSingleChannel */)) {
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400747 return ERROR_JPEGR_ENCODE_ERROR;
748 }
749
Dichen Zhang6947d532022-10-22 02:16:21 +0000750 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700751}
752
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800753const int kJobSzInRows = 16;
754static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
755 "align job size to kMapDimensionScaleFactor");
756
757class JobQueue {
758 public:
759 bool dequeueJob(size_t& rowStart, size_t& rowEnd);
760 void enqueueJob(size_t rowStart, size_t rowEnd);
761 void markQueueForEnd();
762 void reset();
763
764 private:
765 bool mQueuedAllJobs = false;
766 std::deque<std::tuple<size_t, size_t>> mJobs;
767 std::mutex mMutex;
768 std::condition_variable mCv;
769};
770
771bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
772 std::unique_lock<std::mutex> lock{mMutex};
773 while (true) {
774 if (mJobs.empty()) {
775 if (mQueuedAllJobs) {
776 return false;
777 } else {
Ram Mohand39fb082023-05-09 17:25:49 +0000778 mCv.wait_for(lock, std::chrono::milliseconds(100));
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800779 }
780 } else {
781 auto it = mJobs.begin();
782 rowStart = std::get<0>(*it);
783 rowEnd = std::get<1>(*it);
784 mJobs.erase(it);
785 return true;
786 }
787 }
788 return false;
789}
790
791void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) {
792 std::unique_lock<std::mutex> lock{mMutex};
793 mJobs.push_back(std::make_tuple(rowStart, rowEnd));
794 lock.unlock();
795 mCv.notify_one();
796}
797
798void JobQueue::markQueueForEnd() {
799 std::unique_lock<std::mutex> lock{mMutex};
800 mQueuedAllJobs = true;
Ram Mohand39fb082023-05-09 17:25:49 +0000801 lock.unlock();
802 mCv.notify_all();
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800803}
804
805void JobQueue::reset() {
806 std::unique_lock<std::mutex> lock{mMutex};
807 mJobs.clear();
808 mQueuedAllJobs = false;
809}
810
Dichen Zhang10959a42023-04-10 16:28:16 -0700811status_t JpegR::generateGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
812 jr_uncompressed_ptr uncompressed_p010_image,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000813 ultrahdr_transfer_function hdr_tf,
814 ultrahdr_metadata_ptr metadata,
Nick Deakin0db53ee2023-05-19 17:14:45 -0400815 jr_uncompressed_ptr dest,
816 bool sdr_is_601) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700817 if (uncompressed_yuv_420_image == nullptr
818 || uncompressed_p010_image == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500819 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700820 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000821 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700822 }
823
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400824 if (uncompressed_yuv_420_image->width != uncompressed_p010_image->width
825 || uncompressed_yuv_420_image->height != uncompressed_p010_image->height) {
826 return ERROR_JPEGR_RESOLUTION_MISMATCH;
827 }
828
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000829 if (uncompressed_yuv_420_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED
830 || uncompressed_p010_image->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
Nick Deakin6bd90432022-11-20 16:26:37 -0500831 return ERROR_JPEGR_INVALID_COLORGAMUT;
832 }
833
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400834 size_t image_width = uncompressed_yuv_420_image->width;
835 size_t image_height = uncompressed_yuv_420_image->height;
Ram Mohan43c3a802023-07-24 18:33:49 +0530836 size_t map_width = static_cast<size_t>(
837 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
838 size_t map_height = static_cast<size_t>(
839 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400840
Ram Mohan43c3a802023-07-24 18:33:49 +0530841 dest->width = map_width;
842 dest->height = map_height;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000843 dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
Ram Mohan43c3a802023-07-24 18:33:49 +0530844 dest->data = new uint8_t[map_width * map_height];
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400845 std::unique_ptr<uint8_t[]> map_data;
846 map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
847
Nick Deakin6bd90432022-11-20 16:26:37 -0500848 ColorTransformFn hdrInvOetf = nullptr;
Ram Mohancd3f6372023-06-02 15:16:15 +0530849 float hdr_white_nits = kSdrWhiteNits;
Nick Deakin01759062023-02-02 18:21:43 -0500850 switch (hdr_tf) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000851 case ULTRAHDR_TF_LINEAR:
Dichen Zhangdc8452b2022-11-23 17:17:56 +0000852 hdrInvOetf = identityConversion;
853 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000854 case ULTRAHDR_TF_HLG:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800855#if USE_HLG_INVOETF_LUT
856 hdrInvOetf = hlgInvOetfLUT;
857#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500858 hdrInvOetf = hlgInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800859#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500860 hdr_white_nits = kHlgMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500861 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000862 case ULTRAHDR_TF_PQ:
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800863#if USE_PQ_INVOETF_LUT
864 hdrInvOetf = pqInvOetfLUT;
865#else
Nick Deakin6bd90432022-11-20 16:26:37 -0500866 hdrInvOetf = pqInvOetf;
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800867#endif
Nick Deakin65f492a2022-11-29 22:47:40 -0500868 hdr_white_nits = kPqMaxNits;
Nick Deakin6bd90432022-11-20 16:26:37 -0500869 break;
Dichen Zhang6438a192023-01-29 07:51:15 +0000870 default:
Dichen Zhangb27d06d2022-12-14 19:57:50 +0000871 // Should be impossible to hit after input validation.
872 return ERROR_JPEGR_INVALID_TRANS_FUNC;
Nick Deakin6bd90432022-11-20 16:26:37 -0500873 }
874
Nick Deakina2215292023-02-14 21:40:06 -0500875 metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
876 metadata->minContentBoost = 1.0f;
Nick Deakin094946b2023-06-09 11:58:41 -0400877 metadata->gamma = 1.0f;
878 metadata->offsetSdr = 0.0f;
879 metadata->offsetHdr = 0.0f;
880 metadata->hdrCapacityMin = 1.0f;
881 metadata->hdrCapacityMax = metadata->maxContentBoost;
882
Dichen Zhangf7a9e172023-04-06 18:23:47 -0700883 float log2MinBoost = log2(metadata->minContentBoost);
884 float log2MaxBoost = log2(metadata->maxContentBoost);
Nick Deakina2215292023-02-14 21:40:06 -0500885
Nick Deakin6bd90432022-11-20 16:26:37 -0500886 ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(
887 uncompressed_yuv_420_image->colorGamut, uncompressed_p010_image->colorGamut);
888
889 ColorCalculationFn luminanceFn = nullptr;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400890 ColorTransformFn sdrYuvToRgbFn = nullptr;
Nick Deakin6bd90432022-11-20 16:26:37 -0500891 switch (uncompressed_yuv_420_image->colorGamut) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000892 case ULTRAHDR_COLORGAMUT_BT709:
Nick Deakin6bd90432022-11-20 16:26:37 -0500893 luminanceFn = srgbLuminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400894 sdrYuvToRgbFn = srgbYuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500895 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000896 case ULTRAHDR_COLORGAMUT_P3:
Nick Deakin6bd90432022-11-20 16:26:37 -0500897 luminanceFn = p3Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400898 sdrYuvToRgbFn = p3YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500899 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000900 case ULTRAHDR_COLORGAMUT_BT2100:
Nick Deakin6bd90432022-11-20 16:26:37 -0500901 luminanceFn = bt2100Luminance;
Nick Deakin0db53ee2023-05-19 17:14:45 -0400902 sdrYuvToRgbFn = bt2100YuvToRgb;
903 break;
904 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
905 // Should be impossible to hit after input validation.
906 return ERROR_JPEGR_INVALID_COLORGAMUT;
907 }
908 if (sdr_is_601) {
909 sdrYuvToRgbFn = p3YuvToRgb;
910 }
911
912 ColorTransformFn hdrYuvToRgbFn = nullptr;
913 switch (uncompressed_p010_image->colorGamut) {
914 case ULTRAHDR_COLORGAMUT_BT709:
915 hdrYuvToRgbFn = srgbYuvToRgb;
916 break;
917 case ULTRAHDR_COLORGAMUT_P3:
918 hdrYuvToRgbFn = p3YuvToRgb;
919 break;
920 case ULTRAHDR_COLORGAMUT_BT2100:
921 hdrYuvToRgbFn = bt2100YuvToRgb;
Nick Deakin6bd90432022-11-20 16:26:37 -0500922 break;
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000923 case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
Nick Deakin6bd90432022-11-20 16:26:37 -0500924 // Should be impossible to hit after input validation.
925 return ERROR_JPEGR_INVALID_COLORGAMUT;
926 }
927
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800928 std::mutex mutex;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800929 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
930 size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
931 JobQueue jobQueue;
Nick Deakin594a4ca2022-11-16 20:57:42 -0500932
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800933 std::function<void()> generateMap = [uncompressed_yuv_420_image, uncompressed_p010_image,
934 metadata, dest, hdrInvOetf, hdrGamutConversionFn,
Nick Deakin0db53ee2023-05-19 17:14:45 -0400935 luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits,
936 log2MinBoost, log2MaxBoost, &jobQueue]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800937 size_t rowStart, rowEnd;
938 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
939 for (size_t y = rowStart; y < rowEnd; ++y) {
Ram Mohan43c3a802023-07-24 18:33:49 +0530940 for (size_t x = 0; x < dest->width; ++x) {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800941 Color sdr_yuv_gamma =
942 sampleYuv420(uncompressed_yuv_420_image, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400943 Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
944 // We are assuming the SDR input is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800945#if USE_SRGB_INVOETF_LUT
946 Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
947#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800948 Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -0800949#endif
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800950 float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
951
952 Color hdr_yuv_gamma = sampleP010(uncompressed_p010_image, kMapDimensionScaleFactor, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -0400953 Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800954 Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
955 hdr_rgb = hdrGamutConversionFn(hdr_rgb);
956 float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
957
Ram Mohan43c3a802023-07-24 18:33:49 +0530958 size_t pixel_idx = x + y * dest->width;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800959 reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
Dichen Zhang10959a42023-04-10 16:28:16 -0700960 encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800961 }
962 }
963 }
964 };
965
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800966 // generate map
Nick Deakina2215292023-02-14 21:40:06 -0500967 std::vector<std::thread> workers;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800968 for (int th = 0; th < threads - 1; th++) {
969 workers.push_back(std::thread(generateMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400970 }
971
Harish Mahendrakar72b6f302022-12-16 10:39:15 -0800972 rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
973 for (size_t rowStart = 0; rowStart < map_height;) {
974 size_t rowEnd = std::min(rowStart + rowStep, map_height);
975 jobQueue.enqueueJob(rowStart, rowEnd);
976 rowStart = rowEnd;
977 }
978 jobQueue.markQueueForEnd();
979 generateMap();
980 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
981
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400982 map_data.release();
Dichen Zhang6947d532022-10-22 02:16:21 +0000983 return NO_ERROR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700984}
985
Dichen Zhang10959a42023-04-10 16:28:16 -0700986status_t JpegR::applyGainMap(jr_uncompressed_ptr uncompressed_yuv_420_image,
987 jr_uncompressed_ptr uncompressed_gain_map,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +0000988 ultrahdr_metadata_ptr metadata,
989 ultrahdr_output_format output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -0700990 float max_display_boost,
991 jr_uncompressed_ptr dest) {
Dichen Zhang596a7562022-10-12 14:57:05 -0700992 if (uncompressed_yuv_420_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -0700993 || uncompressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -0500994 || metadata == nullptr
Dichen Zhang596a7562022-10-12 14:57:05 -0700995 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +0000996 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang596a7562022-10-12 14:57:05 -0700997 }
998
Nick Deakin094946b2023-06-09 11:58:41 -0400999 if (metadata->version.compare("1.0")) {
1000 ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
1001 return ERROR_JPEGR_UNSUPPORTED_METADATA;
1002 }
1003 if (metadata->gamma != 1.0f) {
1004 ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
1005 return ERROR_JPEGR_UNSUPPORTED_METADATA;
1006 }
1007 if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
1008 ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr,
1009 metadata->offsetHdr);
1010 return ERROR_JPEGR_UNSUPPORTED_METADATA;
1011 }
1012 if (metadata->hdrCapacityMin != metadata->minContentBoost
1013 || metadata->hdrCapacityMax != metadata->maxContentBoost) {
1014 ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
1015 metadata->hdrCapacityMax);
1016 return ERROR_JPEGR_UNSUPPORTED_METADATA;
1017 }
1018
Ram Mohan9b3d6852023-05-26 00:09:50 +05301019 // TODO: remove once map scaling factor is computed based on actual map dims
1020 size_t image_width = uncompressed_yuv_420_image->width;
1021 size_t image_height = uncompressed_yuv_420_image->height;
Ram Mohan43c3a802023-07-24 18:33:49 +05301022 size_t map_width = static_cast<size_t>(
1023 floor((image_width + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
1024 size_t map_height = static_cast<size_t>(
1025 floor((image_height + kMapDimensionScaleFactor - 1) / kMapDimensionScaleFactor));
Ram Mohan9b3d6852023-05-26 00:09:50 +05301026 if (map_width != uncompressed_gain_map->width
1027 || map_height != uncompressed_gain_map->height) {
1028 ALOGE("gain map dimensions and primary image dimensions are not to scale");
1029 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1030 }
1031
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001032 dest->width = uncompressed_yuv_420_image->width;
1033 dest->height = uncompressed_yuv_420_image->height;
Ram Mohanfe723d62022-12-15 00:59:11 +05301034 ShepardsIDW idwTable(kMapDimensionScaleFactor);
Dichen Zhangb96ba8f2023-03-21 23:33:41 +00001035 float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
Dichen Zhang10959a42023-04-10 16:28:16 -07001036 GainLUT gainLUT(metadata, display_boost);
Ram Mohanfe723d62022-12-15 00:59:11 +05301037
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001038 JobQueue jobQueue;
Dichen Zhang10959a42023-04-10 16:28:16 -07001039 std::function<void()> applyRecMap = [uncompressed_yuv_420_image, uncompressed_gain_map,
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001040 metadata, dest, &jobQueue, &idwTable, output_format,
Dichen Zhang10959a42023-04-10 16:28:16 -07001041 &gainLUT, display_boost]() -> void {
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001042 size_t width = uncompressed_yuv_420_image->width;
1043 size_t height = uncompressed_yuv_420_image->height;
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001044
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001045 size_t rowStart, rowEnd;
1046 while (jobQueue.dequeueJob(rowStart, rowEnd)) {
1047 for (size_t y = rowStart; y < rowEnd; ++y) {
1048 for (size_t x = 0; x < width; ++x) {
1049 Color yuv_gamma_sdr = getYuv420Pixel(uncompressed_yuv_420_image, x, y);
Nick Deakin0db53ee2023-05-19 17:14:45 -04001050 // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
1051 Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
1052 // We are assuming the SDR base image is always sRGB transfer.
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001053#if USE_SRGB_INVOETF_LUT
1054 Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
1055#else
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001056 Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
Harish Mahendrakar555a06b2022-12-14 09:37:27 -08001057#endif
Dichen Zhang10959a42023-04-10 16:28:16 -07001058 float gain;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001059 // TODO: determine map scaling factor based on actual map dims
1060 size_t map_scale_factor = kMapDimensionScaleFactor;
1061 // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
1062 // Currently map_scale_factor is of type size_t, but it could be changed to a float
1063 // later.
1064 if (map_scale_factor != floorf(map_scale_factor)) {
Dichen Zhang10959a42023-04-10 16:28:16 -07001065 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001066 } else {
Dichen Zhang10959a42023-04-10 16:28:16 -07001067 gain = sampleMap(uncompressed_gain_map, map_scale_factor, x, y, idwTable);
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001068 }
Dichen Zhangc6605702023-03-15 18:40:55 -07001069
Dichen Zhang10959a42023-04-10 16:28:16 -07001070#if USE_APPLY_GAIN_LUT
1071 Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001072#else
Dichen Zhang10959a42023-04-10 16:28:16 -07001073 Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
Harish Mahendrakarf25991f2022-12-16 11:57:44 -08001074#endif
Dichen Zhangc6605702023-03-15 18:40:55 -07001075 rgb_hdr = rgb_hdr / display_boost;
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001076 size_t pixel_idx = x + y * width;
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001077
1078 switch (output_format) {
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001079 case ULTRAHDR_OUTPUT_HDR_LINEAR:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001080 {
1081 uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
1082 reinterpret_cast<uint64_t*>(dest->data)[pixel_idx] = rgba_f16;
1083 break;
1084 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001085 case ULTRAHDR_OUTPUT_HDR_HLG:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001086 {
1087#if USE_HLG_OETF_LUT
1088 ColorTransformFn hdrOetf = hlgOetfLUT;
1089#else
1090 ColorTransformFn hdrOetf = hlgOetf;
1091#endif
1092 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1093 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1094 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1095 break;
1096 }
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001097 case ULTRAHDR_OUTPUT_HDR_PQ:
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001098 {
Ram Mohan67862992023-06-22 15:56:05 +05301099#if USE_PQ_OETF_LUT
Dichen Zhang3e5798c2023-03-01 22:30:43 +00001100 ColorTransformFn hdrOetf = pqOetfLUT;
1101#else
1102 ColorTransformFn hdrOetf = pqOetf;
1103#endif
1104 Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
1105 uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
1106 reinterpret_cast<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
1107 break;
1108 }
1109 default:
1110 {}
1111 // Should be impossible to hit after input validation.
1112 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001113 }
1114 }
1115 }
1116 };
1117
1118 const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
1119 std::vector<std::thread> workers;
1120 for (int th = 0; th < threads - 1; th++) {
1121 workers.push_back(std::thread(applyRecMap));
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001122 }
Harish Mahendrakar72b6f302022-12-16 10:39:15 -08001123 const int rowStep = threads == 1 ? uncompressed_yuv_420_image->height : kJobSzInRows;
1124 for (int rowStart = 0; rowStart < uncompressed_yuv_420_image->height;) {
1125 int rowEnd = std::min(rowStart + rowStep, uncompressed_yuv_420_image->height);
1126 jobQueue.enqueueJob(rowStart, rowEnd);
1127 rowStart = rowEnd;
1128 }
1129 jobQueue.markQueueForEnd();
1130 applyRecMap();
1131 std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001132 return NO_ERROR;
1133}
1134
Dichen Zhang10959a42023-04-10 16:28:16 -07001135status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr compressed_jpegr_image,
1136 jr_compressed_ptr primary_image,
1137 jr_compressed_ptr gain_map) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001138 if (compressed_jpegr_image == nullptr) {
1139 return ERROR_JPEGR_INVALID_NULL_PTR;
1140 }
1141
1142 MessageHandler msg_handler;
1143 std::shared_ptr<DataSegment> seg =
1144 DataSegment::Create(DataRange(0, compressed_jpegr_image->length),
1145 static_cast<const uint8_t*>(compressed_jpegr_image->data),
1146 DataSegment::BufferDispositionPolicy::kDontDelete);
1147 DataSegmentDataSource data_source(seg);
1148 JpegInfoBuilder jpeg_info_builder;
1149 jpeg_info_builder.SetImageLimit(2);
1150 JpegScanner jpeg_scanner(&msg_handler);
1151 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
1152 data_source.Reset();
1153
1154 if (jpeg_scanner.HasError()) {
1155 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1156 }
1157
1158 const auto& jpeg_info = jpeg_info_builder.GetInfo();
1159 const auto& image_ranges = jpeg_info.GetImageRanges();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001160
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301161 if (image_ranges.empty()) {
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001162 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1163 }
1164
1165 if (primary_image != nullptr) {
1166 primary_image->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
1167 image_ranges[0].GetBegin();
1168 primary_image->length = image_ranges[0].GetLength();
1169 }
1170
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301171 if (image_ranges.size() == 1) {
1172 return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
1173 }
1174
Dichen Zhang10959a42023-04-10 16:28:16 -07001175 if (gain_map != nullptr) {
1176 gain_map->data = static_cast<uint8_t*>(compressed_jpegr_image->data) +
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001177 image_ranges[1].GetBegin();
Dichen Zhang10959a42023-04-10 16:28:16 -07001178 gain_map->length = image_ranges[1].GetLength();
Fyodor Kyslov1dcc4422022-11-16 01:40:53 +00001179 }
Nick Deakinf6bca5a2022-11-04 10:43:43 -04001180
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301181 // TODO: choose primary image and gain map image carefully
1182 if (image_ranges.size() > 2) {
1183 ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
1184 (int)image_ranges.size());
Dichen Zhang85b37562022-10-11 11:08:28 -07001185 }
1186
Ram Mohancdc0f1f2023-06-20 16:45:09 +05301187 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001188}
1189
Dichen Zhangd18bc302022-12-16 20:55:24 +00001190// JPEG/R structure:
1191// SOI (ff d8)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001192//
1193// (Optional, only if EXIF package is from outside)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001194// APP1 (ff e1)
1195// 2 bytes of length (2 + length of exif package)
1196// EXIF package (this includes the first two bytes representing the package length)
Dichen Zhang50ff1292023-01-27 18:03:43 +00001197//
1198// (Required, XMP package) APP1 (ff e1)
Dichen Zhangd18bc302022-12-16 20:55:24 +00001199// 2 bytes of length (2 + 29 + length of xmp package)
1200// name space ("http://ns.adobe.com/xap/1.0/\0")
Dichen Zhang61ede362023-02-22 18:50:13 +00001201// XMP
1202//
1203// (Required, MPF package) APP2 (ff e2)
1204// 2 bytes of length
1205// MPF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001206//
1207// (Required) primary image (without the first two bytes (SOI), may have other packages)
1208//
Dichen Zhang61ede362023-02-22 18:50:13 +00001209// SOI (ff d8)
1210//
1211// (Required, XMP package) APP1 (ff e1)
1212// 2 bytes of length (2 + 29 + length of xmp package)
1213// name space ("http://ns.adobe.com/xap/1.0/\0")
1214// XMP
1215//
Dichen Zhang10959a42023-04-10 16:28:16 -07001216// (Required) secondary image (the gain map, without the first two bytes (SOI))
Dichen Zhangd18bc302022-12-16 20:55:24 +00001217//
1218// Metadata versions we are using:
1219// ECMA TR-98 for JFIF marker
1220// Exif 2.2 spec for EXIF marker
1221// Adobe XMP spec part 3 for XMP marker
1222// ICC v4.3 spec for ICC
Dichen Zhang10959a42023-04-10 16:28:16 -07001223status_t JpegR::appendGainMap(jr_compressed_ptr compressed_jpeg_image,
1224 jr_compressed_ptr compressed_gain_map,
1225 jr_exif_ptr exif,
Nick Deakin0db53ee2023-05-19 17:14:45 -04001226 void* icc, size_t icc_size,
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001227 ultrahdr_metadata_ptr metadata,
Dichen Zhang10959a42023-04-10 16:28:16 -07001228 jr_compressed_ptr dest) {
Dichen Zhang6947d532022-10-22 02:16:21 +00001229 if (compressed_jpeg_image == nullptr
Dichen Zhang10959a42023-04-10 16:28:16 -07001230 || compressed_gain_map == nullptr
Nick Deakin6bd90432022-11-20 16:26:37 -05001231 || metadata == nullptr
Dichen Zhang6947d532022-10-22 02:16:21 +00001232 || dest == nullptr) {
Dichen Zhang80b72482022-11-02 01:55:35 +00001233 return ERROR_JPEGR_INVALID_NULL_PTR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001234 }
1235
Nick Deakin094946b2023-06-09 11:58:41 -04001236 if (metadata->version.compare("1.0")) {
1237 ALOGE("received bad value for version: %s", metadata->version.c_str());
1238 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1239 }
1240 if (metadata->maxContentBoost < metadata->minContentBoost) {
Ram Mohancd3f6372023-06-02 15:16:15 +05301241 ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
1242 metadata->maxContentBoost);
1243 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1244 }
1245
Nick Deakin094946b2023-06-09 11:58:41 -04001246 if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
1247 ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
1248 metadata->hdrCapacityMax);
1249 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1250 }
1251
1252 if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
1253 ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr,
1254 metadata->offsetHdr);
1255 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1256 }
1257
1258 if (metadata->gamma <= 0.0f) {
1259 ALOGE("received bad value for gamma %f", metadata->gamma);
1260 return ERROR_JPEGR_INVALID_INPUT_TYPE;
1261 }
1262
Dichen Zhang15345ea2023-02-23 23:54:32 +00001263 const string nameSpace = "http://ns.adobe.com/xap/1.0/";
1264 const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
Dichen Zhanga8766262022-11-07 23:48:24 +00001265
Dichen Zhang15345ea2023-02-23 23:54:32 +00001266 // calculate secondary image length first, because the length will be written into the primary
1267 // image xmp
Dichen Zhang61ede362023-02-22 18:50:13 +00001268 const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001269 const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
1270 + nameSpaceLength /* 29 bytes length of name space including \0 */
1271 + xmp_secondary.size(); /* length of xmp packet */
1272 const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
1273 + xmp_secondary_length
Dichen Zhang10959a42023-04-10 16:28:16 -07001274 + compressed_gain_map->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001275 // primary image
Nick Deakin05ceebf2023-04-19 15:27:13 -04001276 const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
Dichen Zhang15345ea2023-02-23 23:54:32 +00001277 // same as primary
1278 const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
Dichen Zhang61ede362023-02-22 18:50:13 +00001279
Dichen Zhang15345ea2023-02-23 23:54:32 +00001280 int pos = 0;
Dichen Zhang61ede362023-02-22 18:50:13 +00001281 // Begin primary image
Dichen Zhangd18bc302022-12-16 20:55:24 +00001282 // Write SOI
Dichen Zhanga8766262022-11-07 23:48:24 +00001283 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1284 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001285
1286 // Write EXIF
Dichen Zhang50ff1292023-01-27 18:03:43 +00001287 if (exif != nullptr) {
Dichen Zhangd18bc302022-12-16 20:55:24 +00001288 const int length = 2 + exif->length;
1289 const uint8_t lengthH = ((length >> 8) & 0xff);
1290 const uint8_t lengthL = (length & 0xff);
1291 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1292 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1293 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1294 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1295 JPEGR_CHECK(Write(dest, exif->data, exif->length, pos));
1296 }
1297
1298 // Prepare and write XMP
1299 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001300 const int length = xmp_primary_length;
Dichen Zhangd18bc302022-12-16 20:55:24 +00001301 const uint8_t lengthH = ((length >> 8) & 0xff);
1302 const uint8_t lengthL = (length & 0xff);
1303 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1304 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1305 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1306 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1307 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001308 JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
1309 }
1310
Nick Deakin0db53ee2023-05-19 17:14:45 -04001311 // Write ICC
1312 if (icc != nullptr && icc_size > 0) {
1313 const int length = icc_size + 2;
1314 const uint8_t lengthH = ((length >> 8) & 0xff);
1315 const uint8_t lengthL = (length & 0xff);
1316 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, icc, icc_size, pos));
1321 }
1322
Dichen Zhang61ede362023-02-22 18:50:13 +00001323 // Prepare and write MPF
1324 {
1325 const int length = 2 + calculateMpfSize();
1326 const uint8_t lengthH = ((length >> 8) & 0xff);
1327 const uint8_t lengthL = (length & 0xff);
1328 int primary_image_size = pos + length + compressed_jpeg_image->length;
Dichen Zhang15345ea2023-02-23 23:54:32 +00001329 // between APP2 + package size + signature
1330 // ff e2 00 58 4d 50 46 00
1331 // 2 + 2 + 4 = 8 (bytes)
1332 // and ff d8 sign of the secondary image
1333 int secondary_image_offset = primary_image_size - pos - 8;
1334 sp<DataStruct> mpf = generateMpf(primary_image_size,
1335 0, /* primary_image_offset */
1336 secondary_image_size,
1337 secondary_image_offset);
Dichen Zhang61ede362023-02-22 18:50:13 +00001338 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1339 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
1340 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1341 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1342 JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001343 }
1344
1345 // Write primary image
Dichen Zhanga8766262022-11-07 23:48:24 +00001346 JPEGR_CHECK(Write(dest,
1347 (uint8_t*)compressed_jpeg_image->data + 2, compressed_jpeg_image->length - 2, pos));
Dichen Zhang61ede362023-02-22 18:50:13 +00001348 // Finish primary image
1349
Dichen Zhang10959a42023-04-10 16:28:16 -07001350 // Begin secondary image (gain map)
Dichen Zhang61ede362023-02-22 18:50:13 +00001351 // Write SOI
1352 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1353 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
1354
1355 // Prepare and write XMP
1356 {
Dichen Zhang15345ea2023-02-23 23:54:32 +00001357 const int length = xmp_secondary_length;
Dichen Zhang61ede362023-02-22 18:50:13 +00001358 const uint8_t lengthH = ((length >> 8) & 0xff);
1359 const uint8_t lengthL = (length & 0xff);
1360 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
1361 JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
1362 JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
1363 JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
1364 JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
1365 JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
1366 }
Dichen Zhangd18bc302022-12-16 20:55:24 +00001367
1368 // Write secondary image
Dichen Zhang61ede362023-02-22 18:50:13 +00001369 JPEGR_CHECK(Write(dest,
Dichen Zhang10959a42023-04-10 16:28:16 -07001370 (uint8_t*)compressed_gain_map->data + 2, compressed_gain_map->length - 2, pos));
Dichen Zhangd18bc302022-12-16 20:55:24 +00001371
1372 // Set back length
Dichen Zhanga8766262022-11-07 23:48:24 +00001373 dest->length = pos;
1374
Dichen Zhangd18bc302022-12-16 20:55:24 +00001375 // Done!
Dichen Zhang6947d532022-10-22 02:16:21 +00001376 return NO_ERROR;
Dichen Zhang85b37562022-10-11 11:08:28 -07001377}
1378
Dichen Zhang61ede362023-02-22 18:50:13 +00001379status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001380 if (src == nullptr || dest == nullptr) {
Dichen Zhang636f5242022-12-07 20:25:44 +00001381 return ERROR_JPEGR_INVALID_NULL_PTR;
1382 }
1383
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001384 uint16_t* src_luma_data = reinterpret_cast<uint16_t*>(src->data);
Dichen Zhang98b06852023-05-17 16:56:51 +00001385 size_t src_luma_stride = src->luma_stride == 0 ? src->width : src->luma_stride;
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001386
Dichen Zhang98b06852023-05-17 16:56:51 +00001387 uint16_t* src_chroma_data;
1388 size_t src_chroma_stride;
1389 if (src->chroma_data == nullptr) {
1390 src_chroma_stride = src_luma_stride;
1391 src_chroma_data = &reinterpret_cast<uint16_t*>(src->data)[src_luma_stride * src->height];
1392 } else {
1393 src_chroma_stride = src->chroma_stride;
1394 src_chroma_data = reinterpret_cast<uint16_t*>(src->chroma_data);
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001395 }
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001396 dest->width = src->width;
1397 dest->height = src->height;
Dichen Zhang636f5242022-12-07 20:25:44 +00001398
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001399 size_t dest_luma_pixel_count = dest->width * dest->height;
1400
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001401 for (size_t y = 0; y < src->height; ++y) {
1402 for (size_t x = 0; x < src->width; ++x) {
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001403 size_t src_y_idx = y * src_luma_stride + x;
1404 size_t src_u_idx = (y >> 1) * src_chroma_stride + (x & ~0x1);
1405 size_t src_v_idx = src_u_idx + 1;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001406
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001407 uint16_t y_uint = src_luma_data[src_y_idx] >> 6;
1408 uint16_t u_uint = src_chroma_data[src_u_idx] >> 6;
1409 uint16_t v_uint = src_chroma_data[src_v_idx] >> 6;
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001410
Dichen Zhang66ca6e32023-04-05 12:22:54 -07001411 size_t dest_y_idx = x + y * dest->width;
1412 size_t dest_uv_idx = x / 2 + (y / 2) * (dest->width / 2);
1413
1414 uint8_t* y = &reinterpret_cast<uint8_t*>(dest->data)[dest_y_idx];
1415 uint8_t* u = &reinterpret_cast<uint8_t*>(dest->data)[dest_luma_pixel_count + dest_uv_idx];
1416 uint8_t* v = &reinterpret_cast<uint8_t*>(
1417 dest->data)[dest_luma_pixel_count * 5 / 4 + dest_uv_idx];
Dichen Zhangc3437ca2023-01-04 14:00:08 -08001418
1419 *y = static_cast<uint8_t>((y_uint >> 2) & 0xff);
1420 *u = static_cast<uint8_t>((u_uint >> 2) & 0xff);
1421 *v = static_cast<uint8_t>((v_uint >> 2) & 0xff);
1422 }
1423 }
1424
1425 dest->colorGamut = src->colorGamut;
Dichen Zhang636f5242022-12-07 20:25:44 +00001426
1427 return NO_ERROR;
1428}
1429
Nick Deakin0db53ee2023-05-19 17:14:45 -04001430status_t JpegR::convertYuv(jr_uncompressed_ptr image,
1431 ultrahdr_color_gamut src_encoding,
1432 ultrahdr_color_gamut dest_encoding) {
1433 if (image == nullptr) {
1434 return ERROR_JPEGR_INVALID_NULL_PTR;
1435 }
1436
1437 if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED
1438 || dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
1439 return ERROR_JPEGR_INVALID_COLORGAMUT;
1440 }
1441
1442 ColorTransformFn conversionFn = nullptr;
1443 switch (src_encoding) {
1444 case ULTRAHDR_COLORGAMUT_BT709:
1445 switch (dest_encoding) {
1446 case ULTRAHDR_COLORGAMUT_BT709:
1447 return NO_ERROR;
1448 case ULTRAHDR_COLORGAMUT_P3:
1449 conversionFn = yuv709To601;
1450 break;
1451 case ULTRAHDR_COLORGAMUT_BT2100:
1452 conversionFn = yuv709To2100;
1453 break;
1454 default:
1455 // Should be impossible to hit after input validation
1456 return ERROR_JPEGR_INVALID_COLORGAMUT;
1457 }
1458 break;
1459 case ULTRAHDR_COLORGAMUT_P3:
1460 switch (dest_encoding) {
1461 case ULTRAHDR_COLORGAMUT_BT709:
1462 conversionFn = yuv601To709;
1463 break;
1464 case ULTRAHDR_COLORGAMUT_P3:
1465 return NO_ERROR;
1466 case ULTRAHDR_COLORGAMUT_BT2100:
1467 conversionFn = yuv601To2100;
1468 break;
1469 default:
1470 // Should be impossible to hit after input validation
1471 return ERROR_JPEGR_INVALID_COLORGAMUT;
1472 }
1473 break;
1474 case ULTRAHDR_COLORGAMUT_BT2100:
1475 switch (dest_encoding) {
1476 case ULTRAHDR_COLORGAMUT_BT709:
1477 conversionFn = yuv2100To709;
1478 break;
1479 case ULTRAHDR_COLORGAMUT_P3:
1480 conversionFn = yuv2100To601;
1481 break;
1482 case ULTRAHDR_COLORGAMUT_BT2100:
1483 return NO_ERROR;
1484 default:
1485 // Should be impossible to hit after input validation
1486 return ERROR_JPEGR_INVALID_COLORGAMUT;
1487 }
1488 break;
1489 default:
1490 // Should be impossible to hit after input validation
1491 return ERROR_JPEGR_INVALID_COLORGAMUT;
1492 }
1493
1494 if (conversionFn == nullptr) {
1495 // Should be impossible to hit after input validation
1496 return ERROR_JPEGR_INVALID_COLORGAMUT;
1497 }
1498
1499 for (size_t y = 0; y < image->height / 2; ++y) {
1500 for (size_t x = 0; x < image->width / 2; ++x) {
1501 transformYuv420(image, x, y, conversionFn);
1502 }
1503 }
1504
1505 return NO_ERROR;
1506}
1507
Dichen Zhangdbceb0e2023-04-14 19:03:18 +00001508} // namespace android::ultrahdr