/src/libultrahdr/lib/src/jpegr.cpp
Line | Count | Source |
1 | | /* |
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 | | |
17 | | #ifdef _WIN32 |
18 | | #include <windows.h> |
19 | | #include <sysinfoapi.h> |
20 | | #else |
21 | | #include <unistd.h> |
22 | | #endif |
23 | | |
24 | | #include <condition_variable> |
25 | | #include <deque> |
26 | | #include <functional> |
27 | | #include <mutex> |
28 | | #include <thread> |
29 | | |
30 | | #include "ultrahdr/editorhelper.h" |
31 | | #include "ultrahdr/gainmapmetadata.h" |
32 | | #include "ultrahdr/ultrahdrcommon.h" |
33 | | #include "ultrahdr/jpegr.h" |
34 | | #include "ultrahdr/icc.h" |
35 | | #include "ultrahdr/multipictureformat.h" |
36 | | |
37 | | #include "image_io/base/data_segment_data_source.h" |
38 | | #include "image_io/jpeg/jpeg_info.h" |
39 | | #include "image_io/jpeg/jpeg_info_builder.h" |
40 | | #include "image_io/jpeg/jpeg_marker.h" |
41 | | #include "image_io/jpeg/jpeg_scanner.h" |
42 | | |
43 | | using namespace std; |
44 | | using namespace photos_editing_formats::image_io; |
45 | | |
46 | | namespace ultrahdr { |
47 | | |
48 | | #ifdef UHDR_ENABLE_GLES |
49 | | uhdr_error_info_t applyGainMapGLES(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img, |
50 | | uhdr_gainmap_metadata_ext_t* gainmap_metadata, |
51 | | uhdr_color_transfer_t output_ct, float display_boost, |
52 | | uhdr_color_gamut_t sdr_cg, uhdr_color_gamut_t hdr_cg, |
53 | | uhdr_opengl_ctxt_t* opengl_ctxt); |
54 | | #endif |
55 | | |
56 | | // Gain map metadata |
57 | | #ifdef UHDR_WRITE_XMP |
58 | | static const bool kWriteXmpMetadata = true; |
59 | | #else |
60 | | static const bool kWriteXmpMetadata = false; |
61 | | #endif |
62 | | #ifdef UHDR_WRITE_ISO |
63 | | static const bool kWriteIso21496_1Metadata = true; |
64 | | #else |
65 | | static const bool kWriteIso21496_1Metadata = false; |
66 | | #endif |
67 | | |
68 | | static const string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/"; |
69 | | static const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1"; |
70 | | |
71 | | static_assert(kWriteXmpMetadata || kWriteIso21496_1Metadata, |
72 | | "Must write gain map metadata in XMP format, or iso 21496-1 format, or both."); |
73 | | |
74 | | class JobQueue { |
75 | | public: |
76 | | bool dequeueJob(unsigned int& rowStart, unsigned int& rowEnd); |
77 | | void enqueueJob(unsigned int rowStart, unsigned int rowEnd); |
78 | | void markQueueForEnd(); |
79 | | void reset(); |
80 | | |
81 | | private: |
82 | | bool mQueuedAllJobs = false; |
83 | | std::deque<std::tuple<unsigned int, unsigned int>> mJobs; |
84 | | std::mutex mMutex; |
85 | | std::condition_variable mCv; |
86 | | }; |
87 | | |
88 | 750k | bool JobQueue::dequeueJob(unsigned int& rowStart, unsigned int& rowEnd) { |
89 | 750k | std::unique_lock<std::mutex> lock{mMutex}; |
90 | 766k | while (true) { |
91 | 766k | if (mJobs.empty()) { |
92 | 4.93k | if (mQueuedAllJobs) { |
93 | 4.34k | return false; |
94 | 4.34k | } else { |
95 | 586 | mCv.wait_for(lock, std::chrono::milliseconds(100)); |
96 | 586 | } |
97 | 761k | } else { |
98 | 761k | auto it = mJobs.begin(); |
99 | 761k | rowStart = std::get<0>(*it); |
100 | 761k | rowEnd = std::get<1>(*it); |
101 | 761k | mJobs.erase(it); |
102 | 761k | return true; |
103 | 761k | } |
104 | 766k | } |
105 | 18.4E | return false; |
106 | 750k | } |
107 | | |
108 | 761k | void JobQueue::enqueueJob(unsigned int rowStart, unsigned int rowEnd) { |
109 | 761k | std::unique_lock<std::mutex> lock{mMutex}; |
110 | 761k | mJobs.push_back(std::make_tuple(rowStart, rowEnd)); |
111 | 761k | lock.unlock(); |
112 | 761k | mCv.notify_one(); |
113 | 761k | } |
114 | | |
115 | 1.08k | void JobQueue::markQueueForEnd() { |
116 | 1.08k | std::unique_lock<std::mutex> lock{mMutex}; |
117 | 1.08k | mQueuedAllJobs = true; |
118 | 1.08k | lock.unlock(); |
119 | 1.08k | mCv.notify_all(); |
120 | 1.08k | } |
121 | | |
122 | 0 | void JobQueue::reset() { |
123 | 0 | std::unique_lock<std::mutex> lock{mMutex}; |
124 | 0 | mJobs.clear(); |
125 | 0 | mQueuedAllJobs = false; |
126 | 0 | } |
127 | | |
128 | | /* |
129 | | * MessageWriter implementation for ALOG functions. |
130 | | */ |
131 | | class AlogMessageWriter : public MessageWriter { |
132 | | public: |
133 | 350 | void WriteMessage(const Message& message) override { |
134 | 350 | std::string log = GetFormattedMessage(message); |
135 | 350 | ALOGD("%s", log.c_str()); |
136 | 350 | } |
137 | | }; |
138 | | |
139 | 1.08k | unsigned int GetCPUCoreCount() { return (std::max)(1u, std::thread::hardware_concurrency()); } |
140 | | |
141 | | JpegR::JpegR(void* uhdrGLESCtxt, int mapDimensionScaleFactor, int mapCompressQuality, |
142 | | bool useMultiChannelGainMap, float gamma, uhdr_enc_preset_t preset, |
143 | 29.6k | float minContentBoost, float maxContentBoost, float targetDispPeakBrightness) { |
144 | 29.6k | mUhdrGLESCtxt = uhdrGLESCtxt; |
145 | 29.6k | mMapDimensionScaleFactor = mapDimensionScaleFactor; |
146 | 29.6k | mMapCompressQuality = mapCompressQuality; |
147 | 29.6k | mUseMultiChannelGainMap = useMultiChannelGainMap; |
148 | 29.6k | mGamma = gamma; |
149 | 29.6k | mEncPreset = preset; |
150 | 29.6k | mMinContentBoost = minContentBoost; |
151 | 29.6k | mMaxContentBoost = maxContentBoost; |
152 | 29.6k | mTargetDispPeakBrightness = targetDispPeakBrightness; |
153 | 29.6k | } |
154 | | |
155 | | /* |
156 | | * Helper function copies the JPEG image from without EXIF. |
157 | | * |
158 | | * @param pDest destination of the data to be written. |
159 | | * @param pSource source of data being written. |
160 | | * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos(). |
161 | | * (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>). |
162 | | * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize(). |
163 | | */ |
164 | | static void copyJpegWithoutExif(uhdr_compressed_image_t* pDest, uhdr_compressed_image_t* pSource, |
165 | 0 | size_t exif_pos, size_t exif_size) { |
166 | 0 | const size_t exif_offset = 4; // exif_pos has 4 bytes offset to the FF sign |
167 | 0 | pDest->data_sz = pSource->data_sz - exif_size - exif_offset; |
168 | 0 | pDest->data = new uint8_t[pDest->data_sz]; |
169 | 0 | pDest->capacity = pDest->data_sz; |
170 | 0 | pDest->cg = pSource->cg; |
171 | 0 | pDest->ct = pSource->ct; |
172 | 0 | pDest->range = pSource->range; |
173 | 0 | memcpy(pDest->data, pSource->data, exif_pos - exif_offset); |
174 | 0 | memcpy((uint8_t*)pDest->data + exif_pos - exif_offset, |
175 | 0 | (uint8_t*)pSource->data + exif_pos + exif_size, pSource->data_sz - exif_pos - exif_size); |
176 | 0 | } |
177 | | |
178 | | /* Encode API-0 */ |
179 | | uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest, |
180 | 0 | int quality, uhdr_mem_block_t* exif) { |
181 | 0 | uhdr_img_fmt_t sdr_intent_fmt; |
182 | 0 | if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { |
183 | 0 | sdr_intent_fmt = UHDR_IMG_FMT_12bppYCbCr420; |
184 | 0 | } else if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444) { |
185 | 0 | sdr_intent_fmt = UHDR_IMG_FMT_24bppYCbCr444; |
186 | 0 | } else if (hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || |
187 | 0 | hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { |
188 | 0 | sdr_intent_fmt = UHDR_IMG_FMT_32bppRGBA8888; |
189 | 0 | } else { |
190 | 0 | uhdr_error_info_t status; |
191 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
192 | 0 | status.has_detail = 1; |
193 | 0 | snprintf(status.detail, sizeof status.detail, "unsupported hdr intent color format %d", |
194 | 0 | hdr_intent->fmt); |
195 | 0 | return status; |
196 | 0 | } |
197 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent = std::make_unique<uhdr_raw_image_ext_t>( |
198 | 0 | sdr_intent_fmt, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, hdr_intent->w, |
199 | 0 | hdr_intent->h, 64); |
200 | | |
201 | | // tone map |
202 | 0 | UHDR_ERR_CHECK(toneMap(hdr_intent, sdr_intent.get())); |
203 | | |
204 | | // If hdr intent is tonemapped internally, it is observed from quality pov, |
205 | | // generateGainMapOnePass() is sufficient |
206 | 0 | mEncPreset = UHDR_USAGE_REALTIME; // overriding the config option |
207 | | |
208 | | // generate gain map |
209 | 0 | uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); |
210 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> gainmap; |
211 | 0 | UHDR_ERR_CHECK(generateGainMap(sdr_intent.get(), hdr_intent, &metadata, gainmap, |
212 | 0 | /* sdr_is_601 */ false, |
213 | 0 | /* use_luminance */ false)); |
214 | | |
215 | | // compress gain map |
216 | 0 | JpegEncoderHelper jpeg_enc_obj_gm; |
217 | 0 | UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); |
218 | 0 | uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); |
219 | |
|
220 | 0 | std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg); |
221 | | |
222 | | // compress sdr image |
223 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext; |
224 | 0 | uhdr_raw_image_t* sdr_intent_yuv = sdr_intent.get(); |
225 | 0 | if (isPixelFormatRgb(sdr_intent->fmt)) { |
226 | | #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) |
227 | | sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent.get()); |
228 | | #else |
229 | 0 | sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent.get()); |
230 | 0 | #endif |
231 | 0 | sdr_intent_yuv = sdr_intent_yuv_ext.get(); |
232 | 0 | } |
233 | |
|
234 | 0 | JpegEncoderHelper jpeg_enc_obj_sdr; |
235 | 0 | UHDR_ERR_CHECK( |
236 | 0 | jpeg_enc_obj_sdr.compressImage(sdr_intent_yuv, quality, icc->getData(), icc->getLength())); |
237 | 0 | uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage(); |
238 | 0 | sdr_intent_compressed.cg = sdr_intent_yuv->cg; |
239 | | |
240 | | // append gain map, no ICC since JPEG encode already did it |
241 | 0 | UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr, |
242 | 0 | /* icc size */ 0, &metadata, dest)); |
243 | 0 | return g_no_error; |
244 | 0 | } |
245 | | |
246 | | /* Encode API-1 */ |
247 | | uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, |
248 | | uhdr_compressed_image_t* dest, int quality, |
249 | 0 | uhdr_mem_block_t* exif) { |
250 | | // generate gain map |
251 | 0 | uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); |
252 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> gainmap; |
253 | 0 | UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap)); |
254 | | |
255 | | // compress gain map |
256 | 0 | JpegEncoderHelper jpeg_enc_obj_gm; |
257 | 0 | UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); |
258 | 0 | uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); |
259 | |
|
260 | 0 | std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg); |
261 | |
|
262 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> sdr_intent_yuv_ext; |
263 | 0 | uhdr_raw_image_t* sdr_intent_yuv = sdr_intent; |
264 | 0 | if (isPixelFormatRgb(sdr_intent->fmt)) { |
265 | | #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) |
266 | | sdr_intent_yuv_ext = convert_raw_input_to_ycbcr_neon(sdr_intent); |
267 | | #else |
268 | 0 | sdr_intent_yuv_ext = convert_raw_input_to_ycbcr(sdr_intent); |
269 | 0 | #endif |
270 | 0 | sdr_intent_yuv = sdr_intent_yuv_ext.get(); |
271 | 0 | } |
272 | | |
273 | | // convert to bt601 YUV encoding for JPEG encode |
274 | | #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) |
275 | | UHDR_ERR_CHECK(convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3)); |
276 | | #else |
277 | 0 | UHDR_ERR_CHECK(convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, UHDR_CG_DISPLAY_P3)); |
278 | 0 | #endif |
279 | | |
280 | | // compress sdr image |
281 | 0 | JpegEncoderHelper jpeg_enc_obj_sdr; |
282 | 0 | UHDR_ERR_CHECK( |
283 | 0 | jpeg_enc_obj_sdr.compressImage(sdr_intent_yuv, quality, icc->getData(), icc->getLength())); |
284 | 0 | uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage(); |
285 | 0 | sdr_intent_compressed.cg = sdr_intent_yuv->cg; |
286 | | |
287 | | // append gain map, no ICC since JPEG encode already did it |
288 | 0 | UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr, |
289 | 0 | /* icc size */ 0, &metadata, dest)); |
290 | 0 | return g_no_error; |
291 | 0 | } |
292 | | |
293 | | /* Encode API-2 */ |
294 | | uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, |
295 | | uhdr_compressed_image_t* sdr_intent_compressed, |
296 | 0 | uhdr_compressed_image_t* dest) { |
297 | 0 | JpegDecoderHelper jpeg_dec_obj_sdr; |
298 | 0 | UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data, |
299 | 0 | sdr_intent_compressed->data_sz, PARSE_STREAM)); |
300 | 0 | if (hdr_intent->w != jpeg_dec_obj_sdr.getDecompressedImageWidth() || |
301 | 0 | hdr_intent->h != jpeg_dec_obj_sdr.getDecompressedImageHeight()) { |
302 | 0 | uhdr_error_info_t status; |
303 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
304 | 0 | status.has_detail = 1; |
305 | 0 | snprintf( |
306 | 0 | status.detail, sizeof status.detail, |
307 | 0 | "sdr intent resolution %dx%d and compressed image sdr intent resolution %dx%d do not match", |
308 | 0 | sdr_intent->w, sdr_intent->h, (int)jpeg_dec_obj_sdr.getDecompressedImageWidth(), |
309 | 0 | (int)jpeg_dec_obj_sdr.getDecompressedImageHeight()); |
310 | 0 | return status; |
311 | 0 | } |
312 | | |
313 | | // generate gain map |
314 | 0 | uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); |
315 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> gainmap; |
316 | 0 | UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap)); |
317 | | |
318 | | // compress gain map |
319 | 0 | JpegEncoderHelper jpeg_enc_obj_gm; |
320 | 0 | UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); |
321 | 0 | uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); |
322 | |
|
323 | 0 | return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest); |
324 | 0 | } |
325 | | |
326 | | /* Encode API-3 */ |
327 | | uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, |
328 | | uhdr_compressed_image_t* sdr_intent_compressed, |
329 | 0 | uhdr_compressed_image_t* dest) { |
330 | | // decode input jpeg, gamut is going to be bt601. |
331 | 0 | JpegDecoderHelper jpeg_dec_obj_sdr; |
332 | 0 | UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data, |
333 | 0 | sdr_intent_compressed->data_sz)); |
334 | |
|
335 | 0 | uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage(); |
336 | 0 | if (jpeg_dec_obj_sdr.getICCSize() > 0) { |
337 | 0 | uhdr_color_gamut_t cg = |
338 | 0 | IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize()); |
339 | 0 | if (cg == UHDR_CG_UNSPECIFIED || |
340 | 0 | (sdr_intent_compressed->cg != UHDR_CG_UNSPECIFIED && sdr_intent_compressed->cg != cg)) { |
341 | 0 | uhdr_error_info_t status; |
342 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
343 | 0 | status.has_detail = 1; |
344 | 0 | snprintf(status.detail, sizeof status.detail, |
345 | 0 | "configured color gamut %d does not match with color gamut specified in icc box %d", |
346 | 0 | sdr_intent_compressed->cg, cg); |
347 | 0 | return status; |
348 | 0 | } |
349 | 0 | sdr_intent.cg = cg; |
350 | 0 | } else { |
351 | 0 | if (sdr_intent_compressed->cg <= UHDR_CG_UNSPECIFIED || |
352 | 0 | sdr_intent_compressed->cg > UHDR_CG_BT_2100) { |
353 | 0 | uhdr_error_info_t status; |
354 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
355 | 0 | status.has_detail = 1; |
356 | 0 | snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d", |
357 | 0 | sdr_intent_compressed->cg); |
358 | 0 | return status; |
359 | 0 | } |
360 | 0 | sdr_intent.cg = sdr_intent_compressed->cg; |
361 | 0 | } |
362 | | |
363 | 0 | if (hdr_intent->w != sdr_intent.w || hdr_intent->h != sdr_intent.h) { |
364 | 0 | uhdr_error_info_t status; |
365 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
366 | 0 | status.has_detail = 1; |
367 | 0 | snprintf(status.detail, sizeof status.detail, |
368 | 0 | "sdr intent resolution %dx%d and hdr intent resolution %dx%d do not match", |
369 | 0 | sdr_intent.w, sdr_intent.h, hdr_intent->w, hdr_intent->h); |
370 | 0 | return status; |
371 | 0 | } |
372 | | |
373 | | // generate gain map |
374 | 0 | uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); |
375 | 0 | std::unique_ptr<uhdr_raw_image_ext_t> gainmap; |
376 | 0 | UHDR_ERR_CHECK( |
377 | 0 | generateGainMap(&sdr_intent, hdr_intent, &metadata, gainmap, true /* sdr_is_601 */)); |
378 | | |
379 | | // compress gain map |
380 | 0 | JpegEncoderHelper jpeg_enc_obj_gm; |
381 | 0 | UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); |
382 | 0 | uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); |
383 | |
|
384 | 0 | return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest); |
385 | 0 | } |
386 | | |
387 | | /* Encode API-4 */ |
388 | | uhdr_error_info_t JpegR::encodeJPEGR(uhdr_compressed_image_t* base_img_compressed, |
389 | | uhdr_compressed_image_t* gainmap_img_compressed, |
390 | | uhdr_gainmap_metadata_ext_t* metadata, |
391 | 0 | uhdr_compressed_image_t* dest) { |
392 | | // We just want to check if ICC is present, so don't do a full decode. Note, |
393 | | // this doesn't verify that the ICC is valid. |
394 | 0 | JpegDecoderHelper decoder; |
395 | 0 | UHDR_ERR_CHECK(decoder.parseImage(base_img_compressed->data, base_img_compressed->data_sz)); |
396 | |
|
397 | 0 | if (!metadata->use_base_cg) { |
398 | 0 | JpegDecoderHelper gainmap_decoder; |
399 | 0 | UHDR_ERR_CHECK( |
400 | 0 | gainmap_decoder.parseImage(gainmap_img_compressed->data, gainmap_img_compressed->data_sz)); |
401 | 0 | if (!(gainmap_decoder.getICCSize() > 0)) { |
402 | 0 | uhdr_error_info_t status; |
403 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
404 | 0 | status.has_detail = 1; |
405 | 0 | snprintf(status.detail, sizeof status.detail, |
406 | 0 | "For gainmap application space to be alternate image space, gainmap image is " |
407 | 0 | "expected to contain alternate image color space in the form of ICC. The ICC marker " |
408 | 0 | "in gainmap jpeg is missing."); |
409 | 0 | return status; |
410 | 0 | } |
411 | 0 | } |
412 | | |
413 | | // Add ICC if not already present. |
414 | 0 | if (decoder.getICCSize() > 0) { |
415 | 0 | UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr, |
416 | 0 | /* icc */ nullptr, /* icc size */ 0, metadata, dest)); |
417 | 0 | } else { |
418 | 0 | if (base_img_compressed->cg <= UHDR_CG_UNSPECIFIED || |
419 | 0 | base_img_compressed->cg > UHDR_CG_BT_2100) { |
420 | 0 | uhdr_error_info_t status; |
421 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
422 | 0 | status.has_detail = 1; |
423 | 0 | snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d", |
424 | 0 | base_img_compressed->cg); |
425 | 0 | return status; |
426 | 0 | } |
427 | 0 | std::shared_ptr<DataStruct> newIcc = |
428 | 0 | IccHelper::writeIccProfile(UHDR_CT_SRGB, base_img_compressed->cg); |
429 | 0 | UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr, |
430 | 0 | newIcc->getData(), newIcc->getLength(), metadata, dest)); |
431 | 0 | } |
432 | | |
433 | 0 | return g_no_error; |
434 | 0 | } |
435 | | |
436 | | uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding, |
437 | 0 | uhdr_color_gamut_t dst_encoding) { |
438 | 0 | const std::array<float, 9>* coeffs_ptr = nullptr; |
439 | 0 | uhdr_error_info_t status = g_no_error; |
440 | |
|
441 | 0 | switch (src_encoding) { |
442 | 0 | case UHDR_CG_BT_709: |
443 | 0 | switch (dst_encoding) { |
444 | 0 | case UHDR_CG_BT_709: |
445 | 0 | return status; |
446 | 0 | case UHDR_CG_DISPLAY_P3: |
447 | 0 | coeffs_ptr = &kYuvBt709ToBt601; |
448 | 0 | break; |
449 | 0 | case UHDR_CG_BT_2100: |
450 | 0 | coeffs_ptr = &kYuvBt709ToBt2100; |
451 | 0 | break; |
452 | 0 | default: |
453 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
454 | 0 | status.has_detail = 1; |
455 | 0 | snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", |
456 | 0 | dst_encoding); |
457 | 0 | return status; |
458 | 0 | } |
459 | 0 | break; |
460 | 0 | case UHDR_CG_DISPLAY_P3: |
461 | 0 | switch (dst_encoding) { |
462 | 0 | case UHDR_CG_BT_709: |
463 | 0 | coeffs_ptr = &kYuvBt601ToBt709; |
464 | 0 | break; |
465 | 0 | case UHDR_CG_DISPLAY_P3: |
466 | 0 | return status; |
467 | 0 | case UHDR_CG_BT_2100: |
468 | 0 | coeffs_ptr = &kYuvBt601ToBt2100; |
469 | 0 | break; |
470 | 0 | default: |
471 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
472 | 0 | status.has_detail = 1; |
473 | 0 | snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", |
474 | 0 | dst_encoding); |
475 | 0 | return status; |
476 | 0 | } |
477 | 0 | break; |
478 | 0 | case UHDR_CG_BT_2100: |
479 | 0 | switch (dst_encoding) { |
480 | 0 | case UHDR_CG_BT_709: |
481 | 0 | coeffs_ptr = &kYuvBt2100ToBt709; |
482 | 0 | break; |
483 | 0 | case UHDR_CG_DISPLAY_P3: |
484 | 0 | coeffs_ptr = &kYuvBt2100ToBt601; |
485 | 0 | break; |
486 | 0 | case UHDR_CG_BT_2100: |
487 | 0 | return status; |
488 | 0 | default: |
489 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
490 | 0 | status.has_detail = 1; |
491 | 0 | snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", |
492 | 0 | dst_encoding); |
493 | 0 | return status; |
494 | 0 | } |
495 | 0 | break; |
496 | 0 | default: |
497 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
498 | 0 | status.has_detail = 1; |
499 | 0 | snprintf(status.detail, sizeof status.detail, "Unrecognized src color gamut %d", |
500 | 0 | src_encoding); |
501 | 0 | return status; |
502 | 0 | } |
503 | | |
504 | 0 | if (image->fmt == UHDR_IMG_FMT_12bppYCbCr420) { |
505 | 0 | transformYuv420(image, *coeffs_ptr); |
506 | 0 | } else if (image->fmt == UHDR_IMG_FMT_24bppYCbCr444) { |
507 | 0 | transformYuv444(image, *coeffs_ptr); |
508 | 0 | } else { |
509 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
510 | 0 | status.has_detail = 1; |
511 | 0 | snprintf(status.detail, sizeof status.detail, |
512 | 0 | "No implementation available for performing gamut conversion for color format %d", |
513 | 0 | image->fmt); |
514 | 0 | return status; |
515 | 0 | } |
516 | | |
517 | 0 | return status; |
518 | 0 | } |
519 | | |
520 | | uhdr_error_info_t JpegR::compressGainMap(uhdr_raw_image_t* gainmap_img, |
521 | 0 | JpegEncoderHelper* jpeg_enc_obj) { |
522 | 0 | if (!kWriteXmpMetadata) { |
523 | 0 | std::shared_ptr<DataStruct> icc = IccHelper::writeIccProfile(gainmap_img->ct, gainmap_img->cg); |
524 | 0 | return jpeg_enc_obj->compressImage(gainmap_img, mMapCompressQuality, icc->getData(), |
525 | 0 | icc->getLength()); |
526 | 0 | } |
527 | 0 | return jpeg_enc_obj->compressImage(gainmap_img, mMapCompressQuality, nullptr, 0); |
528 | 0 | } |
529 | | |
530 | | uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* hdr_intent, |
531 | | uhdr_gainmap_metadata_ext_t* gainmap_metadata, |
532 | | std::unique_ptr<uhdr_raw_image_ext_t>& gainmap_img, |
533 | 0 | bool sdr_is_601, bool use_luminance) { |
534 | 0 | uhdr_error_info_t status = g_no_error; |
535 | |
|
536 | 0 | if (sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444 && |
537 | 0 | sdr_intent->fmt != UHDR_IMG_FMT_16bppYCbCr422 && |
538 | 0 | sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420 && |
539 | 0 | sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) { |
540 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
541 | 0 | status.has_detail = 1; |
542 | 0 | snprintf(status.detail, sizeof status.detail, |
543 | 0 | "generate gainmap method expects sdr intent color format to be one of " |
544 | 0 | "{UHDR_IMG_FMT_24bppYCbCr444, UHDR_IMG_FMT_16bppYCbCr422, " |
545 | 0 | "UHDR_IMG_FMT_12bppYCbCr420, UHDR_IMG_FMT_32bppRGBA8888}. Received %d", |
546 | 0 | sdr_intent->fmt); |
547 | 0 | return status; |
548 | 0 | } |
549 | 0 | if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 && |
550 | 0 | hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 && |
551 | 0 | hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 && |
552 | 0 | hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) { |
553 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
554 | 0 | status.has_detail = 1; |
555 | 0 | snprintf(status.detail, sizeof status.detail, |
556 | 0 | "generate gainmap method expects hdr intent color format to be one of " |
557 | 0 | "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, " |
558 | 0 | "UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d", |
559 | 0 | hdr_intent->fmt); |
560 | 0 | return status; |
561 | 0 | } |
562 | | |
563 | 0 | ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct); |
564 | 0 | if (hdrInvOetf == nullptr) { |
565 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
566 | 0 | status.has_detail = 1; |
567 | 0 | snprintf(status.detail, sizeof status.detail, |
568 | 0 | "No implementation available for converting transfer characteristics %d to linear", |
569 | 0 | hdr_intent->ct); |
570 | 0 | return status; |
571 | 0 | } |
572 | | |
573 | 0 | LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg); |
574 | 0 | if (hdrLuminanceFn == nullptr) { |
575 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
576 | 0 | status.has_detail = 1; |
577 | 0 | snprintf(status.detail, sizeof status.detail, |
578 | 0 | "No implementation available for calculating luminance for color gamut %d", |
579 | 0 | hdr_intent->cg); |
580 | 0 | return status; |
581 | 0 | } |
582 | | |
583 | 0 | SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct); |
584 | 0 | if (hdrOotfFn == nullptr) { |
585 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
586 | 0 | status.has_detail = 1; |
587 | 0 | snprintf(status.detail, sizeof status.detail, |
588 | 0 | "No implementation available for calculating Ootf for color transfer %d", |
589 | 0 | hdr_intent->ct); |
590 | 0 | return status; |
591 | 0 | } |
592 | | |
593 | 0 | float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct); |
594 | 0 | if (hdr_white_nits == -1.0f) { |
595 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
596 | 0 | status.has_detail = 1; |
597 | 0 | snprintf(status.detail, sizeof status.detail, |
598 | 0 | "received invalid peak brightness %f nits for hdr reference display with color " |
599 | 0 | "transfer %d ", |
600 | 0 | hdr_white_nits, hdr_intent->ct); |
601 | 0 | return status; |
602 | 0 | } |
603 | | |
604 | 0 | ColorTransformFn hdrGamutConversionFn; |
605 | 0 | ColorTransformFn sdrGamutConversionFn; |
606 | 0 | bool use_sdr_cg = true; |
607 | 0 | if (sdr_intent->cg != hdr_intent->cg) { |
608 | 0 | use_sdr_cg = kWriteXmpMetadata || |
609 | 0 | !(hdr_intent->cg == UHDR_CG_BT_2100 || |
610 | 0 | (hdr_intent->cg == UHDR_CG_DISPLAY_P3 && sdr_intent->cg != UHDR_CG_BT_2100)); |
611 | 0 | if (use_sdr_cg) { |
612 | 0 | hdrGamutConversionFn = getGamutConversionFn(sdr_intent->cg, hdr_intent->cg); |
613 | 0 | if (hdrGamutConversionFn == nullptr) { |
614 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
615 | 0 | status.has_detail = 1; |
616 | 0 | snprintf(status.detail, sizeof status.detail, |
617 | 0 | "No implementation available for gamut conversion from %d to %d", hdr_intent->cg, |
618 | 0 | sdr_intent->cg); |
619 | 0 | return status; |
620 | 0 | } |
621 | 0 | sdrGamutConversionFn = identityConversion; |
622 | 0 | } else { |
623 | 0 | hdrGamutConversionFn = identityConversion; |
624 | 0 | sdrGamutConversionFn = getGamutConversionFn(hdr_intent->cg, sdr_intent->cg); |
625 | 0 | if (sdrGamutConversionFn == nullptr) { |
626 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
627 | 0 | status.has_detail = 1; |
628 | 0 | snprintf(status.detail, sizeof status.detail, |
629 | 0 | "No implementation available for gamut conversion from %d to %d", sdr_intent->cg, |
630 | 0 | hdr_intent->cg); |
631 | 0 | return status; |
632 | 0 | } |
633 | 0 | } |
634 | 0 | } else { |
635 | 0 | hdrGamutConversionFn = sdrGamutConversionFn = identityConversion; |
636 | 0 | } |
637 | 0 | gainmap_metadata->use_base_cg = use_sdr_cg; |
638 | |
|
639 | 0 | ColorTransformFn sdrYuvToRgbFn = getYuvToRgbFn(sdr_intent->cg); |
640 | 0 | if (sdrYuvToRgbFn == nullptr) { |
641 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
642 | 0 | status.has_detail = 1; |
643 | 0 | snprintf(status.detail, sizeof status.detail, |
644 | 0 | "No implementation available for converting yuv to rgb for color gamut %d", |
645 | 0 | sdr_intent->cg); |
646 | 0 | return status; |
647 | 0 | } |
648 | | |
649 | 0 | ColorTransformFn hdrYuvToRgbFn = getYuvToRgbFn(hdr_intent->cg); |
650 | 0 | if (hdrYuvToRgbFn == nullptr) { |
651 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
652 | 0 | status.has_detail = 1; |
653 | 0 | snprintf(status.detail, sizeof status.detail, |
654 | 0 | "No implementation available for converting yuv to rgb for color gamut %d", |
655 | 0 | hdr_intent->cg); |
656 | 0 | return status; |
657 | 0 | } |
658 | | |
659 | 0 | LuminanceFn luminanceFn = getLuminanceFn(sdr_intent->cg); |
660 | 0 | if (luminanceFn == nullptr) { |
661 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
662 | 0 | status.has_detail = 1; |
663 | 0 | snprintf(status.detail, sizeof status.detail, |
664 | 0 | "No implementation available for computing luminance for color gamut %d", |
665 | 0 | sdr_intent->cg); |
666 | 0 | return status; |
667 | 0 | } |
668 | | |
669 | 0 | SamplePixelFn sdr_sample_pixel_fn = getSamplePixelFn(sdr_intent->fmt); |
670 | 0 | if (sdr_sample_pixel_fn == nullptr) { |
671 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
672 | 0 | status.has_detail = 1; |
673 | 0 | snprintf(status.detail, sizeof status.detail, |
674 | 0 | "No implementation available for reading pixels for color format %d", sdr_intent->fmt); |
675 | 0 | return status; |
676 | 0 | } |
677 | | |
678 | 0 | SamplePixelFn hdr_sample_pixel_fn = getSamplePixelFn(hdr_intent->fmt); |
679 | 0 | if (hdr_sample_pixel_fn == nullptr) { |
680 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
681 | 0 | status.has_detail = 1; |
682 | 0 | snprintf(status.detail, sizeof status.detail, |
683 | 0 | "No implementation available for reading pixels for color format %d", hdr_intent->fmt); |
684 | 0 | return status; |
685 | 0 | } |
686 | | |
687 | 0 | if (sdr_is_601) { |
688 | 0 | sdrYuvToRgbFn = p3YuvToRgb; |
689 | 0 | } |
690 | |
|
691 | 0 | unsigned int image_width = sdr_intent->w; |
692 | 0 | unsigned int image_height = sdr_intent->h; |
693 | 0 | unsigned int map_width = image_width / mMapDimensionScaleFactor; |
694 | 0 | unsigned int map_height = image_height / mMapDimensionScaleFactor; |
695 | 0 | if (map_width == 0 || map_height == 0) { |
696 | 0 | int scaleFactor = (std::min)(image_width, image_height); |
697 | 0 | scaleFactor = (scaleFactor >= DCTSIZE) ? (scaleFactor / DCTSIZE) : 1; |
698 | 0 | ALOGW( |
699 | 0 | "configured gainmap scale factor is resulting in gainmap width and/or height to be zero, " |
700 | 0 | "image width %u, image height %u, scale factor %d. Modifying gainmap scale factor to %d ", |
701 | 0 | image_width, image_height, mMapDimensionScaleFactor, scaleFactor); |
702 | 0 | setMapDimensionScaleFactor(scaleFactor); |
703 | 0 | map_width = image_width / mMapDimensionScaleFactor; |
704 | 0 | map_height = image_height / mMapDimensionScaleFactor; |
705 | 0 | } |
706 | | |
707 | | // NOTE: Even though gainmap image raw descriptor is being initialized with hdr intent's color |
708 | | // aspects, one should not associate gainmap image to this color profile. gain map image gamut |
709 | | // space can be hdr intent's or sdr intent's space (a decision made during gainmap generation). |
710 | | // Its color transfer is dependent on the gainmap encoding gamma. The reason to initialize with |
711 | | // hdr color aspects is compressGainMap method will use this to write hdr intent color profile in |
712 | | // the bitstream. |
713 | 0 | gainmap_img = std::make_unique<uhdr_raw_image_ext_t>( |
714 | 0 | mUseMultiChannelGainMap ? UHDR_IMG_FMT_24bppRGB888 : UHDR_IMG_FMT_8bppYCbCr400, |
715 | 0 | hdr_intent->cg, hdr_intent->ct, hdr_intent->range, map_width, map_height, 64); |
716 | 0 | uhdr_raw_image_ext_t* dest = gainmap_img.get(); |
717 | |
|
718 | 0 | auto generateGainMapOnePass = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_height, |
719 | 0 | hdrInvOetf, hdrLuminanceFn, hdrOotfFn, hdrGamutConversionFn, |
720 | 0 | sdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, |
721 | 0 | sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, |
722 | 0 | use_luminance]() -> void { |
723 | 0 | std::fill_n(gainmap_metadata->max_content_boost, 3, hdr_white_nits / kSdrWhiteNits); |
724 | 0 | std::fill_n(gainmap_metadata->min_content_boost, 3, 1.0f); |
725 | 0 | std::fill_n(gainmap_metadata->gamma, 3, mGamma); |
726 | 0 | std::fill_n(gainmap_metadata->offset_sdr, 3, 0.0f); |
727 | 0 | std::fill_n(gainmap_metadata->offset_hdr, 3, 0.0f); |
728 | 0 | gainmap_metadata->hdr_capacity_min = 1.0f; |
729 | 0 | if (this->mTargetDispPeakBrightness != -1.0f) { |
730 | 0 | gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits; |
731 | 0 | } else { |
732 | 0 | gainmap_metadata->hdr_capacity_max = gainmap_metadata->max_content_boost[0]; |
733 | 0 | } |
734 | |
|
735 | 0 | float log2MinBoost = log2(gainmap_metadata->min_content_boost[0]); |
736 | 0 | float log2MaxBoost = log2(gainmap_metadata->max_content_boost[0]); |
737 | |
|
738 | 0 | const int threads = (std::min)(GetCPUCoreCount(), 4u); |
739 | 0 | const int jobSizeInRows = 1; |
740 | 0 | unsigned int rowStep = threads == 1 ? map_height : jobSizeInRows; |
741 | 0 | JobQueue jobQueue; |
742 | 0 | std::function<void()> generateMap = |
743 | 0 | [this, sdr_intent, hdr_intent, gainmap_metadata, dest, hdrInvOetf, hdrLuminanceFn, |
744 | 0 | hdrOotfFn, hdrGamutConversionFn, sdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, |
745 | 0 | hdrYuvToRgbFn, sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, log2MinBoost, |
746 | 0 | log2MaxBoost, use_luminance, &jobQueue]() -> void { |
747 | 0 | unsigned int rowStart, rowEnd; |
748 | 0 | const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt); |
749 | 0 | const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt); |
750 | 0 | const float hdrSampleToNitsFactor = |
751 | 0 | hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits; |
752 | 0 | while (jobQueue.dequeueJob(rowStart, rowEnd)) { |
753 | 0 | for (size_t y = rowStart; y < rowEnd; ++y) { |
754 | 0 | for (size_t x = 0; x < dest->w; ++x) { |
755 | 0 | Color sdr_rgb_gamma; |
756 | |
|
757 | 0 | if (isSdrIntentRgb) { |
758 | 0 | sdr_rgb_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y); |
759 | 0 | } else { |
760 | 0 | Color sdr_yuv_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y); |
761 | 0 | sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); |
762 | 0 | } |
763 | | |
764 | | // We are assuming the SDR input is always sRGB transfer. |
765 | 0 | #if USE_SRGB_INVOETF_LUT |
766 | 0 | Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); |
767 | | #else |
768 | | Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); |
769 | | #endif |
770 | 0 | sdr_rgb = sdrGamutConversionFn(sdr_rgb); |
771 | 0 | sdr_rgb = clipNegatives(sdr_rgb); |
772 | |
|
773 | 0 | Color hdr_rgb_gamma; |
774 | |
|
775 | 0 | if (isHdrIntentRgb) { |
776 | 0 | hdr_rgb_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y); |
777 | 0 | } else { |
778 | 0 | Color hdr_yuv_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y); |
779 | 0 | hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); |
780 | 0 | } |
781 | 0 | Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); |
782 | 0 | hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn); |
783 | 0 | hdr_rgb = hdrGamutConversionFn(hdr_rgb); |
784 | 0 | hdr_rgb = clipNegatives(hdr_rgb); |
785 | |
|
786 | 0 | if (mUseMultiChannelGainMap) { |
787 | 0 | Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits; |
788 | 0 | Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor; |
789 | 0 | size_t pixel_idx = (x + y * dest->stride[UHDR_PLANE_PACKED]) * 3; |
790 | |
|
791 | 0 | reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = encodeGain( |
792 | 0 | sdr_rgb_nits.r, hdr_rgb_nits.r, gainmap_metadata, log2MinBoost, log2MaxBoost, 0); |
793 | 0 | reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 1] = |
794 | 0 | encodeGain(sdr_rgb_nits.g, hdr_rgb_nits.g, gainmap_metadata, log2MinBoost, |
795 | 0 | log2MaxBoost, 1); |
796 | 0 | reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 2] = |
797 | 0 | encodeGain(sdr_rgb_nits.b, hdr_rgb_nits.b, gainmap_metadata, log2MinBoost, |
798 | 0 | log2MaxBoost, 2); |
799 | 0 | } else { |
800 | 0 | float sdr_y_nits; |
801 | 0 | float hdr_y_nits; |
802 | 0 | if (use_luminance) { |
803 | 0 | sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; |
804 | 0 | hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor; |
805 | 0 | } else { |
806 | 0 | sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits; |
807 | 0 | hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor; |
808 | 0 | } |
809 | |
|
810 | 0 | size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_Y]; |
811 | |
|
812 | 0 | reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_Y])[pixel_idx] = encodeGain( |
813 | 0 | sdr_y_nits, hdr_y_nits, gainmap_metadata, log2MinBoost, log2MaxBoost, 0); |
814 | 0 | } |
815 | 0 | } |
816 | 0 | } |
817 | 0 | } |
818 | 0 | }; |
819 | | |
820 | | // generate map |
821 | 0 | std::vector<std::thread> workers; |
822 | 0 | for (int th = 0; th < threads - 1; th++) { |
823 | 0 | workers.push_back(std::thread(generateMap)); |
824 | 0 | } |
825 | |
|
826 | 0 | for (unsigned int rowStart = 0; rowStart < map_height;) { |
827 | 0 | unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height); |
828 | 0 | jobQueue.enqueueJob(rowStart, rowEnd); |
829 | 0 | rowStart = rowEnd; |
830 | 0 | } |
831 | 0 | jobQueue.markQueueForEnd(); |
832 | 0 | generateMap(); |
833 | 0 | std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); |
834 | 0 | }; |
835 | |
|
836 | 0 | auto generateGainMapTwoPass = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, map_width, |
837 | 0 | map_height, hdrInvOetf, hdrLuminanceFn, hdrOotfFn, |
838 | 0 | hdrGamutConversionFn, sdrGamutConversionFn, luminanceFn, |
839 | 0 | sdrYuvToRgbFn, hdrYuvToRgbFn, sdr_sample_pixel_fn, |
840 | 0 | hdr_sample_pixel_fn, hdr_white_nits, use_luminance]() -> void { |
841 | 0 | uhdr_memory_block_t gainmap_mem((size_t)map_width * map_height * sizeof(float) * |
842 | 0 | (mUseMultiChannelGainMap ? 3 : 1)); |
843 | 0 | float* gainmap_data = reinterpret_cast<float*>(gainmap_mem.m_buffer.get()); |
844 | 0 | float gainmap_min[3] = {127.0f, 127.0f, 127.0f}; |
845 | 0 | float gainmap_max[3] = {-128.0f, -128.0f, -128.0f}; |
846 | 0 | std::mutex gainmap_minmax; |
847 | |
|
848 | 0 | const int threads = (std::min)(GetCPUCoreCount(), 4u); |
849 | 0 | const int jobSizeInRows = 1; |
850 | 0 | unsigned int rowStep = threads == 1 ? map_height : jobSizeInRows; |
851 | 0 | JobQueue jobQueue; |
852 | 0 | std::function<void()> generateMap = |
853 | 0 | [this, sdr_intent, hdr_intent, gainmap_data, map_width, hdrInvOetf, hdrLuminanceFn, |
854 | 0 | hdrOotfFn, hdrGamutConversionFn, sdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, |
855 | 0 | hdrYuvToRgbFn, sdr_sample_pixel_fn, hdr_sample_pixel_fn, hdr_white_nits, use_luminance, |
856 | 0 | &gainmap_min, &gainmap_max, &gainmap_minmax, &jobQueue]() -> void { |
857 | 0 | unsigned int rowStart, rowEnd; |
858 | 0 | const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt); |
859 | 0 | const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt); |
860 | 0 | const float hdrSampleToNitsFactor = |
861 | 0 | hdr_intent->ct == UHDR_CT_LINEAR ? kSdrWhiteNits : hdr_white_nits; |
862 | 0 | float gainmap_min_th[3] = {127.0f, 127.0f, 127.0f}; |
863 | 0 | float gainmap_max_th[3] = {-128.0f, -128.0f, -128.0f}; |
864 | |
|
865 | 0 | while (jobQueue.dequeueJob(rowStart, rowEnd)) { |
866 | 0 | for (size_t y = rowStart; y < rowEnd; ++y) { |
867 | 0 | for (size_t x = 0; x < map_width; ++x) { |
868 | 0 | Color sdr_rgb_gamma; |
869 | |
|
870 | 0 | if (isSdrIntentRgb) { |
871 | 0 | sdr_rgb_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y); |
872 | 0 | } else { |
873 | 0 | Color sdr_yuv_gamma = sdr_sample_pixel_fn(sdr_intent, mMapDimensionScaleFactor, x, y); |
874 | 0 | sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); |
875 | 0 | } |
876 | | |
877 | | // We are assuming the SDR input is always sRGB transfer. |
878 | 0 | #if USE_SRGB_INVOETF_LUT |
879 | 0 | Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); |
880 | | #else |
881 | | Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); |
882 | | #endif |
883 | 0 | sdr_rgb = sdrGamutConversionFn(sdr_rgb); |
884 | 0 | sdr_rgb = clipNegatives(sdr_rgb); |
885 | |
|
886 | 0 | Color hdr_rgb_gamma; |
887 | |
|
888 | 0 | if (isHdrIntentRgb) { |
889 | 0 | hdr_rgb_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y); |
890 | 0 | } else { |
891 | 0 | Color hdr_yuv_gamma = hdr_sample_pixel_fn(hdr_intent, mMapDimensionScaleFactor, x, y); |
892 | 0 | hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); |
893 | 0 | } |
894 | 0 | Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); |
895 | 0 | hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn); |
896 | 0 | hdr_rgb = hdrGamutConversionFn(hdr_rgb); |
897 | 0 | hdr_rgb = clipNegatives(hdr_rgb); |
898 | |
|
899 | 0 | if (mUseMultiChannelGainMap) { |
900 | 0 | Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits; |
901 | 0 | Color hdr_rgb_nits = hdr_rgb * hdrSampleToNitsFactor; |
902 | 0 | size_t pixel_idx = (x + y * map_width) * 3; |
903 | |
|
904 | 0 | gainmap_data[pixel_idx] = computeGain(sdr_rgb_nits.r, hdr_rgb_nits.r); |
905 | 0 | gainmap_data[pixel_idx + 1] = computeGain(sdr_rgb_nits.g, hdr_rgb_nits.g); |
906 | 0 | gainmap_data[pixel_idx + 2] = computeGain(sdr_rgb_nits.b, hdr_rgb_nits.b); |
907 | 0 | for (int i = 0; i < 3; i++) { |
908 | 0 | gainmap_min_th[i] = (std::min)(gainmap_data[pixel_idx + i], gainmap_min_th[i]); |
909 | 0 | gainmap_max_th[i] = (std::max)(gainmap_data[pixel_idx + i], gainmap_max_th[i]); |
910 | 0 | } |
911 | 0 | } else { |
912 | 0 | float sdr_y_nits; |
913 | 0 | float hdr_y_nits; |
914 | |
|
915 | 0 | if (use_luminance) { |
916 | 0 | sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; |
917 | 0 | hdr_y_nits = luminanceFn(hdr_rgb) * hdrSampleToNitsFactor; |
918 | 0 | } else { |
919 | 0 | sdr_y_nits = fmax(sdr_rgb.r, fmax(sdr_rgb.g, sdr_rgb.b)) * kSdrWhiteNits; |
920 | 0 | hdr_y_nits = fmax(hdr_rgb.r, fmax(hdr_rgb.g, hdr_rgb.b)) * hdrSampleToNitsFactor; |
921 | 0 | } |
922 | |
|
923 | 0 | size_t pixel_idx = x + y * map_width; |
924 | 0 | gainmap_data[pixel_idx] = computeGain(sdr_y_nits, hdr_y_nits); |
925 | 0 | gainmap_min_th[0] = (std::min)(gainmap_data[pixel_idx], gainmap_min_th[0]); |
926 | 0 | gainmap_max_th[0] = (std::max)(gainmap_data[pixel_idx], gainmap_max_th[0]); |
927 | 0 | } |
928 | 0 | } |
929 | 0 | } |
930 | 0 | } |
931 | 0 | { |
932 | 0 | std::unique_lock<std::mutex> lock{gainmap_minmax}; |
933 | 0 | for (int index = 0; index < (mUseMultiChannelGainMap ? 3 : 1); index++) { |
934 | 0 | gainmap_min[index] = (std::min)(gainmap_min[index], gainmap_min_th[index]); |
935 | 0 | gainmap_max[index] = (std::max)(gainmap_max[index], gainmap_max_th[index]); |
936 | 0 | } |
937 | 0 | } |
938 | 0 | }; |
939 | | |
940 | | // generate map |
941 | 0 | std::vector<std::thread> workers; |
942 | 0 | for (int th = 0; th < threads - 1; th++) { |
943 | 0 | workers.push_back(std::thread(generateMap)); |
944 | 0 | } |
945 | |
|
946 | 0 | for (unsigned int rowStart = 0; rowStart < map_height;) { |
947 | 0 | unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height); |
948 | 0 | jobQueue.enqueueJob(rowStart, rowEnd); |
949 | 0 | rowStart = rowEnd; |
950 | 0 | } |
951 | 0 | jobQueue.markQueueForEnd(); |
952 | 0 | generateMap(); |
953 | 0 | std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); |
954 | | |
955 | | // xmp metadata current implementation does not support writing multichannel metadata |
956 | | // so merge them in to one |
957 | 0 | if (kWriteXmpMetadata) { |
958 | 0 | float min_content_boost_log2 = gainmap_min[0]; |
959 | 0 | float max_content_boost_log2 = gainmap_max[0]; |
960 | 0 | for (int index = 1; index < (mUseMultiChannelGainMap ? 3 : 1); index++) { |
961 | 0 | min_content_boost_log2 = (std::min)(gainmap_min[index], min_content_boost_log2); |
962 | 0 | max_content_boost_log2 = (std::max)(gainmap_max[index], max_content_boost_log2); |
963 | 0 | } |
964 | 0 | std::fill_n(gainmap_min, 3, min_content_boost_log2); |
965 | 0 | std::fill_n(gainmap_max, 3, max_content_boost_log2); |
966 | 0 | } |
967 | |
|
968 | 0 | for (int index = 0; index < (mUseMultiChannelGainMap ? 3 : 1); index++) { |
969 | | // gain coefficient range [-14.3, 15.6] is capable of representing hdr pels from sdr pels. |
970 | | // Allowing further excursion might not offer any benefit and on the downside can cause bigger |
971 | | // error during affine map and inverse affine map. |
972 | 0 | gainmap_min[index] = (std::clamp)(gainmap_min[index], -14.3f, 15.6f); |
973 | 0 | gainmap_max[index] = (std::clamp)(gainmap_max[index], -14.3f, 15.6f); |
974 | 0 | if (this->mMaxContentBoost != FLT_MAX) { |
975 | 0 | float suggestion = log2(this->mMaxContentBoost); |
976 | 0 | gainmap_max[index] = (std::min)(gainmap_max[index], suggestion); |
977 | 0 | } |
978 | 0 | if (this->mMinContentBoost != FLT_MIN) { |
979 | 0 | float suggestion = log2(this->mMinContentBoost); |
980 | 0 | gainmap_min[index] = (std::max)(gainmap_min[index], suggestion); |
981 | 0 | } |
982 | 0 | if (fabs(gainmap_max[index] - gainmap_min[index]) < FLT_EPSILON) { |
983 | 0 | gainmap_max[index] += 0.1f; // to avoid div by zero during affine transform |
984 | 0 | } |
985 | 0 | } |
986 | |
|
987 | 0 | std::function<void()> encodeMap = [this, gainmap_data, map_width, dest, gainmap_min, |
988 | 0 | gainmap_max, &jobQueue]() -> void { |
989 | 0 | unsigned int rowStart, rowEnd; |
990 | |
|
991 | 0 | while (jobQueue.dequeueJob(rowStart, rowEnd)) { |
992 | 0 | if (mUseMultiChannelGainMap) { |
993 | 0 | for (size_t j = rowStart; j < rowEnd; j++) { |
994 | 0 | size_t dst_pixel_idx = j * dest->stride[UHDR_PLANE_PACKED] * 3; |
995 | 0 | size_t src_pixel_idx = j * map_width * 3; |
996 | 0 | for (size_t i = 0; i < map_width * 3; i++) { |
997 | 0 | reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_PACKED])[dst_pixel_idx + i] = |
998 | 0 | affineMapGain(gainmap_data[src_pixel_idx + i], gainmap_min[i % 3], |
999 | 0 | gainmap_max[i % 3], this->mGamma); |
1000 | 0 | } |
1001 | 0 | } |
1002 | 0 | } else { |
1003 | 0 | for (size_t j = rowStart; j < rowEnd; j++) { |
1004 | 0 | size_t dst_pixel_idx = j * dest->stride[UHDR_PLANE_Y]; |
1005 | 0 | size_t src_pixel_idx = j * map_width; |
1006 | 0 | for (size_t i = 0; i < map_width; i++) { |
1007 | 0 | reinterpret_cast<uint8_t*>(dest->planes[UHDR_PLANE_Y])[dst_pixel_idx + i] = |
1008 | 0 | affineMapGain(gainmap_data[src_pixel_idx + i], gainmap_min[0], gainmap_max[0], |
1009 | 0 | this->mGamma); |
1010 | 0 | } |
1011 | 0 | } |
1012 | 0 | } |
1013 | 0 | } |
1014 | 0 | }; |
1015 | 0 | workers.clear(); |
1016 | 0 | jobQueue.reset(); |
1017 | 0 | rowStep = threads == 1 ? map_height : 1; |
1018 | 0 | for (int th = 0; th < threads - 1; th++) { |
1019 | 0 | workers.push_back(std::thread(encodeMap)); |
1020 | 0 | } |
1021 | 0 | for (unsigned int rowStart = 0; rowStart < map_height;) { |
1022 | 0 | unsigned int rowEnd = (std::min)(rowStart + rowStep, map_height); |
1023 | 0 | jobQueue.enqueueJob(rowStart, rowEnd); |
1024 | 0 | rowStart = rowEnd; |
1025 | 0 | } |
1026 | 0 | jobQueue.markQueueForEnd(); |
1027 | 0 | encodeMap(); |
1028 | 0 | std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); |
1029 | |
|
1030 | 0 | if (mUseMultiChannelGainMap) { |
1031 | 0 | for (int i = 0; i < 3; i++) { |
1032 | 0 | gainmap_metadata->max_content_boost[i] = exp2(gainmap_max[i]); |
1033 | 0 | gainmap_metadata->min_content_boost[i] = exp2(gainmap_min[i]); |
1034 | 0 | } |
1035 | 0 | } else { |
1036 | 0 | std::fill_n(gainmap_metadata->max_content_boost, 3, exp2(gainmap_max[0])); |
1037 | 0 | std::fill_n(gainmap_metadata->min_content_boost, 3, exp2(gainmap_min[0])); |
1038 | 0 | } |
1039 | 0 | std::fill_n(gainmap_metadata->gamma, 3, this->mGamma); |
1040 | 0 | std::fill_n(gainmap_metadata->offset_sdr, 3, kSdrOffset); |
1041 | 0 | std::fill_n(gainmap_metadata->offset_hdr, 3, kHdrOffset); |
1042 | 0 | gainmap_metadata->hdr_capacity_min = 1.0f; |
1043 | 0 | if (this->mTargetDispPeakBrightness != -1.0f) { |
1044 | 0 | gainmap_metadata->hdr_capacity_max = this->mTargetDispPeakBrightness / kSdrWhiteNits; |
1045 | 0 | } else { |
1046 | 0 | gainmap_metadata->hdr_capacity_max = hdr_white_nits / kSdrWhiteNits; |
1047 | 0 | } |
1048 | 0 | }; |
1049 | |
|
1050 | 0 | if (mEncPreset == UHDR_USAGE_REALTIME) { |
1051 | 0 | generateGainMapOnePass(); |
1052 | 0 | } else { |
1053 | 0 | generateGainMapTwoPass(); |
1054 | 0 | } |
1055 | |
|
1056 | 0 | return status; |
1057 | 0 | } |
1058 | | |
1059 | | // JPEG/R structure: |
1060 | | // SOI (ff d8) |
1061 | | // |
1062 | | // (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents |
1063 | | // in the JPEG input (Encode API-2, API-3, API-4)) |
1064 | | // APP1 (ff e1) |
1065 | | // 2 bytes of length (2 + length of exif package) |
1066 | | // EXIF package (this includes the first two bytes representing the package length) |
1067 | | // |
1068 | | // (Required, XMP package) APP1 (ff e1) |
1069 | | // 2 bytes of length (2 + 29 + length of xmp package) |
1070 | | // name space ("http://ns.adobe.com/xap/1.0/\0") |
1071 | | // XMP |
1072 | | // |
1073 | | // (Required, ISO 21496-1 metadata, version only) APP2 (ff e2) |
1074 | | // 2 bytes of length |
1075 | | // name space (""urn:iso:std:iso:ts:21496:-1\0") |
1076 | | // 2 bytes minimum_version: (00 00) |
1077 | | // 2 bytes writer_version: (00 00) |
1078 | | // |
1079 | | // (Required, MPF package) APP2 (ff e2) |
1080 | | // 2 bytes of length |
1081 | | // MPF |
1082 | | // |
1083 | | // (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages) |
1084 | | // |
1085 | | // SOI (ff d8) |
1086 | | // |
1087 | | // (Required, XMP package) APP1 (ff e1) |
1088 | | // 2 bytes of length (2 + 29 + length of xmp package) |
1089 | | // name space ("http://ns.adobe.com/xap/1.0/\0") |
1090 | | // XMP |
1091 | | // |
1092 | | // (Required, ISO 21496-1 metadata) APP2 (ff e2) |
1093 | | // 2 bytes of length |
1094 | | // name space (""urn:iso:std:iso:ts:21496:-1\0") |
1095 | | // metadata |
1096 | | // |
1097 | | // (Required) secondary image (the gain map, without the first two bytes (SOI)) |
1098 | | // |
1099 | | // Metadata versions we are using: |
1100 | | // ECMA TR-98 for JFIF marker |
1101 | | // Exif 2.2 spec for EXIF marker |
1102 | | // Adobe XMP spec part 3 for XMP marker |
1103 | | // ICC v4.3 spec for ICC |
1104 | | uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compressed, |
1105 | | uhdr_compressed_image_t* gainmap_compressed, |
1106 | | uhdr_mem_block_t* pExif, void* pIcc, size_t icc_size, |
1107 | | uhdr_gainmap_metadata_ext_t* metadata, |
1108 | 0 | uhdr_compressed_image_t* dest) { |
1109 | 0 | if (kWriteXmpMetadata && !metadata->use_base_cg) { |
1110 | 0 | uhdr_error_info_t status; |
1111 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1112 | 0 | status.has_detail = 1; |
1113 | 0 | snprintf( |
1114 | 0 | status.detail, sizeof status.detail, |
1115 | 0 | "setting gainmap application space as alternate image space in xmp mode is not supported"); |
1116 | 0 | return status; |
1117 | 0 | } |
1118 | | |
1119 | 0 | if (kWriteXmpMetadata && !metadata->are_all_channels_identical()) { |
1120 | 0 | uhdr_error_info_t status; |
1121 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1122 | 0 | status.has_detail = 1; |
1123 | 0 | snprintf(status.detail, sizeof status.detail, |
1124 | 0 | "signalling multichannel gainmap metadata in xmp mode is not supported"); |
1125 | 0 | return status; |
1126 | 0 | } |
1127 | | |
1128 | 0 | const size_t xmpNameSpaceLength = kXmpNameSpace.size() + 1; // need to count the null terminator |
1129 | 0 | const size_t isoNameSpaceLength = kIsoNameSpace.size() + 1; // need to count the null terminator |
1130 | | |
1131 | | ///////////////////////////////////////////////////////////////////////////////////////////////// |
1132 | | // calculate secondary image length first, because the length will be written into the primary // |
1133 | | // image xmp // |
1134 | | ///////////////////////////////////////////////////////////////////////////////////////////////// |
1135 | | |
1136 | | // XMP |
1137 | 0 | string xmp_secondary; |
1138 | 0 | size_t xmp_secondary_length; |
1139 | 0 | if (kWriteXmpMetadata) { |
1140 | 0 | xmp_secondary = generateXmpForSecondaryImage(*metadata); |
1141 | | // xmp_secondary_length = 2 bytes representing the length of the package + |
1142 | | // + xmpNameSpaceLength = 29 bytes length |
1143 | | // + length of xmp packet = xmp_secondary.size() |
1144 | 0 | xmp_secondary_length = 2 + xmpNameSpaceLength + xmp_secondary.size(); |
1145 | 0 | } |
1146 | | |
1147 | | // ISO |
1148 | 0 | uhdr_gainmap_metadata_frac iso_secondary_metadata; |
1149 | 0 | std::vector<uint8_t> iso_secondary_data; |
1150 | 0 | size_t iso_secondary_length; |
1151 | 0 | if (kWriteIso21496_1Metadata) { |
1152 | 0 | UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction( |
1153 | 0 | metadata, &iso_secondary_metadata)); |
1154 | |
|
1155 | 0 | UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::encodeGainmapMetadata(&iso_secondary_metadata, |
1156 | 0 | iso_secondary_data)); |
1157 | | // iso_secondary_length = 2 bytes representing the length of the package + |
1158 | | // + isoNameSpaceLength = 28 bytes length |
1159 | | // + length of iso metadata packet = iso_secondary_data.size() |
1160 | 0 | iso_secondary_length = 2 + isoNameSpaceLength + iso_secondary_data.size(); |
1161 | 0 | } |
1162 | | |
1163 | 0 | size_t secondary_image_size = gainmap_compressed->data_sz; |
1164 | 0 | if (kWriteXmpMetadata) { |
1165 | 0 | secondary_image_size += 2 /* 2 bytes length of APP1 sign */ + xmp_secondary_length; |
1166 | 0 | } |
1167 | 0 | if (kWriteIso21496_1Metadata) { |
1168 | 0 | secondary_image_size += 2 /* 2 bytes length of APP2 sign */ + iso_secondary_length; |
1169 | 0 | } |
1170 | | |
1171 | | // Check if EXIF package presents in the JPEG input. |
1172 | | // If so, extract and remove the EXIF package. |
1173 | 0 | JpegDecoderHelper decoder; |
1174 | 0 | UHDR_ERR_CHECK(decoder.parseImage(sdr_intent_compressed->data, sdr_intent_compressed->data_sz)); |
1175 | |
|
1176 | 0 | uhdr_mem_block_t exif_from_jpg; |
1177 | 0 | exif_from_jpg.data = nullptr; |
1178 | 0 | exif_from_jpg.data_sz = 0; |
1179 | |
|
1180 | 0 | uhdr_compressed_image_t new_jpg_image; |
1181 | 0 | new_jpg_image.data = nullptr; |
1182 | 0 | new_jpg_image.data_sz = 0; |
1183 | 0 | new_jpg_image.capacity = 0; |
1184 | 0 | new_jpg_image.cg = UHDR_CG_UNSPECIFIED; |
1185 | 0 | new_jpg_image.ct = UHDR_CT_UNSPECIFIED; |
1186 | 0 | new_jpg_image.range = UHDR_CR_UNSPECIFIED; |
1187 | |
|
1188 | 0 | std::unique_ptr<uint8_t[]> dest_data; |
1189 | 0 | if (decoder.getEXIFPos() >= 0) { |
1190 | 0 | if (pExif != nullptr) { |
1191 | 0 | uhdr_error_info_t status; |
1192 | 0 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
1193 | 0 | status.has_detail = 1; |
1194 | 0 | snprintf(status.detail, sizeof status.detail, |
1195 | 0 | "received exif from uhdr_enc_set_exif_data() while the base image intent already " |
1196 | 0 | "contains exif, unsure which one to use"); |
1197 | 0 | return status; |
1198 | 0 | } |
1199 | 0 | copyJpegWithoutExif(&new_jpg_image, sdr_intent_compressed, decoder.getEXIFPos(), |
1200 | 0 | decoder.getEXIFSize()); |
1201 | 0 | dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data)); |
1202 | 0 | exif_from_jpg.data = decoder.getEXIFPtr(); |
1203 | 0 | exif_from_jpg.data_sz = decoder.getEXIFSize(); |
1204 | 0 | pExif = &exif_from_jpg; |
1205 | 0 | } |
1206 | | |
1207 | 0 | uhdr_compressed_image_t* final_primary_jpg_image_ptr = |
1208 | 0 | new_jpg_image.data_sz == 0 ? sdr_intent_compressed : &new_jpg_image; |
1209 | |
|
1210 | 0 | size_t pos = 0; |
1211 | | // Begin primary image |
1212 | | // Write SOI |
1213 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1214 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); |
1215 | | |
1216 | | // Write EXIF |
1217 | 0 | if (pExif != nullptr) { |
1218 | 0 | const size_t length = 2 + pExif->data_sz; |
1219 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1220 | 0 | const uint8_t lengthL = (length & 0xff); |
1221 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1222 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); |
1223 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1224 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1225 | 0 | UHDR_ERR_CHECK(Write(dest, pExif->data, pExif->data_sz, pos)); |
1226 | 0 | } |
1227 | | |
1228 | | // Prepare and write XMP |
1229 | 0 | if (kWriteXmpMetadata) { |
1230 | 0 | const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata); |
1231 | 0 | const size_t length = 2 + xmpNameSpaceLength + xmp_primary.size(); |
1232 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1233 | 0 | const uint8_t lengthL = (length & 0xff); |
1234 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1235 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); |
1236 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1237 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1238 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos)); |
1239 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); |
1240 | 0 | } |
1241 | | |
1242 | | // Write ICC |
1243 | 0 | if (pIcc != nullptr && icc_size > 0) { |
1244 | 0 | const size_t length = icc_size + 2; |
1245 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1246 | 0 | const uint8_t lengthL = (length & 0xff); |
1247 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1248 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); |
1249 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1250 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1251 | 0 | UHDR_ERR_CHECK(Write(dest, pIcc, icc_size, pos)); |
1252 | 0 | } |
1253 | | |
1254 | | // Prepare and write ISO 21496-1 metadata |
1255 | 0 | if (kWriteIso21496_1Metadata) { |
1256 | 0 | const size_t length = 2 + isoNameSpaceLength + 4; |
1257 | 0 | uint8_t zero = 0; |
1258 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1259 | 0 | const uint8_t lengthL = (length & 0xff); |
1260 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1261 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); |
1262 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1263 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1264 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos)); |
1265 | 0 | UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); |
1266 | 0 | UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes minimum_version: (00 00) |
1267 | 0 | UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); |
1268 | 0 | UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes writer_version: (00 00) |
1269 | 0 | } |
1270 | | |
1271 | | // Prepare and write MPF |
1272 | 0 | { |
1273 | 0 | const size_t length = 2 + calculateMpfSize(); |
1274 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1275 | 0 | const uint8_t lengthL = (length & 0xff); |
1276 | 0 | size_t primary_image_size = pos + length + final_primary_jpg_image_ptr->data_sz; |
1277 | | // between APP2 + package size + signature |
1278 | | // ff e2 00 58 4d 50 46 00 |
1279 | | // 2 + 2 + 4 = 8 (bytes) |
1280 | | // and ff d8 sign of the secondary image |
1281 | 0 | size_t secondary_image_offset = primary_image_size - pos - 8; |
1282 | 0 | std::shared_ptr<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */ |
1283 | 0 | secondary_image_size, secondary_image_offset); |
1284 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1285 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); |
1286 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1287 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1288 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos)); |
1289 | 0 | } |
1290 | | |
1291 | | // Write primary image |
1292 | 0 | UHDR_ERR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2, |
1293 | 0 | final_primary_jpg_image_ptr->data_sz - 2, pos)); |
1294 | | // Finish primary image |
1295 | | |
1296 | | // Begin secondary image (gain map) |
1297 | | // Write SOI |
1298 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1299 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); |
1300 | | |
1301 | | // Prepare and write XMP |
1302 | 0 | if (kWriteXmpMetadata) { |
1303 | 0 | const size_t length = xmp_secondary_length; |
1304 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1305 | 0 | const uint8_t lengthL = (length & 0xff); |
1306 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1307 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); |
1308 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1309 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1310 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos)); |
1311 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos)); |
1312 | 0 | } |
1313 | | |
1314 | | // Prepare and write ISO 21496-1 metadata |
1315 | 0 | if (kWriteIso21496_1Metadata) { |
1316 | 0 | const size_t length = iso_secondary_length; |
1317 | 0 | const uint8_t lengthH = ((length >> 8) & 0xff); |
1318 | 0 | const uint8_t lengthL = (length & 0xff); |
1319 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); |
1320 | 0 | UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); |
1321 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); |
1322 | 0 | UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); |
1323 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos)); |
1324 | 0 | UHDR_ERR_CHECK(Write(dest, (void*)iso_secondary_data.data(), iso_secondary_data.size(), pos)); |
1325 | 0 | } |
1326 | | |
1327 | | // Write secondary image |
1328 | 0 | UHDR_ERR_CHECK( |
1329 | 0 | Write(dest, (uint8_t*)gainmap_compressed->data + 2, gainmap_compressed->data_sz - 2, pos)); |
1330 | | |
1331 | | // Set back length |
1332 | 0 | dest->data_sz = pos; |
1333 | | |
1334 | | // Done! |
1335 | 0 | return g_no_error; |
1336 | 0 | } |
1337 | | |
1338 | | uhdr_error_info_t JpegR::getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img, |
1339 | 22.4k | jr_info_ptr uhdr_image_info) { |
1340 | 22.4k | uhdr_compressed_image_t primary_image, gainmap; |
1341 | | |
1342 | 22.4k | UHDR_ERR_CHECK(extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_image, &gainmap)) |
1343 | | |
1344 | 21.4k | UHDR_ERR_CHECK(parseJpegInfo(&primary_image, uhdr_image_info->primaryImgInfo, |
1345 | 21.4k | &uhdr_image_info->width, &uhdr_image_info->height)) |
1346 | 18.5k | if (uhdr_image_info->gainmapImgInfo != nullptr) { |
1347 | 18.5k | UHDR_ERR_CHECK(parseJpegInfo(&gainmap, uhdr_image_info->gainmapImgInfo)) |
1348 | 18.5k | } |
1349 | | |
1350 | 18.5k | return g_no_error; |
1351 | 18.5k | } |
1352 | | |
1353 | | uhdr_error_info_t JpegR::parseGainMapMetadata(uint8_t* iso_data, size_t iso_size, uint8_t* xmp_data, |
1354 | | size_t xmp_size, |
1355 | 21.3k | uhdr_gainmap_metadata_ext_t* uhdr_metadata) { |
1356 | 21.3k | if (iso_size > 0) { |
1357 | 17.5k | if (iso_size < kIsoNameSpace.size() + 1) { |
1358 | 0 | uhdr_error_info_t status; |
1359 | 0 | status.error_code = UHDR_CODEC_ERROR; |
1360 | 0 | status.has_detail = 1; |
1361 | 0 | snprintf(status.detail, sizeof status.detail, |
1362 | 0 | "iso block size needs to be atleast %zd but got %zd", kIsoNameSpace.size() + 1, |
1363 | 0 | iso_size); |
1364 | 0 | return status; |
1365 | 0 | } |
1366 | 17.5k | uhdr_gainmap_metadata_frac decodedMetadata; |
1367 | 17.5k | std::vector<uint8_t> iso_vec; |
1368 | 2.20M | for (size_t i = kIsoNameSpace.size() + 1; i < iso_size; i++) { |
1369 | 2.18M | iso_vec.push_back(iso_data[i]); |
1370 | 2.18M | } |
1371 | | |
1372 | 17.5k | UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::decodeGainmapMetadata(iso_vec, &decodedMetadata)); |
1373 | 17.3k | UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&decodedMetadata, |
1374 | 17.3k | uhdr_metadata)); |
1375 | 17.2k | } else if (xmp_size > 0) { |
1376 | 2.78k | UHDR_ERR_CHECK(getMetadataFromXMP(xmp_data, xmp_size, uhdr_metadata)); |
1377 | 1.05k | } else { |
1378 | 1.05k | uhdr_error_info_t status; |
1379 | 1.05k | status.error_code = UHDR_CODEC_INVALID_PARAM; |
1380 | 1.05k | status.has_detail = 1; |
1381 | 1.05k | snprintf(status.detail, sizeof status.detail, |
1382 | 1.05k | "received no valid buffer to parse gainmap metadata"); |
1383 | 1.05k | return status; |
1384 | 1.05k | } |
1385 | | |
1386 | 17.2k | return g_no_error; |
1387 | 21.3k | } |
1388 | | |
1389 | | /* Decode API */ |
1390 | | uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_img, |
1391 | | uhdr_raw_image_t* dest, float max_display_boost, |
1392 | | uhdr_color_transfer_t output_ct, uhdr_img_fmt_t output_format, |
1393 | | uhdr_raw_image_t* gainmap_img, |
1394 | 7.22k | uhdr_gainmap_metadata_t* gainmap_metadata) { |
1395 | 7.22k | uhdr_compressed_image_t primary_jpeg_image, gainmap_jpeg_image; |
1396 | 7.22k | UHDR_ERR_CHECK( |
1397 | 7.22k | extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_jpeg_image, &gainmap_jpeg_image)) |
1398 | | |
1399 | 7.22k | JpegDecoderHelper jpeg_dec_obj_sdr; |
1400 | 7.22k | UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage( |
1401 | 7.22k | primary_jpeg_image.data, primary_jpeg_image.data_sz, |
1402 | 7.22k | (output_ct == UHDR_CT_SRGB) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS)); |
1403 | | |
1404 | 3.92k | JpegDecoderHelper jpeg_dec_obj_gm; |
1405 | 3.92k | uhdr_raw_image_t gainmap; |
1406 | 3.92k | if (gainmap_img != nullptr || output_ct != UHDR_CT_SRGB) { |
1407 | 3.92k | UHDR_ERR_CHECK(jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, |
1408 | 3.92k | gainmap_jpeg_image.data_sz, DECODE_STREAM)); |
1409 | 3.75k | gainmap = jpeg_dec_obj_gm.getDecompressedImage(); |
1410 | 3.75k | if (gainmap_img != nullptr) { |
1411 | 3.75k | UHDR_ERR_CHECK(copy_raw_image(&gainmap, gainmap_img)); |
1412 | 3.75k | } |
1413 | 3.75k | gainmap.cg = |
1414 | 3.75k | IccHelper::readIccColorGamut(jpeg_dec_obj_gm.getICCPtr(), jpeg_dec_obj_gm.getICCSize()); |
1415 | 3.75k | } |
1416 | | |
1417 | 3.75k | uhdr_gainmap_metadata_ext_t uhdr_metadata; |
1418 | 3.75k | if (gainmap_metadata != nullptr || output_ct != UHDR_CT_SRGB) { |
1419 | 2.83k | UHDR_ERR_CHECK(parseGainMapMetadata(static_cast<uint8_t*>(jpeg_dec_obj_gm.getIsoMetadataPtr()), |
1420 | 2.83k | jpeg_dec_obj_gm.getIsoMetadataSize(), |
1421 | 2.83k | static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()), |
1422 | 2.83k | jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) |
1423 | 2.83k | if (gainmap_metadata != nullptr) { |
1424 | 0 | std::copy(uhdr_metadata.min_content_boost, uhdr_metadata.min_content_boost + 3, |
1425 | 0 | gainmap_metadata->min_content_boost); |
1426 | 0 | std::copy(uhdr_metadata.max_content_boost, uhdr_metadata.max_content_boost + 3, |
1427 | 0 | gainmap_metadata->max_content_boost); |
1428 | 0 | std::copy(uhdr_metadata.gamma, uhdr_metadata.gamma + 3, gainmap_metadata->gamma); |
1429 | 0 | std::copy(uhdr_metadata.offset_sdr, uhdr_metadata.offset_sdr + 3, |
1430 | 0 | gainmap_metadata->offset_sdr); |
1431 | 0 | std::copy(uhdr_metadata.offset_hdr, uhdr_metadata.offset_hdr + 3, |
1432 | 0 | gainmap_metadata->offset_hdr); |
1433 | 0 | gainmap_metadata->hdr_capacity_min = uhdr_metadata.hdr_capacity_min; |
1434 | 0 | gainmap_metadata->hdr_capacity_max = uhdr_metadata.hdr_capacity_max; |
1435 | 0 | gainmap_metadata->use_base_cg = uhdr_metadata.use_base_cg; |
1436 | 0 | } |
1437 | 2.83k | } |
1438 | | |
1439 | 3.75k | uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage(); |
1440 | 3.75k | sdr_intent.cg = |
1441 | 3.75k | IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize()); |
1442 | 3.75k | if (output_ct == UHDR_CT_SRGB) { |
1443 | 921 | UHDR_ERR_CHECK(copy_raw_image(&sdr_intent, dest)); |
1444 | 921 | return g_no_error; |
1445 | 921 | } |
1446 | | |
1447 | 2.83k | UHDR_ERR_CHECK(applyGainMap(&sdr_intent, &gainmap, &uhdr_metadata, output_ct, output_format, |
1448 | 2.83k | max_display_boost, dest)); |
1449 | | |
1450 | 1.08k | return g_no_error; |
1451 | 2.83k | } |
1452 | | |
1453 | | uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img, |
1454 | | uhdr_gainmap_metadata_ext_t* gainmap_metadata, |
1455 | | uhdr_color_transfer_t output_ct, |
1456 | | [[maybe_unused]] uhdr_img_fmt_t output_format, |
1457 | 2.83k | float max_display_boost, uhdr_raw_image_t* dest) { |
1458 | 2.83k | if (gainmap_metadata->version.compare(kJpegrVersion)) { |
1459 | 0 | uhdr_error_info_t status; |
1460 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1461 | 0 | status.has_detail = 1; |
1462 | 0 | snprintf(status.detail, sizeof status.detail, |
1463 | 0 | "Unsupported gainmap metadata, version. Expected %s, Got %s", kJpegrVersion, |
1464 | 0 | gainmap_metadata->version.c_str()); |
1465 | 0 | return status; |
1466 | 0 | } |
1467 | 2.83k | UHDR_ERR_CHECK(uhdr_validate_gainmap_metadata_descriptor(gainmap_metadata)); |
1468 | 1.30k | if (sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444 && |
1469 | 1.22k | sdr_intent->fmt != UHDR_IMG_FMT_16bppYCbCr422 && |
1470 | 1.04k | sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { |
1471 | 221 | uhdr_error_info_t status; |
1472 | 221 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1473 | 221 | status.has_detail = 1; |
1474 | 221 | snprintf(status.detail, sizeof status.detail, |
1475 | 221 | "apply gainmap method expects base image color format to be one of " |
1476 | 221 | "{UHDR_IMG_FMT_24bppYCbCr444, UHDR_IMG_FMT_16bppYCbCr422, " |
1477 | 221 | "UHDR_IMG_FMT_12bppYCbCr420}. Received %d", |
1478 | 221 | sdr_intent->fmt); |
1479 | 221 | return status; |
1480 | 221 | } |
1481 | 1.08k | if (gainmap_img->fmt != UHDR_IMG_FMT_8bppYCbCr400 && |
1482 | 690 | gainmap_img->fmt != UHDR_IMG_FMT_24bppRGB888 && |
1483 | 690 | gainmap_img->fmt != UHDR_IMG_FMT_32bppRGBA8888) { |
1484 | 0 | uhdr_error_info_t status; |
1485 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1486 | 0 | status.has_detail = 1; |
1487 | 0 | snprintf(status.detail, sizeof status.detail, |
1488 | 0 | "apply gainmap method expects gainmap image color format to be one of " |
1489 | 0 | "{UHDR_IMG_FMT_8bppYCbCr400, UHDR_IMG_FMT_24bppRGB888, UHDR_IMG_FMT_32bppRGBA8888}. " |
1490 | 0 | "Received %d", |
1491 | 0 | gainmap_img->fmt); |
1492 | 0 | return status; |
1493 | 0 | } |
1494 | | |
1495 | 1.08k | uhdr_color_gamut_t sdr_cg = |
1496 | 1.08k | sdr_intent->cg == UHDR_CG_UNSPECIFIED ? UHDR_CG_BT_709 : sdr_intent->cg; |
1497 | 1.08k | uhdr_color_gamut_t hdr_cg = gainmap_img->cg == UHDR_CG_UNSPECIFIED ? sdr_cg : gainmap_img->cg; |
1498 | 1.08k | dest->cg = hdr_cg; |
1499 | 1.08k | ColorTransformFn hdrGamutConversionFn = |
1500 | 1.08k | gainmap_metadata->use_base_cg ? getGamutConversionFn(hdr_cg, sdr_cg) : identityConversion; |
1501 | 1.08k | ColorTransformFn sdrGamutConversionFn = |
1502 | 1.08k | gainmap_metadata->use_base_cg ? identityConversion : getGamutConversionFn(hdr_cg, sdr_cg); |
1503 | 1.08k | if (hdrGamutConversionFn == nullptr || sdrGamutConversionFn == nullptr) { |
1504 | 0 | uhdr_error_info_t status; |
1505 | 0 | status.error_code = UHDR_CODEC_ERROR; |
1506 | 0 | status.has_detail = 1; |
1507 | 0 | snprintf(status.detail, sizeof status.detail, |
1508 | 0 | "No implementation available for converting from gamut %d to %d", sdr_cg, hdr_cg); |
1509 | 0 | return status; |
1510 | 0 | } |
1511 | | |
1512 | | #ifdef UHDR_ENABLE_GLES |
1513 | | if (mUhdrGLESCtxt != nullptr) { |
1514 | | if (((sdr_intent->fmt == UHDR_IMG_FMT_12bppYCbCr420 && sdr_intent->w % 2 == 0 && |
1515 | | sdr_intent->h % 2 == 0) || |
1516 | | (sdr_intent->fmt == UHDR_IMG_FMT_16bppYCbCr422 && sdr_intent->w % 2 == 0) || |
1517 | | (sdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCr444)) && |
1518 | | isBufferDataContiguous(sdr_intent) && isBufferDataContiguous(gainmap_img) && |
1519 | | isBufferDataContiguous(dest)) { |
1520 | | // TODO: both inputs and outputs of GLES implementation assumes that raw image is contiguous |
1521 | | // and without strides. If not, handle the same by using temp copy |
1522 | | float display_boost = (std::min)(max_display_boost, gainmap_metadata->hdr_capacity_max); |
1523 | | |
1524 | | return applyGainMapGLES(sdr_intent, gainmap_img, gainmap_metadata, output_ct, display_boost, |
1525 | | sdr_cg, hdr_cg, static_cast<uhdr_opengl_ctxt_t*>(mUhdrGLESCtxt)); |
1526 | | } |
1527 | | } |
1528 | | #endif |
1529 | | |
1530 | 1.08k | std::unique_ptr<uhdr_raw_image_ext_t> resized_gainmap = nullptr; |
1531 | 1.08k | { |
1532 | 1.08k | float primary_aspect_ratio = (float)sdr_intent->w / sdr_intent->h; |
1533 | 1.08k | float gainmap_aspect_ratio = (float)gainmap_img->w / gainmap_img->h; |
1534 | 1.08k | float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio); |
1535 | | // Allow 1% delta |
1536 | 1.08k | const float delta_tolerance = 0.01f; |
1537 | 1.08k | if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) { |
1538 | 362 | resized_gainmap = resize_image(gainmap_img, sdr_intent->w, sdr_intent->h); |
1539 | 362 | if (resized_gainmap == nullptr) { |
1540 | 0 | uhdr_error_info_t status; |
1541 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1542 | 0 | status.has_detail = 1; |
1543 | 0 | snprintf(status.detail, sizeof status.detail, |
1544 | 0 | "encountered error while resizing the gainmap image from %ux%u to %ux%u", |
1545 | 0 | gainmap_img->w, gainmap_img->h, sdr_intent->w, sdr_intent->h); |
1546 | 0 | return status; |
1547 | 0 | } |
1548 | 362 | gainmap_img = resized_gainmap.get(); |
1549 | 362 | } |
1550 | 1.08k | } |
1551 | | |
1552 | 1.08k | float map_scale_factor = (float)sdr_intent->w / gainmap_img->w; |
1553 | 1.08k | int map_scale_factor_rnd = (std::max)(1, (int)std::roundf(map_scale_factor)); |
1554 | | |
1555 | | // Table will only be used when map scale factor is integer. |
1556 | 1.08k | ShepardsIDW idwTable(map_scale_factor_rnd); |
1557 | 1.08k | float display_boost = (std::min)(max_display_boost, gainmap_metadata->hdr_capacity_max); |
1558 | | |
1559 | 1.08k | float gainmap_weight; |
1560 | 1.08k | if (display_boost != gainmap_metadata->hdr_capacity_max) { |
1561 | 287 | gainmap_weight = |
1562 | 287 | (log2(display_boost) - log2(gainmap_metadata->hdr_capacity_min)) / |
1563 | 287 | (log2(gainmap_metadata->hdr_capacity_max) - log2(gainmap_metadata->hdr_capacity_min)); |
1564 | | // avoid extrapolating the gain map to fill the displayable range |
1565 | 287 | gainmap_weight = CLIP3(gainmap_weight, 0.0f, 1.0f); |
1566 | 800 | } else { |
1567 | 800 | gainmap_weight = 1.0f; |
1568 | 800 | } |
1569 | 1.08k | GainLUT gainLUT(gainmap_metadata, gainmap_weight); |
1570 | | |
1571 | 1.08k | GetPixelFn get_pixel_fn = getPixelFn(sdr_intent->fmt); |
1572 | 1.08k | if (get_pixel_fn == nullptr) { |
1573 | 0 | uhdr_error_info_t status; |
1574 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1575 | 0 | status.has_detail = 1; |
1576 | 0 | snprintf(status.detail, sizeof status.detail, |
1577 | 0 | "No implementation available for reading pixels for color format %d", sdr_intent->fmt); |
1578 | 0 | return status; |
1579 | 0 | } |
1580 | | |
1581 | 1.08k | JobQueue jobQueue; |
1582 | 1.08k | std::function<void()> applyRecMap = [sdr_intent, gainmap_img, dest, &jobQueue, &idwTable, |
1583 | 1.08k | output_ct, &gainLUT, gainmap_metadata, hdrGamutConversionFn, |
1584 | 1.08k | sdrGamutConversionFn, |
1585 | | #if !USE_APPLY_GAIN_LUT |
1586 | | gainmap_weight, |
1587 | | #endif |
1588 | 4.34k | map_scale_factor, get_pixel_fn]() -> void { |
1589 | 4.34k | unsigned int width = sdr_intent->w; |
1590 | 4.34k | unsigned int rowStart, rowEnd; |
1591 | | |
1592 | 18.4E | while (jobQueue.dequeueJob(rowStart, rowEnd)) { |
1593 | 18.4E | for (size_t y = rowStart; y < rowEnd; ++y) { |
1594 | 233M | for (size_t x = 0; x < width; ++x) { |
1595 | 233M | Color yuv_gamma_sdr = get_pixel_fn(sdr_intent, x, y); |
1596 | | // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients |
1597 | 233M | Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); |
1598 | | // We are assuming the SDR base image is always sRGB transfer. |
1599 | 233M | #if USE_SRGB_INVOETF_LUT |
1600 | 233M | Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr); |
1601 | | #else |
1602 | | Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr); |
1603 | | #endif |
1604 | 233M | rgb_sdr = sdrGamutConversionFn(rgb_sdr); |
1605 | 233M | Color rgb_hdr; |
1606 | 233M | if (gainmap_img->fmt == UHDR_IMG_FMT_8bppYCbCr400) { |
1607 | 99.3M | float gain; |
1608 | | |
1609 | 99.3M | if (map_scale_factor != floorf(map_scale_factor)) { |
1610 | 24.7M | gain = sampleMap(gainmap_img, map_scale_factor, x, y); |
1611 | 74.5M | } else { |
1612 | 74.5M | gain = sampleMap(gainmap_img, map_scale_factor, x, y, idwTable); |
1613 | 74.5M | } |
1614 | | |
1615 | 99.3M | #if USE_APPLY_GAIN_LUT |
1616 | 99.3M | rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT, gainmap_metadata); |
1617 | | #else |
1618 | | rgb_hdr = applyGain(rgb_sdr, gain, gainmap_metadata, gainmap_weight); |
1619 | | #endif |
1620 | 134M | } else { |
1621 | 134M | Color gain; |
1622 | | |
1623 | 134M | if (map_scale_factor != floorf(map_scale_factor)) { |
1624 | 28.3M | gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y, |
1625 | 28.3M | gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888); |
1626 | 106M | } else { |
1627 | 106M | gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y, idwTable, |
1628 | 106M | gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888); |
1629 | 106M | } |
1630 | | |
1631 | 134M | #if USE_APPLY_GAIN_LUT |
1632 | 134M | rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT, gainmap_metadata); |
1633 | | #else |
1634 | | rgb_hdr = applyGain(rgb_sdr, gain, gainmap_metadata, gainmap_weight); |
1635 | | #endif |
1636 | 134M | } |
1637 | | |
1638 | 233M | size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_PACKED]; |
1639 | | |
1640 | 233M | switch (output_ct) { |
1641 | 92.8M | case UHDR_CT_LINEAR: { |
1642 | 92.8M | rgb_hdr = hdrGamutConversionFn(rgb_hdr); |
1643 | 92.8M | rgb_hdr = clampPixelFloatLinear(rgb_hdr); |
1644 | 92.8M | uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr); |
1645 | 92.8M | reinterpret_cast<uint64_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = rgba_f16; |
1646 | 92.8M | break; |
1647 | 0 | } |
1648 | 74.9M | case UHDR_CT_HLG: { |
1649 | 74.9M | #if USE_HLG_OETF_LUT |
1650 | 74.9M | ColorTransformFn hdrOetf = hlgOetfLUT; |
1651 | | #else |
1652 | | ColorTransformFn hdrOetf = hlgOetf; |
1653 | | #endif |
1654 | 74.9M | rgb_hdr = rgb_hdr * kSdrWhiteNits / kHlgMaxNits; |
1655 | 74.9M | rgb_hdr = hdrGamutConversionFn(rgb_hdr); |
1656 | 74.9M | rgb_hdr = clampPixelFloat(rgb_hdr); |
1657 | 74.9M | rgb_hdr = hlgInverseOotfApprox(rgb_hdr); |
1658 | 74.9M | Color rgb_gamma_hdr = hdrOetf(rgb_hdr); |
1659 | 74.9M | uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); |
1660 | 74.9M | reinterpret_cast<uint32_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = |
1661 | 74.9M | rgba_1010102; |
1662 | 74.9M | break; |
1663 | 0 | } |
1664 | 39.0M | case UHDR_CT_PQ: { |
1665 | 39.0M | #if USE_PQ_OETF_LUT |
1666 | 39.0M | ColorTransformFn hdrOetf = pqOetfLUT; |
1667 | | #else |
1668 | | ColorTransformFn hdrOetf = pqOetf; |
1669 | | #endif |
1670 | 39.0M | rgb_hdr = rgb_hdr * kSdrWhiteNits / kPqMaxNits; |
1671 | 39.0M | rgb_hdr = hdrGamutConversionFn(rgb_hdr); |
1672 | 39.0M | rgb_hdr = clampPixelFloat(rgb_hdr); |
1673 | 39.0M | Color rgb_gamma_hdr = hdrOetf(rgb_hdr); |
1674 | 39.0M | uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); |
1675 | 39.0M | reinterpret_cast<uint32_t*>(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = |
1676 | 39.0M | rgba_1010102; |
1677 | 39.0M | break; |
1678 | 0 | } |
1679 | 0 | default: { |
1680 | 0 | } |
1681 | | // Should be impossible to hit after input validation. |
1682 | 233M | } |
1683 | 233M | } |
1684 | 804k | } |
1685 | 761k | } |
1686 | 4.34k | }; |
1687 | | |
1688 | 1.08k | const int threads = (std::min)(GetCPUCoreCount(), 4u); |
1689 | 1.08k | std::vector<std::thread> workers; |
1690 | 4.34k | for (int th = 0; th < threads - 1; th++) { |
1691 | 3.26k | workers.push_back(std::thread(applyRecMap)); |
1692 | 3.26k | } |
1693 | 1.08k | const unsigned int rowStep = threads == 1 ? sdr_intent->h : map_scale_factor_rnd; |
1694 | 762k | for (unsigned int rowStart = 0; rowStart < sdr_intent->h;) { |
1695 | 761k | unsigned int rowEnd = (std::min)(rowStart + rowStep, sdr_intent->h); |
1696 | 761k | jobQueue.enqueueJob(rowStart, rowEnd); |
1697 | 761k | rowStart = rowEnd; |
1698 | 761k | } |
1699 | 1.08k | jobQueue.markQueueForEnd(); |
1700 | 1.08k | applyRecMap(); |
1701 | 3.26k | std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); |
1702 | | |
1703 | 1.08k | return g_no_error; |
1704 | 1.08k | } |
1705 | | |
1706 | | uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image, |
1707 | | uhdr_compressed_image_t* primary_image, |
1708 | 29.6k | uhdr_compressed_image_t* gainmap_image) { |
1709 | 29.6k | MessageHandler msg_handler; |
1710 | 29.6k | msg_handler.SetMessageWriter(make_unique<AlogMessageWriter>(AlogMessageWriter())); |
1711 | | |
1712 | 29.6k | std::shared_ptr<DataSegment> seg = DataSegment::Create( |
1713 | 29.6k | DataRange(0, jpegr_image->data_sz), static_cast<const uint8_t*>(jpegr_image->data), |
1714 | 29.6k | DataSegment::BufferDispositionPolicy::kDontDelete); |
1715 | 29.6k | DataSegmentDataSource data_source(seg); |
1716 | | |
1717 | 29.6k | JpegInfoBuilder jpeg_info_builder; |
1718 | 29.6k | jpeg_info_builder.SetImageLimit(2); |
1719 | | |
1720 | 29.6k | JpegScanner jpeg_scanner(&msg_handler); |
1721 | 29.6k | jpeg_scanner.Run(&data_source, &jpeg_info_builder); |
1722 | 29.6k | data_source.Reset(); |
1723 | | |
1724 | 29.6k | if (jpeg_scanner.HasError()) { |
1725 | 250 | uhdr_error_info_t status; |
1726 | 250 | status.error_code = UHDR_CODEC_ERROR; |
1727 | 250 | status.has_detail = 1; |
1728 | 250 | auto messages = msg_handler.GetMessages(); |
1729 | 250 | std::string append{}; |
1730 | 350 | for (auto message : messages) append += message.GetText(); |
1731 | 250 | snprintf(status.detail, sizeof status.detail, "%s", append.c_str()); |
1732 | 250 | return status; |
1733 | 250 | } |
1734 | | |
1735 | 29.4k | const auto& jpeg_info = jpeg_info_builder.GetInfo(); |
1736 | 29.4k | const auto& image_ranges = jpeg_info.GetImageRanges(); |
1737 | | |
1738 | 29.4k | if (image_ranges.empty()) { |
1739 | 632 | uhdr_error_info_t status; |
1740 | 632 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
1741 | 632 | status.has_detail = 1; |
1742 | 632 | snprintf(status.detail, sizeof status.detail, "input uhdr image does not contain any valid images"); |
1743 | 632 | return status; |
1744 | 632 | } |
1745 | | |
1746 | 28.8k | if (primary_image != nullptr) { |
1747 | 28.8k | primary_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[0].GetBegin(); |
1748 | 28.8k | primary_image->data_sz = image_ranges[0].GetLength(); |
1749 | 28.8k | } |
1750 | | |
1751 | 28.8k | if (image_ranges.size() == 1) { |
1752 | 170 | uhdr_error_info_t status; |
1753 | 170 | status.error_code = UHDR_CODEC_INVALID_PARAM; |
1754 | 170 | status.has_detail = 1; |
1755 | 170 | snprintf(status.detail, sizeof status.detail, |
1756 | 170 | "input uhdr image does not contain gainmap image"); |
1757 | 170 | return status; |
1758 | 170 | } |
1759 | | |
1760 | 28.6k | if (gainmap_image != nullptr) { |
1761 | 28.6k | gainmap_image->data = static_cast<uint8_t*>(jpegr_image->data) + image_ranges[1].GetBegin(); |
1762 | 28.6k | gainmap_image->data_sz = image_ranges[1].GetLength(); |
1763 | 28.6k | } |
1764 | | |
1765 | | // TODO: choose primary image and gain map image carefully |
1766 | 28.6k | if (image_ranges.size() > 2) { |
1767 | 335 | ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen", |
1768 | 335 | (int)image_ranges.size()); |
1769 | 335 | } |
1770 | | |
1771 | 28.6k | return g_no_error; |
1772 | 28.8k | } |
1773 | | |
1774 | | uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info, |
1775 | 39.9k | unsigned int* img_width, unsigned int* img_height) { |
1776 | 39.9k | JpegDecoderHelper jpeg_dec_obj; |
1777 | 39.9k | UHDR_ERR_CHECK(jpeg_dec_obj.parseImage(jpeg_image->data, jpeg_image->data_sz)) |
1778 | 37.0k | unsigned int imgWidth, imgHeight, numComponents; |
1779 | 37.0k | imgWidth = jpeg_dec_obj.getDecompressedImageWidth(); |
1780 | 37.0k | imgHeight = jpeg_dec_obj.getDecompressedImageHeight(); |
1781 | 37.0k | numComponents = jpeg_dec_obj.getNumComponentsInImage(); |
1782 | | |
1783 | 37.0k | if (image_info != nullptr) { |
1784 | 37.0k | image_info->width = imgWidth; |
1785 | 37.0k | image_info->height = imgHeight; |
1786 | 37.0k | image_info->numComponents = numComponents; |
1787 | 37.0k | image_info->imgData.resize(jpeg_image->data_sz, 0); |
1788 | 37.0k | memcpy(static_cast<void*>(image_info->imgData.data()), jpeg_image->data, jpeg_image->data_sz); |
1789 | 37.0k | if (jpeg_dec_obj.getICCSize() != 0) { |
1790 | 2.64k | image_info->iccData.resize(jpeg_dec_obj.getICCSize(), 0); |
1791 | 2.64k | memcpy(static_cast<void*>(image_info->iccData.data()), jpeg_dec_obj.getICCPtr(), |
1792 | 2.64k | jpeg_dec_obj.getICCSize()); |
1793 | 2.64k | } |
1794 | 37.0k | if (jpeg_dec_obj.getEXIFSize() != 0) { |
1795 | 76 | image_info->exifData.resize(jpeg_dec_obj.getEXIFSize(), 0); |
1796 | 76 | memcpy(static_cast<void*>(image_info->exifData.data()), jpeg_dec_obj.getEXIFPtr(), |
1797 | 76 | jpeg_dec_obj.getEXIFSize()); |
1798 | 76 | } |
1799 | 37.0k | if (jpeg_dec_obj.getXMPSize() != 0) { |
1800 | 5.87k | image_info->xmpData.resize(jpeg_dec_obj.getXMPSize(), 0); |
1801 | 5.87k | memcpy(static_cast<void*>(image_info->xmpData.data()), jpeg_dec_obj.getXMPPtr(), |
1802 | 5.87k | jpeg_dec_obj.getXMPSize()); |
1803 | 5.87k | } |
1804 | 37.0k | if (jpeg_dec_obj.getIsoMetadataSize() != 0) { |
1805 | 27.8k | image_info->isoData.resize(jpeg_dec_obj.getIsoMetadataSize(), 0); |
1806 | 27.8k | memcpy(static_cast<void*>(image_info->isoData.data()), jpeg_dec_obj.getIsoMetadataPtr(), |
1807 | 27.8k | jpeg_dec_obj.getIsoMetadataSize()); |
1808 | 27.8k | } |
1809 | 37.0k | } |
1810 | 37.0k | if (img_width != nullptr && img_height != nullptr) { |
1811 | 18.5k | *img_width = imgWidth; |
1812 | 18.5k | *img_height = imgHeight; |
1813 | 18.5k | } |
1814 | 37.0k | return g_no_error; |
1815 | 39.9k | } |
1816 | | |
1817 | 0 | static float ReinhardMap(float y_hdr, float headroom) { |
1818 | 0 | float out = 1.0f + y_hdr / (headroom * headroom); |
1819 | 0 | out /= 1.0f + y_hdr; |
1820 | 0 | return out * y_hdr; |
1821 | 0 | } |
1822 | | |
1823 | | GlobalTonemapOutputs globalTonemap(const std::array<float, 3>& rgb_in, float headroom, |
1824 | 0 | bool is_normalized) { |
1825 | | // Scale to Headroom to get HDR values that are referenced to SDR white. The range [0.0, 1.0] is |
1826 | | // linearly stretched to [0.0, headroom]. |
1827 | 0 | std::array<float, 3> rgb_hdr; |
1828 | 0 | std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(), |
1829 | 0 | [&](float x) { return is_normalized ? x * headroom : x; }); |
1830 | | |
1831 | | // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by |
1832 | | // keeping the shadows the same and crushing the highlights. |
1833 | 0 | float max_hdr = *std::max_element(rgb_hdr.begin(), rgb_hdr.end()); |
1834 | 0 | float max_sdr = ReinhardMap(max_hdr, headroom); |
1835 | 0 | std::array<float, 3> rgb_sdr; |
1836 | 0 | std::transform(rgb_hdr.begin(), rgb_hdr.end(), rgb_sdr.begin(), [&](float x) { |
1837 | 0 | if (x > 0.0f) { |
1838 | 0 | return x * max_sdr / max_hdr; |
1839 | 0 | } |
1840 | 0 | return 0.0f; |
1841 | 0 | }); |
1842 | |
|
1843 | 0 | GlobalTonemapOutputs tonemap_outputs; |
1844 | 0 | tonemap_outputs.rgb_out = rgb_sdr; |
1845 | 0 | tonemap_outputs.y_hdr = max_hdr; |
1846 | 0 | tonemap_outputs.y_sdr = max_sdr; |
1847 | |
|
1848 | 0 | return tonemap_outputs; |
1849 | 0 | } |
1850 | | |
1851 | 0 | uint8_t ScaleTo8Bit(float value) { |
1852 | 0 | constexpr float kMaxValFloat = 255.0f; |
1853 | 0 | constexpr int kMaxValInt = 255; |
1854 | 0 | return std::clamp(static_cast<int>(std::round(value * kMaxValFloat)), 0, kMaxValInt); |
1855 | 0 | } |
1856 | | |
1857 | 0 | uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent) { |
1858 | 0 | if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010 && |
1859 | 0 | hdr_intent->fmt != UHDR_IMG_FMT_30bppYCbCr444 && |
1860 | 0 | hdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA1010102 && |
1861 | 0 | hdr_intent->fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat) { |
1862 | 0 | uhdr_error_info_t status; |
1863 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1864 | 0 | status.has_detail = 1; |
1865 | 0 | snprintf(status.detail, sizeof status.detail, |
1866 | 0 | "tonemap method expects hdr intent color format to be one of " |
1867 | 0 | "{UHDR_IMG_FMT_24bppYCbCrP010, UHDR_IMG_FMT_30bppYCbCr444, " |
1868 | 0 | "UHDR_IMG_FMT_32bppRGBA1010102, UHDR_IMG_FMT_64bppRGBAHalfFloat}. Received %d", |
1869 | 0 | hdr_intent->fmt); |
1870 | 0 | return status; |
1871 | 0 | } |
1872 | | |
1873 | 0 | if (hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 && |
1874 | 0 | sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { |
1875 | 0 | uhdr_error_info_t status; |
1876 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1877 | 0 | status.has_detail = 1; |
1878 | 0 | snprintf(status.detail, sizeof status.detail, |
1879 | 0 | "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_12bppYCbCr420, if " |
1880 | 0 | "hdr intent color format is UHDR_IMG_FMT_24bppYCbCrP010. Received %d", |
1881 | 0 | sdr_intent->fmt); |
1882 | 0 | return status; |
1883 | 0 | } |
1884 | | |
1885 | 0 | if (hdr_intent->fmt == UHDR_IMG_FMT_30bppYCbCr444 && |
1886 | 0 | sdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCr444) { |
1887 | 0 | uhdr_error_info_t status; |
1888 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1889 | 0 | status.has_detail = 1; |
1890 | 0 | snprintf(status.detail, sizeof status.detail, |
1891 | 0 | "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_24bppYCbCr444, if " |
1892 | 0 | "hdr intent color format is UHDR_IMG_FMT_30bppYCbCr444. Received %d", |
1893 | 0 | sdr_intent->fmt); |
1894 | 0 | return status; |
1895 | 0 | } |
1896 | | |
1897 | 0 | if ((hdr_intent->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || |
1898 | 0 | hdr_intent->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) && |
1899 | 0 | sdr_intent->fmt != UHDR_IMG_FMT_32bppRGBA8888) { |
1900 | 0 | uhdr_error_info_t status; |
1901 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1902 | 0 | status.has_detail = 1; |
1903 | 0 | snprintf(status.detail, sizeof status.detail, |
1904 | 0 | "tonemap method expects sdr intent color format to be UHDR_IMG_FMT_32bppRGBA8888, if " |
1905 | 0 | "hdr intent color format is UHDR_IMG_FMT_32bppRGBA1010102 or " |
1906 | 0 | "UHDR_IMG_FMT_64bppRGBAHalfFloat. Received %d", |
1907 | 0 | sdr_intent->fmt); |
1908 | 0 | return status; |
1909 | 0 | } |
1910 | | |
1911 | 0 | ColorTransformFn hdrYuvToRgbFn = getYuvToRgbFn(hdr_intent->cg); |
1912 | 0 | if (hdrYuvToRgbFn == nullptr) { |
1913 | 0 | uhdr_error_info_t status; |
1914 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1915 | 0 | status.has_detail = 1; |
1916 | 0 | snprintf(status.detail, sizeof status.detail, |
1917 | 0 | "No implementation available for converting yuv to rgb for color gamut %d", |
1918 | 0 | hdr_intent->cg); |
1919 | 0 | return status; |
1920 | 0 | } |
1921 | | |
1922 | 0 | LuminanceFn hdrLuminanceFn = getLuminanceFn(hdr_intent->cg); |
1923 | 0 | if (hdrLuminanceFn == nullptr) { |
1924 | 0 | uhdr_error_info_t status; |
1925 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1926 | 0 | status.has_detail = 1; |
1927 | 0 | snprintf(status.detail, sizeof status.detail, |
1928 | 0 | "No implementation available for calculating luminance for color gamut %d", |
1929 | 0 | hdr_intent->cg); |
1930 | 0 | return status; |
1931 | 0 | } |
1932 | | |
1933 | 0 | SceneToDisplayLuminanceFn hdrOotfFn = getOotfFn(hdr_intent->ct); |
1934 | 0 | if (hdrOotfFn == nullptr) { |
1935 | 0 | uhdr_error_info_t status; |
1936 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1937 | 0 | status.has_detail = 1; |
1938 | 0 | snprintf(status.detail, sizeof status.detail, |
1939 | 0 | "No implementation available for calculating Ootf for color transfer %d", |
1940 | 0 | hdr_intent->ct); |
1941 | 0 | return status; |
1942 | 0 | } |
1943 | | |
1944 | 0 | ColorTransformFn hdrInvOetf = getInverseOetfFn(hdr_intent->ct); |
1945 | 0 | if (hdrInvOetf == nullptr) { |
1946 | 0 | uhdr_error_info_t status; |
1947 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1948 | 0 | status.has_detail = 1; |
1949 | 0 | snprintf(status.detail, sizeof status.detail, |
1950 | 0 | "No implementation available for converting transfer characteristics %d to linear", |
1951 | 0 | hdr_intent->ct); |
1952 | 0 | return status; |
1953 | 0 | } |
1954 | | |
1955 | 0 | float hdr_white_nits = getReferenceDisplayPeakLuminanceInNits(hdr_intent->ct); |
1956 | 0 | if (hdr_white_nits == -1.0f) { |
1957 | 0 | uhdr_error_info_t status; |
1958 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1959 | 0 | status.has_detail = 1; |
1960 | 0 | snprintf(status.detail, sizeof status.detail, |
1961 | 0 | "received invalid peak brightness %f nits for hdr reference display with color " |
1962 | 0 | "transfer %d ", |
1963 | 0 | hdr_white_nits, hdr_intent->ct); |
1964 | 0 | return status; |
1965 | 0 | } |
1966 | | |
1967 | 0 | GetPixelFn get_pixel_fn = getPixelFn(hdr_intent->fmt); |
1968 | 0 | if (get_pixel_fn == nullptr) { |
1969 | 0 | uhdr_error_info_t status; |
1970 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1971 | 0 | status.has_detail = 1; |
1972 | 0 | snprintf(status.detail, sizeof status.detail, |
1973 | 0 | "No implementation available for reading pixels for color format %d", hdr_intent->fmt); |
1974 | 0 | return status; |
1975 | 0 | } |
1976 | | |
1977 | 0 | PutPixelFn put_pixel_fn = putPixelFn(sdr_intent->fmt); |
1978 | | // for subsampled formats, we are writing to raw image buffers directly instead of using |
1979 | | // put_pixel_fn |
1980 | 0 | if (put_pixel_fn == nullptr && sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { |
1981 | 0 | uhdr_error_info_t status; |
1982 | 0 | status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; |
1983 | 0 | status.has_detail = 1; |
1984 | 0 | snprintf(status.detail, sizeof status.detail, |
1985 | 0 | "No implementation available for writing pixels for color format %d", sdr_intent->fmt); |
1986 | 0 | return status; |
1987 | 0 | } |
1988 | | |
1989 | 0 | sdr_intent->cg = UHDR_CG_DISPLAY_P3; |
1990 | 0 | sdr_intent->ct = UHDR_CT_SRGB; |
1991 | 0 | sdr_intent->range = UHDR_CR_FULL_RANGE; |
1992 | |
|
1993 | 0 | ColorTransformFn hdrGamutConversionFn = getGamutConversionFn(sdr_intent->cg, hdr_intent->cg); |
1994 | |
|
1995 | 0 | unsigned int height = hdr_intent->h; |
1996 | 0 | const int threads = (std::min)(GetCPUCoreCount(), 4u); |
1997 | | // for 420 subsampling, process 2 rows at once |
1998 | 0 | const int jobSizeInRows = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1; |
1999 | 0 | unsigned int rowStep = threads == 1 ? height : jobSizeInRows; |
2000 | 0 | JobQueue jobQueue; |
2001 | 0 | std::function<void()> toneMapInternal; |
2002 | |
|
2003 | 0 | toneMapInternal = [hdr_intent, sdr_intent, hdrInvOetf, hdrGamutConversionFn, hdrYuvToRgbFn, |
2004 | 0 | hdr_white_nits, get_pixel_fn, put_pixel_fn, hdrLuminanceFn, hdrOotfFn, |
2005 | 0 | &jobQueue]() -> void { |
2006 | 0 | unsigned int rowStart, rowEnd; |
2007 | 0 | const int hfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1; |
2008 | 0 | const int vfactor = hdr_intent->fmt == UHDR_IMG_FMT_24bppYCbCrP010 ? 2 : 1; |
2009 | 0 | const bool isHdrIntentRgb = isPixelFormatRgb(hdr_intent->fmt); |
2010 | 0 | const bool isSdrIntentRgb = isPixelFormatRgb(sdr_intent->fmt); |
2011 | 0 | const bool is_normalized = hdr_intent->ct != UHDR_CT_LINEAR; |
2012 | 0 | uint8_t* luma_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_Y]); |
2013 | 0 | uint8_t* cb_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_U]); |
2014 | 0 | uint8_t* cr_data = reinterpret_cast<uint8_t*>(sdr_intent->planes[UHDR_PLANE_V]); |
2015 | 0 | size_t luma_stride = sdr_intent->stride[UHDR_PLANE_Y]; |
2016 | 0 | size_t cb_stride = sdr_intent->stride[UHDR_PLANE_U]; |
2017 | 0 | size_t cr_stride = sdr_intent->stride[UHDR_PLANE_V]; |
2018 | |
|
2019 | 0 | while (jobQueue.dequeueJob(rowStart, rowEnd)) { |
2020 | 0 | for (size_t y = rowStart; y < rowEnd; y += vfactor) { |
2021 | 0 | for (size_t x = 0; x < hdr_intent->w; x += hfactor) { |
2022 | | // meant for p010 input |
2023 | 0 | float sdr_u_gamma = 0.0f; |
2024 | 0 | float sdr_v_gamma = 0.0f; |
2025 | |
|
2026 | 0 | for (int i = 0; i < vfactor; i++) { |
2027 | 0 | for (int j = 0; j < hfactor; j++) { |
2028 | 0 | Color hdr_rgb_gamma; |
2029 | |
|
2030 | 0 | if (isHdrIntentRgb) { |
2031 | 0 | hdr_rgb_gamma = get_pixel_fn(hdr_intent, x + j, y + i); |
2032 | 0 | } else { |
2033 | 0 | Color hdr_yuv_gamma = get_pixel_fn(hdr_intent, x + j, y + i); |
2034 | 0 | hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); |
2035 | 0 | } |
2036 | 0 | Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); |
2037 | 0 | hdr_rgb = hdrOotfFn(hdr_rgb, hdrLuminanceFn); |
2038 | |
|
2039 | 0 | GlobalTonemapOutputs tonemap_outputs = globalTonemap( |
2040 | 0 | {hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, hdr_white_nits / kSdrWhiteNits, is_normalized); |
2041 | 0 | Color sdr_rgb_linear_bt2100 = { |
2042 | 0 | {{tonemap_outputs.rgb_out[0], tonemap_outputs.rgb_out[1], |
2043 | 0 | tonemap_outputs.rgb_out[2]}}}; |
2044 | 0 | Color sdr_rgb = hdrGamutConversionFn(sdr_rgb_linear_bt2100); |
2045 | | |
2046 | | // Hard clip out-of-gamut values; |
2047 | 0 | sdr_rgb = clampPixelFloat(sdr_rgb); |
2048 | |
|
2049 | 0 | Color sdr_rgb_gamma = srgbOetf(sdr_rgb); |
2050 | 0 | if (isSdrIntentRgb) { |
2051 | 0 | put_pixel_fn(sdr_intent, (x + j), (y + i), sdr_rgb_gamma); |
2052 | 0 | } else { |
2053 | 0 | Color sdr_yuv_gamma = p3RgbToYuv(sdr_rgb_gamma); |
2054 | 0 | sdr_yuv_gamma += {{{0.0f, 0.5f, 0.5f}}}; |
2055 | 0 | if (sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { |
2056 | 0 | put_pixel_fn(sdr_intent, (x + j), (y + i), sdr_yuv_gamma); |
2057 | 0 | } else { |
2058 | 0 | size_t out_y_idx = (y + i) * luma_stride + x + j; |
2059 | 0 | luma_data[out_y_idx] = ScaleTo8Bit(sdr_yuv_gamma.y); |
2060 | |
|
2061 | 0 | sdr_u_gamma += sdr_yuv_gamma.u; |
2062 | 0 | sdr_v_gamma += sdr_yuv_gamma.v; |
2063 | 0 | } |
2064 | 0 | } |
2065 | 0 | } |
2066 | 0 | } |
2067 | 0 | if (sdr_intent->fmt == UHDR_IMG_FMT_12bppYCbCr420) { |
2068 | 0 | sdr_u_gamma /= (hfactor * vfactor); |
2069 | 0 | sdr_v_gamma /= (hfactor * vfactor); |
2070 | 0 | cb_data[x / hfactor + (y / vfactor) * cb_stride] = ScaleTo8Bit(sdr_u_gamma); |
2071 | 0 | cr_data[x / hfactor + (y / vfactor) * cr_stride] = ScaleTo8Bit(sdr_v_gamma); |
2072 | 0 | } |
2073 | 0 | } |
2074 | 0 | } |
2075 | 0 | } |
2076 | 0 | }; |
2077 | | |
2078 | | // tone map |
2079 | 0 | std::vector<std::thread> workers; |
2080 | 0 | for (int th = 0; th < threads - 1; th++) { |
2081 | 0 | workers.push_back(std::thread(toneMapInternal)); |
2082 | 0 | } |
2083 | |
|
2084 | 0 | for (unsigned int rowStart = 0; rowStart < height;) { |
2085 | 0 | unsigned int rowEnd = (std::min)(rowStart + rowStep, height); |
2086 | 0 | jobQueue.enqueueJob(rowStart, rowEnd); |
2087 | 0 | rowStart = rowEnd; |
2088 | 0 | } |
2089 | 0 | jobQueue.markQueueForEnd(); |
2090 | 0 | toneMapInternal(); |
2091 | 0 | std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); |
2092 | |
|
2093 | 0 | return g_no_error; |
2094 | 0 | } |
2095 | | |
2096 | | status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, |
2097 | | jr_uncompressed_ptr yuv420_image_ptr, |
2098 | | ultrahdr_transfer_function hdr_tf, |
2099 | 0 | jr_compressed_ptr dest_ptr) { |
2100 | 0 | if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) { |
2101 | 0 | ALOGE("Received nullptr for input p010 image"); |
2102 | 0 | return ERROR_JPEGR_BAD_PTR; |
2103 | 0 | } |
2104 | 0 | if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) { |
2105 | 0 | ALOGE("Image dimensions cannot be odd, image dimensions %ux%u", p010_image_ptr->width, |
2106 | 0 | p010_image_ptr->height); |
2107 | 0 | return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; |
2108 | 0 | } |
2109 | 0 | if ((int)p010_image_ptr->width < kMinWidth || (int)p010_image_ptr->height < kMinHeight) { |
2110 | 0 | ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %ux%u", kMinWidth, |
2111 | 0 | kMinHeight, p010_image_ptr->width, p010_image_ptr->height); |
2112 | 0 | return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; |
2113 | 0 | } |
2114 | 0 | if ((int)p010_image_ptr->width > kMaxWidth || (int)p010_image_ptr->height > kMaxHeight) { |
2115 | 0 | ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %ux%u", kMaxWidth, |
2116 | 0 | kMaxHeight, p010_image_ptr->width, p010_image_ptr->height); |
2117 | 0 | return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; |
2118 | 0 | } |
2119 | 0 | if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || |
2120 | 0 | p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { |
2121 | 0 | ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut); |
2122 | 0 | return ERROR_JPEGR_INVALID_COLORGAMUT; |
2123 | 0 | } |
2124 | 0 | if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) { |
2125 | 0 | ALOGE("Luma stride must not be smaller than width, stride=%u, width=%u", |
2126 | 0 | p010_image_ptr->luma_stride, p010_image_ptr->width); |
2127 | 0 | return ERROR_JPEGR_INVALID_STRIDE; |
2128 | 0 | } |
2129 | 0 | if (p010_image_ptr->chroma_data != nullptr && |
2130 | 0 | p010_image_ptr->chroma_stride < p010_image_ptr->width) { |
2131 | 0 | ALOGE("Chroma stride must not be smaller than width, stride=%u, width=%u", |
2132 | 0 | p010_image_ptr->chroma_stride, p010_image_ptr->width); |
2133 | 0 | return ERROR_JPEGR_INVALID_STRIDE; |
2134 | 0 | } |
2135 | 0 | if (dest_ptr == nullptr || dest_ptr->data == nullptr) { |
2136 | 0 | ALOGE("Received nullptr for destination"); |
2137 | 0 | return ERROR_JPEGR_BAD_PTR; |
2138 | 0 | } |
2139 | 0 | if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) { |
2140 | 0 | ALOGE("Invalid hdr transfer function %d", hdr_tf); |
2141 | 0 | return ERROR_JPEGR_INVALID_TRANS_FUNC; |
2142 | 0 | } |
2143 | 0 | if (mMapDimensionScaleFactor <= 0 || mMapDimensionScaleFactor > 128) { |
2144 | 0 | ALOGE("gainmap scale factor is ecpected to be in range (0, 128], received %d", |
2145 | 0 | mMapDimensionScaleFactor); |
2146 | 0 | return ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR; |
2147 | 0 | } |
2148 | 0 | if (mMapCompressQuality < 0 || mMapCompressQuality > 100) { |
2149 | 0 | ALOGE("invalid quality factor %d, expects in range [0-100]", mMapCompressQuality); |
2150 | 0 | return ERROR_JPEGR_INVALID_QUALITY_FACTOR; |
2151 | 0 | } |
2152 | 0 | if (!std::isfinite(mGamma) || mGamma <= 0.0f) { |
2153 | 0 | ALOGE("unsupported gainmap gamma %f, expects to be > 0", mGamma); |
2154 | 0 | return ERROR_JPEGR_INVALID_GAMMA; |
2155 | 0 | } |
2156 | 0 | if (mEncPreset != UHDR_USAGE_REALTIME && mEncPreset != UHDR_USAGE_BEST_QUALITY) { |
2157 | 0 | ALOGE("invalid preset %d, expects one of {UHDR_USAGE_REALTIME, UHDR_USAGE_BEST_QUALITY}", |
2158 | 0 | mEncPreset); |
2159 | 0 | return ERROR_JPEGR_INVALID_ENC_PRESET; |
2160 | 0 | } |
2161 | 0 | if (!std::isfinite(mMinContentBoost) || !std::isfinite(mMaxContentBoost) || |
2162 | 0 | mMaxContentBoost < mMinContentBoost || mMinContentBoost <= 0.0f) { |
2163 | 0 | ALOGE("Invalid min boost / max boost configuration. Configured max boost %f, min boost %f", |
2164 | 0 | mMaxContentBoost, mMinContentBoost); |
2165 | 0 | return ERROR_JPEGR_INVALID_DISPLAY_BOOST; |
2166 | 0 | } |
2167 | 0 | if ((!std::isfinite(mTargetDispPeakBrightness) || |
2168 | 0 | mTargetDispPeakBrightness < ultrahdr::kSdrWhiteNits || |
2169 | 0 | mTargetDispPeakBrightness > ultrahdr::kPqMaxNits) && |
2170 | 0 | mTargetDispPeakBrightness != -1.0f) { |
2171 | 0 | ALOGE("unexpected target display peak brightness nits %f, expects to be with in range [%f %f]", |
2172 | 0 | mTargetDispPeakBrightness, ultrahdr::kSdrWhiteNits, ultrahdr::kPqMaxNits); |
2173 | 0 | return ERROR_JPEGR_INVALID_TARGET_DISP_PEAK_BRIGHTNESS; |
2174 | 0 | } |
2175 | 0 | if (yuv420_image_ptr == nullptr) { |
2176 | 0 | return JPEGR_NO_ERROR; |
2177 | 0 | } |
2178 | 0 | if (yuv420_image_ptr->data == nullptr) { |
2179 | 0 | ALOGE("Received nullptr for uncompressed 420 image"); |
2180 | 0 | return ERROR_JPEGR_BAD_PTR; |
2181 | 0 | } |
2182 | 0 | if (yuv420_image_ptr->luma_stride != 0 && |
2183 | 0 | yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) { |
2184 | 0 | ALOGE("Luma stride must not be smaller than width, stride=%u, width=%u", |
2185 | 0 | yuv420_image_ptr->luma_stride, yuv420_image_ptr->width); |
2186 | 0 | return ERROR_JPEGR_INVALID_STRIDE; |
2187 | 0 | } |
2188 | 0 | if (yuv420_image_ptr->chroma_data != nullptr && |
2189 | 0 | yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) { |
2190 | 0 | ALOGE("Chroma stride must not be smaller than (width / 2), stride=%u, width=%u", |
2191 | 0 | yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width); |
2192 | 0 | return ERROR_JPEGR_INVALID_STRIDE; |
2193 | 0 | } |
2194 | 0 | if (p010_image_ptr->width != yuv420_image_ptr->width || |
2195 | 0 | p010_image_ptr->height != yuv420_image_ptr->height) { |
2196 | 0 | ALOGE("Image resolutions mismatch: P010: %ux%u, YUV420: %ux%u", p010_image_ptr->width, |
2197 | 0 | p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height); |
2198 | 0 | return ERROR_JPEGR_RESOLUTION_MISMATCH; |
2199 | 0 | } |
2200 | 0 | if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || |
2201 | 0 | yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { |
2202 | 0 | ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut); |
2203 | 0 | return ERROR_JPEGR_INVALID_COLORGAMUT; |
2204 | 0 | } |
2205 | 0 | return JPEGR_NO_ERROR; |
2206 | 0 | } |
2207 | | |
2208 | | status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, |
2209 | | jr_uncompressed_ptr yuv420_image_ptr, |
2210 | | ultrahdr_transfer_function hdr_tf, |
2211 | 0 | jr_compressed_ptr dest_ptr, int quality) { |
2212 | 0 | if (quality < 0 || quality > 100) { |
2213 | 0 | ALOGE("quality factor is out side range [0-100], quality factor : %d", quality); |
2214 | 0 | return ERROR_JPEGR_INVALID_QUALITY_FACTOR; |
2215 | 0 | } |
2216 | 0 | return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr); |
2217 | 0 | } |
2218 | | |
2219 | 0 | uhdr_color_transfer_t map_legacy_ct_to_ct(ultrahdr::ultrahdr_transfer_function ct) { |
2220 | 0 | switch (ct) { |
2221 | 0 | case ultrahdr::ULTRAHDR_TF_HLG: |
2222 | 0 | return UHDR_CT_HLG; |
2223 | 0 | case ultrahdr::ULTRAHDR_TF_PQ: |
2224 | 0 | return UHDR_CT_PQ; |
2225 | 0 | case ultrahdr::ULTRAHDR_TF_LINEAR: |
2226 | 0 | return UHDR_CT_LINEAR; |
2227 | 0 | case ultrahdr::ULTRAHDR_TF_SRGB: |
2228 | 0 | return UHDR_CT_SRGB; |
2229 | 0 | default: |
2230 | 0 | return UHDR_CT_UNSPECIFIED; |
2231 | 0 | } |
2232 | 0 | } |
2233 | | |
2234 | 0 | uhdr_color_gamut_t map_legacy_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) { |
2235 | 0 | switch (cg) { |
2236 | 0 | case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100: |
2237 | 0 | return UHDR_CG_BT_2100; |
2238 | 0 | case ultrahdr::ULTRAHDR_COLORGAMUT_BT709: |
2239 | 0 | return UHDR_CG_BT_709; |
2240 | 0 | case ultrahdr::ULTRAHDR_COLORGAMUT_P3: |
2241 | 0 | return UHDR_CG_DISPLAY_P3; |
2242 | 0 | default: |
2243 | 0 | return UHDR_CG_UNSPECIFIED; |
2244 | 0 | } |
2245 | 0 | } |
2246 | | |
2247 | 0 | ultrahdr::ultrahdr_color_gamut map_cg_to_legacy_cg(uhdr_color_gamut_t cg) { |
2248 | 0 | switch (cg) { |
2249 | 0 | case UHDR_CG_BT_2100: |
2250 | 0 | return ultrahdr::ULTRAHDR_COLORGAMUT_BT2100; |
2251 | 0 | case UHDR_CG_BT_709: |
2252 | 0 | return ultrahdr::ULTRAHDR_COLORGAMUT_BT709; |
2253 | 0 | case UHDR_CG_DISPLAY_P3: |
2254 | 0 | return ultrahdr::ULTRAHDR_COLORGAMUT_P3; |
2255 | 0 | default: |
2256 | 0 | return ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED; |
2257 | 0 | } |
2258 | 0 | } |
2259 | | |
2260 | | /* Encode API-0 */ |
2261 | | status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, |
2262 | 0 | jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { |
2263 | | // validate input arguments |
2264 | 0 | JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality)); |
2265 | 0 | if (exif != nullptr && exif->data == nullptr) { |
2266 | 0 | ALOGE("received nullptr for exif metadata"); |
2267 | 0 | return ERROR_JPEGR_BAD_PTR; |
2268 | 0 | } |
2269 | | |
2270 | | // clean up input structure for later usage |
2271 | 0 | jpegr_uncompressed_struct p010_image = *p010_image_ptr; |
2272 | 0 | if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; |
2273 | 0 | if (!p010_image.chroma_data) { |
2274 | 0 | uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data); |
2275 | 0 | p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height; |
2276 | 0 | p010_image.chroma_stride = p010_image.luma_stride; |
2277 | 0 | } |
2278 | |
|
2279 | 0 | uhdr_raw_image_t hdr_intent; |
2280 | 0 | hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; |
2281 | 0 | hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); |
2282 | 0 | hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); |
2283 | 0 | hdr_intent.range = p010_image.colorRange; |
2284 | 0 | hdr_intent.w = p010_image.width; |
2285 | 0 | hdr_intent.h = p010_image.height; |
2286 | 0 | hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; |
2287 | 0 | hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; |
2288 | 0 | hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; |
2289 | 0 | hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; |
2290 | 0 | hdr_intent.planes[UHDR_PLANE_V] = nullptr; |
2291 | 0 | hdr_intent.stride[UHDR_PLANE_V] = 0; |
2292 | |
|
2293 | 0 | uhdr_compressed_image_t output; |
2294 | 0 | output.data = dest->data; |
2295 | 0 | output.data_sz = 0; |
2296 | 0 | output.capacity = dest->maxLength; |
2297 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2298 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2299 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2300 | |
|
2301 | 0 | uhdr_mem_block_t exifBlock; |
2302 | 0 | if (exif) { |
2303 | 0 | exifBlock.data = exif->data; |
2304 | 0 | exifBlock.data_sz = exifBlock.capacity = exif->length; |
2305 | 0 | } |
2306 | |
|
2307 | 0 | auto result = encodeJPEGR(&hdr_intent, &output, quality, exif ? &exifBlock : nullptr); |
2308 | 0 | if (result.error_code == UHDR_CODEC_OK) { |
2309 | 0 | dest->colorGamut = map_cg_to_legacy_cg(output.cg); |
2310 | 0 | dest->length = output.data_sz; |
2311 | 0 | } |
2312 | |
|
2313 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2314 | 0 | } |
2315 | | |
2316 | | /* Encode API-1 */ |
2317 | | status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, |
2318 | | jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf, |
2319 | 0 | jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { |
2320 | | // validate input arguments |
2321 | 0 | if (yuv420_image_ptr == nullptr) { |
2322 | 0 | ALOGE("received nullptr for uncompressed 420 image"); |
2323 | 0 | return ERROR_JPEGR_BAD_PTR; |
2324 | 0 | } |
2325 | 0 | if (exif != nullptr && exif->data == nullptr) { |
2326 | 0 | ALOGE("received nullptr for exif metadata"); |
2327 | 0 | return ERROR_JPEGR_BAD_PTR; |
2328 | 0 | } |
2329 | 0 | JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality)) |
2330 | | |
2331 | | // clean up input structure for later usage |
2332 | 0 | jpegr_uncompressed_struct p010_image = *p010_image_ptr; |
2333 | 0 | if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; |
2334 | 0 | if (!p010_image.chroma_data) { |
2335 | 0 | uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data); |
2336 | 0 | p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height; |
2337 | 0 | p010_image.chroma_stride = p010_image.luma_stride; |
2338 | 0 | } |
2339 | 0 | uhdr_raw_image_t hdr_intent; |
2340 | 0 | hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; |
2341 | 0 | hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); |
2342 | 0 | hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); |
2343 | 0 | hdr_intent.range = p010_image.colorRange; |
2344 | 0 | hdr_intent.w = p010_image.width; |
2345 | 0 | hdr_intent.h = p010_image.height; |
2346 | 0 | hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; |
2347 | 0 | hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; |
2348 | 0 | hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; |
2349 | 0 | hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; |
2350 | 0 | hdr_intent.planes[UHDR_PLANE_V] = nullptr; |
2351 | 0 | hdr_intent.stride[UHDR_PLANE_V] = 0; |
2352 | |
|
2353 | 0 | jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; |
2354 | 0 | if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; |
2355 | 0 | if (!yuv420_image.chroma_data) { |
2356 | 0 | uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data); |
2357 | 0 | yuv420_image.chroma_data = data + (size_t)yuv420_image.luma_stride * yuv420_image.height; |
2358 | 0 | yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; |
2359 | 0 | } |
2360 | 0 | uhdr_raw_image_t sdrRawImg; |
2361 | 0 | sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420; |
2362 | 0 | sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut); |
2363 | 0 | sdrRawImg.ct = UHDR_CT_SRGB; |
2364 | 0 | sdrRawImg.range = yuv420_image.colorRange; |
2365 | 0 | sdrRawImg.w = yuv420_image.width; |
2366 | 0 | sdrRawImg.h = yuv420_image.height; |
2367 | 0 | sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data; |
2368 | 0 | sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride; |
2369 | 0 | sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data; |
2370 | 0 | sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride; |
2371 | 0 | uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data); |
2372 | 0 | data += (yuv420_image.height * yuv420_image.chroma_stride) / 2; |
2373 | 0 | sdrRawImg.planes[UHDR_PLANE_V] = data; |
2374 | 0 | sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride; |
2375 | 0 | auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg); |
2376 | |
|
2377 | 0 | uhdr_compressed_image_t output; |
2378 | 0 | output.data = dest->data; |
2379 | 0 | output.data_sz = 0; |
2380 | 0 | output.capacity = dest->maxLength; |
2381 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2382 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2383 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2384 | |
|
2385 | 0 | uhdr_mem_block_t exifBlock; |
2386 | 0 | if (exif) { |
2387 | 0 | exifBlock.data = exif->data; |
2388 | 0 | exifBlock.data_sz = exifBlock.capacity = exif->length; |
2389 | 0 | } |
2390 | |
|
2391 | 0 | auto result = |
2392 | 0 | encodeJPEGR(&hdr_intent, sdr_intent.get(), &output, quality, exif ? &exifBlock : nullptr); |
2393 | 0 | if (result.error_code == UHDR_CODEC_OK) { |
2394 | 0 | dest->colorGamut = map_cg_to_legacy_cg(output.cg); |
2395 | 0 | dest->length = output.data_sz; |
2396 | 0 | } |
2397 | |
|
2398 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2399 | 0 | } |
2400 | | |
2401 | | /* Encode API-2 */ |
2402 | | status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, |
2403 | | jr_uncompressed_ptr yuv420_image_ptr, |
2404 | | jr_compressed_ptr yuv420jpg_image_ptr, |
2405 | 0 | ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { |
2406 | | // validate input arguments |
2407 | 0 | if (yuv420_image_ptr == nullptr) { |
2408 | 0 | ALOGE("received nullptr for uncompressed 420 image"); |
2409 | 0 | return ERROR_JPEGR_BAD_PTR; |
2410 | 0 | } |
2411 | 0 | if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { |
2412 | 0 | ALOGE("received nullptr for compressed jpeg image"); |
2413 | 0 | return ERROR_JPEGR_BAD_PTR; |
2414 | 0 | } |
2415 | 0 | JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest)) |
2416 | | |
2417 | | // clean up input structure for later usage |
2418 | 0 | jpegr_uncompressed_struct p010_image = *p010_image_ptr; |
2419 | 0 | if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; |
2420 | 0 | if (!p010_image.chroma_data) { |
2421 | 0 | uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data); |
2422 | 0 | p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height; |
2423 | 0 | p010_image.chroma_stride = p010_image.luma_stride; |
2424 | 0 | } |
2425 | 0 | uhdr_raw_image_t hdr_intent; |
2426 | 0 | hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; |
2427 | 0 | hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); |
2428 | 0 | hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); |
2429 | 0 | hdr_intent.range = p010_image.colorRange; |
2430 | 0 | hdr_intent.w = p010_image.width; |
2431 | 0 | hdr_intent.h = p010_image.height; |
2432 | 0 | hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; |
2433 | 0 | hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; |
2434 | 0 | hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; |
2435 | 0 | hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; |
2436 | 0 | hdr_intent.planes[UHDR_PLANE_V] = nullptr; |
2437 | 0 | hdr_intent.stride[UHDR_PLANE_V] = 0; |
2438 | |
|
2439 | 0 | jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; |
2440 | 0 | if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; |
2441 | 0 | if (!yuv420_image.chroma_data) { |
2442 | 0 | uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data); |
2443 | 0 | yuv420_image.chroma_data = data + (size_t)yuv420_image.luma_stride * p010_image.height; |
2444 | 0 | yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; |
2445 | 0 | } |
2446 | 0 | uhdr_raw_image_t sdrRawImg; |
2447 | 0 | sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420; |
2448 | 0 | sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut); |
2449 | 0 | sdrRawImg.ct = UHDR_CT_SRGB; |
2450 | 0 | sdrRawImg.range = yuv420_image.colorRange; |
2451 | 0 | sdrRawImg.w = yuv420_image.width; |
2452 | 0 | sdrRawImg.h = yuv420_image.height; |
2453 | 0 | sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data; |
2454 | 0 | sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride; |
2455 | 0 | sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data; |
2456 | 0 | sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride; |
2457 | 0 | uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data); |
2458 | 0 | data += (yuv420_image.height * yuv420_image.chroma_stride) / 2; |
2459 | 0 | sdrRawImg.planes[UHDR_PLANE_V] = data; |
2460 | 0 | sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride; |
2461 | 0 | auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg); |
2462 | |
|
2463 | 0 | uhdr_compressed_image_t input; |
2464 | 0 | input.data = yuv420jpg_image_ptr->data; |
2465 | 0 | input.data_sz = yuv420jpg_image_ptr->length; |
2466 | 0 | input.capacity = yuv420jpg_image_ptr->maxLength; |
2467 | 0 | input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut); |
2468 | 0 | input.ct = UHDR_CT_UNSPECIFIED; |
2469 | 0 | input.range = UHDR_CR_UNSPECIFIED; |
2470 | |
|
2471 | 0 | uhdr_compressed_image_t output; |
2472 | 0 | output.data = dest->data; |
2473 | 0 | output.data_sz = 0; |
2474 | 0 | output.capacity = dest->maxLength; |
2475 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2476 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2477 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2478 | |
|
2479 | 0 | auto result = encodeJPEGR(&hdr_intent, sdr_intent.get(), &input, &output); |
2480 | 0 | if (result.error_code == UHDR_CODEC_OK) { |
2481 | 0 | dest->colorGamut = map_cg_to_legacy_cg(output.cg); |
2482 | 0 | dest->length = output.data_sz; |
2483 | 0 | } |
2484 | |
|
2485 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2486 | 0 | } |
2487 | | |
2488 | | /* Encode API-3 */ |
2489 | | status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, |
2490 | | jr_compressed_ptr yuv420jpg_image_ptr, |
2491 | 0 | ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { |
2492 | | // validate input arguments |
2493 | 0 | if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { |
2494 | 0 | ALOGE("received nullptr for compressed jpeg image"); |
2495 | 0 | return ERROR_JPEGR_BAD_PTR; |
2496 | 0 | } |
2497 | 0 | JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest)) |
2498 | | |
2499 | | // clean up input structure for later usage |
2500 | 0 | jpegr_uncompressed_struct p010_image = *p010_image_ptr; |
2501 | 0 | if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; |
2502 | 0 | if (!p010_image.chroma_data) { |
2503 | 0 | uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data); |
2504 | 0 | p010_image.chroma_data = data + (size_t)p010_image.luma_stride * p010_image.height; |
2505 | 0 | p010_image.chroma_stride = p010_image.luma_stride; |
2506 | 0 | } |
2507 | 0 | uhdr_raw_image_t hdr_intent; |
2508 | 0 | hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; |
2509 | 0 | hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); |
2510 | 0 | hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); |
2511 | 0 | hdr_intent.range = p010_image.colorRange; |
2512 | 0 | hdr_intent.w = p010_image.width; |
2513 | 0 | hdr_intent.h = p010_image.height; |
2514 | 0 | hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; |
2515 | 0 | hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; |
2516 | 0 | hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; |
2517 | 0 | hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; |
2518 | 0 | hdr_intent.planes[UHDR_PLANE_V] = nullptr; |
2519 | 0 | hdr_intent.stride[UHDR_PLANE_V] = 0; |
2520 | |
|
2521 | 0 | uhdr_compressed_image_t input; |
2522 | 0 | input.data = yuv420jpg_image_ptr->data; |
2523 | 0 | input.data_sz = yuv420jpg_image_ptr->length; |
2524 | 0 | input.capacity = yuv420jpg_image_ptr->maxLength; |
2525 | 0 | input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut); |
2526 | 0 | input.ct = UHDR_CT_UNSPECIFIED; |
2527 | 0 | input.range = UHDR_CR_UNSPECIFIED; |
2528 | |
|
2529 | 0 | uhdr_compressed_image_t output; |
2530 | 0 | output.data = dest->data; |
2531 | 0 | output.data_sz = 0; |
2532 | 0 | output.capacity = dest->maxLength; |
2533 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2534 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2535 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2536 | |
|
2537 | 0 | auto result = encodeJPEGR(&hdr_intent, &input, &output); |
2538 | 0 | if (result.error_code == UHDR_CODEC_OK) { |
2539 | 0 | dest->colorGamut = map_cg_to_legacy_cg(output.cg); |
2540 | 0 | dest->length = output.data_sz; |
2541 | 0 | } |
2542 | |
|
2543 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2544 | 0 | } |
2545 | | |
2546 | | /* Encode API-4 */ |
2547 | | status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, |
2548 | | jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, |
2549 | 0 | jr_compressed_ptr dest) { |
2550 | 0 | if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { |
2551 | 0 | ALOGE("received nullptr for compressed jpeg image"); |
2552 | 0 | return ERROR_JPEGR_BAD_PTR; |
2553 | 0 | } |
2554 | 0 | if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) { |
2555 | 0 | ALOGE("received nullptr for compressed gain map"); |
2556 | 0 | return ERROR_JPEGR_BAD_PTR; |
2557 | 0 | } |
2558 | 0 | if (dest == nullptr || dest->data == nullptr) { |
2559 | 0 | ALOGE("received nullptr for destination"); |
2560 | 0 | return ERROR_JPEGR_BAD_PTR; |
2561 | 0 | } |
2562 | | |
2563 | 0 | uhdr_compressed_image_t input; |
2564 | 0 | input.data = yuv420jpg_image_ptr->data; |
2565 | 0 | input.data_sz = yuv420jpg_image_ptr->length; |
2566 | 0 | input.capacity = yuv420jpg_image_ptr->maxLength; |
2567 | 0 | input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut); |
2568 | 0 | input.ct = UHDR_CT_UNSPECIFIED; |
2569 | 0 | input.range = UHDR_CR_UNSPECIFIED; |
2570 | |
|
2571 | 0 | uhdr_compressed_image_t gainmap; |
2572 | 0 | gainmap.data = gainmapjpg_image_ptr->data; |
2573 | 0 | gainmap.data_sz = gainmapjpg_image_ptr->length; |
2574 | 0 | gainmap.capacity = gainmapjpg_image_ptr->maxLength; |
2575 | 0 | gainmap.cg = UHDR_CG_UNSPECIFIED; |
2576 | 0 | gainmap.ct = UHDR_CT_UNSPECIFIED; |
2577 | 0 | gainmap.range = UHDR_CR_UNSPECIFIED; |
2578 | |
|
2579 | 0 | uhdr_compressed_image_t output; |
2580 | 0 | output.data = dest->data; |
2581 | 0 | output.data_sz = 0; |
2582 | 0 | output.capacity = dest->maxLength; |
2583 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2584 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2585 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2586 | |
|
2587 | 0 | uhdr_gainmap_metadata_ext_t meta(metadata->version); |
2588 | 0 | meta.hdr_capacity_max = metadata->hdrCapacityMax; |
2589 | 0 | meta.hdr_capacity_min = metadata->hdrCapacityMin; |
2590 | 0 | std::fill_n(meta.gamma, 3, metadata->gamma); |
2591 | 0 | std::fill_n(meta.offset_sdr, 3, metadata->offsetSdr); |
2592 | 0 | std::fill_n(meta.offset_hdr, 3, metadata->offsetHdr); |
2593 | 0 | std::fill_n(meta.max_content_boost, 3, metadata->maxContentBoost); |
2594 | 0 | std::fill_n(meta.min_content_boost, 3, metadata->minContentBoost); |
2595 | 0 | meta.use_base_cg = true; |
2596 | |
|
2597 | 0 | auto result = encodeJPEGR(&input, &gainmap, &meta, &output); |
2598 | 0 | if (result.error_code == UHDR_CODEC_OK) { |
2599 | 0 | dest->colorGamut = map_cg_to_legacy_cg(output.cg); |
2600 | 0 | dest->length = output.data_sz; |
2601 | 0 | } |
2602 | |
|
2603 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2604 | 0 | } |
2605 | | |
2606 | | /* Decode API */ |
2607 | 0 | status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr) { |
2608 | 0 | if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { |
2609 | 0 | ALOGE("received nullptr for compressed jpegr image"); |
2610 | 0 | return ERROR_JPEGR_BAD_PTR; |
2611 | 0 | } |
2612 | 0 | if (jpegr_image_info_ptr == nullptr) { |
2613 | 0 | ALOGE("received nullptr for compressed jpegr info struct"); |
2614 | 0 | return ERROR_JPEGR_BAD_PTR; |
2615 | 0 | } |
2616 | | |
2617 | 0 | uhdr_compressed_image_t input; |
2618 | 0 | input.data = jpegr_image_ptr->data; |
2619 | 0 | input.data_sz = jpegr_image_ptr->length; |
2620 | 0 | input.capacity = jpegr_image_ptr->maxLength; |
2621 | 0 | input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut); |
2622 | 0 | input.ct = UHDR_CT_UNSPECIFIED; |
2623 | 0 | input.range = UHDR_CR_UNSPECIFIED; |
2624 | |
|
2625 | 0 | auto result = getJPEGRInfo(&input, jpegr_image_info_ptr); |
2626 | |
|
2627 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2628 | 0 | } |
2629 | | |
2630 | | status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, |
2631 | | float max_display_boost, jr_exif_ptr exif, |
2632 | | ultrahdr_output_format output_format, |
2633 | 0 | jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) { |
2634 | 0 | if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { |
2635 | 0 | ALOGE("received nullptr for compressed jpegr image"); |
2636 | 0 | return ERROR_JPEGR_BAD_PTR; |
2637 | 0 | } |
2638 | 0 | if (dest == nullptr || dest->data == nullptr) { |
2639 | 0 | ALOGE("received nullptr for dest image"); |
2640 | 0 | return ERROR_JPEGR_BAD_PTR; |
2641 | 0 | } |
2642 | 0 | if (max_display_boost < 1.0f) { |
2643 | 0 | ALOGE("received bad value for max_display_boost %f", max_display_boost); |
2644 | 0 | return ERROR_JPEGR_INVALID_DISPLAY_BOOST; |
2645 | 0 | } |
2646 | 0 | if (exif != nullptr && exif->data == nullptr) { |
2647 | 0 | ALOGE("received nullptr address for exif data"); |
2648 | 0 | return ERROR_JPEGR_BAD_PTR; |
2649 | 0 | } |
2650 | 0 | if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) { |
2651 | 0 | ALOGE("received nullptr address for gainmap data"); |
2652 | 0 | return ERROR_JPEGR_BAD_PTR; |
2653 | 0 | } |
2654 | 0 | if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) { |
2655 | 0 | ALOGE("received bad value for output format %d", output_format); |
2656 | 0 | return ERROR_JPEGR_INVALID_OUTPUT_FORMAT; |
2657 | 0 | } |
2658 | | |
2659 | 0 | uhdr_color_transfer_t ct; |
2660 | 0 | uhdr_img_fmt fmt; |
2661 | 0 | if (output_format == ULTRAHDR_OUTPUT_HDR_HLG) { |
2662 | 0 | fmt = UHDR_IMG_FMT_32bppRGBA1010102; |
2663 | 0 | ct = UHDR_CT_HLG; |
2664 | 0 | } else if (output_format == ULTRAHDR_OUTPUT_HDR_PQ) { |
2665 | 0 | fmt = UHDR_IMG_FMT_32bppRGBA1010102; |
2666 | 0 | ct = UHDR_CT_PQ; |
2667 | 0 | } else if (output_format == ULTRAHDR_OUTPUT_HDR_LINEAR) { |
2668 | 0 | fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat; |
2669 | 0 | ct = UHDR_CT_LINEAR; |
2670 | 0 | } else if (output_format == ULTRAHDR_OUTPUT_SDR) { |
2671 | 0 | fmt = UHDR_IMG_FMT_32bppRGBA8888; |
2672 | 0 | ct = UHDR_CT_SRGB; |
2673 | 0 | } |
2674 | |
|
2675 | 0 | uhdr_compressed_image_t input; |
2676 | 0 | input.data = jpegr_image_ptr->data; |
2677 | 0 | input.data_sz = jpegr_image_ptr->length; |
2678 | 0 | input.capacity = jpegr_image_ptr->maxLength; |
2679 | 0 | input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut); |
2680 | 0 | input.ct = UHDR_CT_UNSPECIFIED; |
2681 | 0 | input.range = UHDR_CR_UNSPECIFIED; |
2682 | |
|
2683 | 0 | jpeg_info_struct primary_image; |
2684 | 0 | jpeg_info_struct gainmap_image; |
2685 | 0 | jpegr_info_struct jpegr_info; |
2686 | 0 | jpegr_info.primaryImgInfo = &primary_image; |
2687 | 0 | jpegr_info.gainmapImgInfo = &gainmap_image; |
2688 | 0 | if (getJPEGRInfo(&input, &jpegr_info).error_code != UHDR_CODEC_OK) return JPEGR_UNKNOWN_ERROR; |
2689 | | |
2690 | 0 | if (exif != nullptr) { |
2691 | 0 | if (exif->length < primary_image.exifData.size()) { |
2692 | 0 | return ERROR_JPEGR_BUFFER_TOO_SMALL; |
2693 | 0 | } |
2694 | 0 | memcpy(exif->data, primary_image.exifData.data(), primary_image.exifData.size()); |
2695 | 0 | exif->length = primary_image.exifData.size(); |
2696 | 0 | } |
2697 | | |
2698 | 0 | uhdr_raw_image_t output; |
2699 | 0 | output.fmt = fmt; |
2700 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2701 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2702 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2703 | 0 | output.w = jpegr_info.width; |
2704 | 0 | output.h = jpegr_info.height; |
2705 | 0 | output.planes[UHDR_PLANE_PACKED] = dest->data; |
2706 | 0 | output.stride[UHDR_PLANE_PACKED] = jpegr_info.width; |
2707 | 0 | output.planes[UHDR_PLANE_U] = nullptr; |
2708 | 0 | output.stride[UHDR_PLANE_U] = 0; |
2709 | 0 | output.planes[UHDR_PLANE_V] = nullptr; |
2710 | 0 | output.stride[UHDR_PLANE_V] = 0; |
2711 | |
|
2712 | 0 | uhdr_raw_image_t output_gm; |
2713 | 0 | if (gainmap_image_ptr) { |
2714 | 0 | output.fmt = |
2715 | 0 | gainmap_image.numComponents == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_24bppRGB888; |
2716 | 0 | output.cg = UHDR_CG_UNSPECIFIED; |
2717 | 0 | output.ct = UHDR_CT_UNSPECIFIED; |
2718 | 0 | output.range = UHDR_CR_UNSPECIFIED; |
2719 | 0 | output.w = gainmap_image.width; |
2720 | 0 | output.h = gainmap_image.height; |
2721 | 0 | output.planes[UHDR_PLANE_PACKED] = gainmap_image_ptr->data; |
2722 | 0 | output.stride[UHDR_PLANE_PACKED] = gainmap_image.width; |
2723 | 0 | output.planes[UHDR_PLANE_U] = nullptr; |
2724 | 0 | output.stride[UHDR_PLANE_U] = 0; |
2725 | 0 | output.planes[UHDR_PLANE_V] = nullptr; |
2726 | 0 | output.stride[UHDR_PLANE_V] = 0; |
2727 | 0 | } |
2728 | |
|
2729 | 0 | uhdr_gainmap_metadata_ext_t meta; |
2730 | 0 | auto result = decodeJPEGR(&input, &output, max_display_boost, ct, fmt, |
2731 | 0 | gainmap_image_ptr ? &output_gm : nullptr, metadata ? &meta : nullptr); |
2732 | |
|
2733 | 0 | if (result.error_code == UHDR_CODEC_OK) { |
2734 | 0 | dest->width = output.w; |
2735 | 0 | dest->height = output.h; |
2736 | 0 | dest->colorGamut = map_cg_to_legacy_cg(output.cg); |
2737 | 0 | dest->colorRange = output.range; |
2738 | 0 | dest->pixelFormat = output.fmt; |
2739 | 0 | dest->chroma_data = nullptr; |
2740 | 0 | if (gainmap_image_ptr) { |
2741 | 0 | gainmap_image_ptr->width = output_gm.w; |
2742 | 0 | gainmap_image_ptr->height = output_gm.h; |
2743 | 0 | gainmap_image_ptr->colorGamut = map_cg_to_legacy_cg(output_gm.cg); |
2744 | 0 | gainmap_image_ptr->colorRange = output_gm.range; |
2745 | 0 | gainmap_image_ptr->pixelFormat = output_gm.fmt; |
2746 | 0 | gainmap_image_ptr->chroma_data = nullptr; |
2747 | 0 | } |
2748 | 0 | if (metadata) { |
2749 | 0 | if (!meta.are_all_channels_identical()) return ERROR_JPEGR_METADATA_ERROR; |
2750 | 0 | metadata->version = meta.version; |
2751 | 0 | metadata->hdrCapacityMax = meta.hdr_capacity_max; |
2752 | 0 | metadata->hdrCapacityMin = meta.hdr_capacity_min; |
2753 | 0 | metadata->gamma = meta.gamma[0]; |
2754 | 0 | metadata->offsetSdr = meta.offset_sdr[0]; |
2755 | 0 | metadata->offsetHdr = meta.offset_hdr[0]; |
2756 | 0 | metadata->maxContentBoost = meta.max_content_boost[0]; |
2757 | 0 | metadata->minContentBoost = meta.min_content_boost[0]; |
2758 | 0 | } |
2759 | 0 | } |
2760 | | |
2761 | 0 | return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; |
2762 | 0 | } |
2763 | | |
2764 | | } // namespace ultrahdr |