From d52e776be9ad99c408cb208ba1f8175fb78147ee Mon Sep 17 00:00:00 2001 From: Ram Mohan M Date: Wed, 19 Jun 2024 13:37:00 +0530 Subject: [PATCH 1/2] Update error checks for newly advertised encoder options - Add error checks for input params of newly added setter functions - update documentation of sample app for newly added options - fix sample app build issues in aosp - communicate error correctly if write to output has failed - update gainmap scale factor if its too large Test: ./ultrahdr_unit_test --- examples/ultrahdr_app.cpp | 75 ++++++++++++++++++++------------------- lib/src/jpegr.cpp | 12 +++++++ lib/src/ultrahdr_api.cpp | 44 +++++++++++++++++++---- ultrahdr_api.h | 66 ++++++++++++++++++++++------------ 4 files changed, 131 insertions(+), 66 deletions(-) diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp index d78db54..b6bbcfb 100644 --- a/examples/ultrahdr_app.cpp +++ b/examples/ultrahdr_app.cpp @@ -253,11 +253,9 @@ class UltraHdrAppInput { uhdr_color_gamut_t sdrCg = UHDR_CG_BT_709, uhdr_color_transfer_t hdrTf = UHDR_CT_HLG, int quality = 95, uhdr_color_transfer_t oTf = UHDR_CT_HLG, - uhdr_img_fmt_t oFmt = UHDR_IMG_FMT_32bppRGBA1010102, - bool use_full_range_color_hdr = false, - int gainmap_scale_factor = 4, - bool use_multi_channel_gainmap = false, - int gainmap_compression_quality = 85) + uhdr_img_fmt_t oFmt = UHDR_IMG_FMT_32bppRGBA1010102, bool isHdrCrFull = false, + int gainmapScaleFactor = 4, int gainmapQuality = 85, + bool enableMultiChannelGainMap = false) : mHdrIntentRawFile(hdrIntentRawFile), mSdrIntentRawFile(sdrIntentRawFile), mSdrIntentCompressedFile(sdrIntentCompressedFile), @@ -275,10 +273,10 @@ class UltraHdrAppInput { mQuality(quality), mOTf(oTf), mOfmt(oFmt), - mFullRange(use_full_range_color_hdr), - mMapDimensionScaleFactor(gainmap_scale_factor), - mMapCompressQuality(gainmap_compression_quality), - mUseMultiChannelGainMap(use_multi_channel_gainmap), + mFullRange(isHdrCrFull), + mMapDimensionScaleFactor(gainmapScaleFactor), + mMapCompressQuality(gainmapQuality), + mUseMultiChannelGainMap(enableMultiChannelGainMap), mMode(0){}; UltraHdrAppInput(const char* uhdrFile, const char* outputFile, @@ -298,6 +296,10 @@ class UltraHdrAppInput { mQuality(95), mOTf(oTf), mOfmt(oFmt), + mFullRange(UHDR_CR_UNSPECIFIED), + mMapDimensionScaleFactor(4), + mMapCompressQuality(85), + mUseMultiChannelGainMap(false), mMode(1){}; ~UltraHdrAppInput() { @@ -367,11 +369,11 @@ class UltraHdrAppInput { const int mQuality; const uhdr_color_transfer_t mOTf; const uhdr_img_fmt_t mOfmt; + const bool mFullRange; + const size_t mMapDimensionScaleFactor; + const int mMapCompressQuality; + const bool mUseMultiChannelGainMap; const int mMode; - bool mFullRange; - size_t mMapDimensionScaleFactor; - int mMapCompressQuality; - bool mUseMultiChannelGainMap; uhdr_raw_image_t mRawP010Image{}; uhdr_raw_image_t mRawRgba1010102Image{}; @@ -631,16 +633,13 @@ bool UltraHdrAppInput::encode() { RET_IF_ERR(uhdr_enc_set_using_multi_channel_gainmap(handle, mUseMultiChannelGainMap)) RET_IF_ERR(uhdr_enc_set_gainmap_scale_factor(handle, mMapDimensionScaleFactor)) #ifdef PROFILE_ENABLE - const int profileCount = 10; Profiler profileEncode; profileEncode.timerStart(); - for (auto i = 0; i < profileCount; i++) { #endif - RET_IF_ERR(uhdr_encode(handle)) + RET_IF_ERR(uhdr_encode(handle)) #ifdef PROFILE_ENABLE - } profileEncode.timerStop(); - auto avgEncTime = profileEncode.elapsedTime() / (profileCount * 1000.f); + auto avgEncTime = profileEncode.elapsedTime() / 1000.f; printf("Average encode time for res %d x %d is %f ms \n", mWidth, mHeight, avgEncTime); #endif @@ -655,10 +654,9 @@ bool UltraHdrAppInput::encode() { mUhdrImage.cg = output->cg; mUhdrImage.ct = output->ct; mUhdrImage.range = output->range; - writeFile(mOutputFile, output->data, output->data_sz); uhdr_release_encoder(handle); - return true; + return writeFile(mOutputFile, mUhdrImage.data, mUhdrImage.data_sz); } bool UltraHdrAppInput::decode() { @@ -685,17 +683,15 @@ bool UltraHdrAppInput::decode() { RET_IF_ERR(uhdr_dec_set_out_img_format(handle, mOfmt)) #ifdef PROFILE_ENABLE - const int profileCount = 10; Profiler profileDecode; profileDecode.timerStart(); - for (auto i = 0; i < profileCount; i++) { #endif - RET_IF_ERR(uhdr_decode(handle)) + RET_IF_ERR(uhdr_decode(handle)) #ifdef PROFILE_ENABLE - } profileDecode.timerStop(); - auto avgDecTime = profileDecode.elapsedTime() / (profileCount * 1000.f); - printf("Average decode time for res %ld x %ld is %f ms \n", info.width, info.height, avgDecTime); + auto avgDecTime = profileDecode.elapsedTime() / 1000.f; + printf("Average decode time for res %ld x %ld is %f ms \n", uhdr_dec_get_image_width(handle), + uhdr_dec_get_image_height(handle), avgDecTime); #endif #undef RET_IF_ERR @@ -719,10 +715,9 @@ bool UltraHdrAppInput::decode() { for (unsigned i = 0; i < output->h; i++, inData += inStride, outData += outStride) { memcpy(outData, inData, length); } - writeFile(mOutputFile, output); uhdr_release_decoder(handle); - return true; + return mMode == 1 ? writeFile(mOutputFile, &mDecodedUhdrRgbImage) : true; } #define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x) @@ -1240,10 +1235,18 @@ static void usage(const char* name) { " -t 10 bit input transfer function, optional. [0:linear, 1:hlg (default), 2:pq] \n"); fprintf(stderr, " -q quality factor to be used while encoding 8 bit image, optional. [0-100], 95 : " - "default.\n" - " gain map image does not use this quality factor. \n" - " for now gain map image quality factor is not configurable. \n"); + "default.\n"); fprintf(stderr, " -e compute psnr, optional. [0:no (default), 1:yes] \n"); + fprintf(stderr, + " -R color range for hdr intent, optional. [0:narrow-range (default), " + "1:full-range]. \n"); + fprintf(stderr, " -s gain map scale factor, optional. [factor > 0 (4 : default)]. \n"); + fprintf(stderr, + " -Q quality factor to be used while encoding gain map image, optional. [0-100], " + "85 : default. \n"); + fprintf( + stderr, + " -M enable / disable multi channel gain map, optional. [0:no (default), 1:yes]. \n"); fprintf(stderr, "\n## decoder options : \n"); fprintf(stderr, " -j ultra hdr compressed input resource. \n"); fprintf( @@ -1258,8 +1261,6 @@ static void usage(const char* name) { " srgb output color transfer shall be paired with rgba8888 only. \n" " hlg, pq shall be paired with rgba1010102. \n" " linear shall be paired with rgbahalffloat. \n"); - fprintf(stderr, - " -R color range for the HDR YUV input. [0:narrow-range (default), 1:full-range]. \n"); fprintf(stderr, "\n## common options : \n"); fprintf(stderr, " -z output filename, optional. \n" @@ -1393,9 +1394,9 @@ int main(int argc, char* argv[]) { use_full_range_color_hdr = atoi(optarg_s) == 1 ? true : false; break; // TODO -// case 'r': -// use_full_range_color_sdr = atoi(optarg_s) == 1 ? true : false; -// break; + /*case 'r': + use_full_range_color_sdr = atoi(optarg_s) == 1 ? true : false; + break;*/ case 's': gainmap_scale_factor = atoi(optarg_s); break; @@ -1441,7 +1442,7 @@ int main(int argc, char* argv[]) { output_file ? output_file : "out.jpeg", width, height, hdr_cf, sdr_cf, hdr_cg, sdr_cg, hdr_tf, quality, out_tf, out_cf, use_full_range_color_hdr, gainmap_scale_factor, - use_multi_channel_gainmap, gainmap_compression_quality); + gainmap_compression_quality, use_multi_channel_gainmap); if (!appInput.encode()) return -1; if (compute_psnr == 1) { if (!appInput.decode()) return -1; diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index 57a04a9..06344c3 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -928,6 +928,18 @@ status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, size_t map_width = image_width / mMapDimensionScaleFactor; size_t map_height = image_height / mMapDimensionScaleFactor; + if (map_width == 0 || map_height == 0) { + int scaleFactor = (std::min)(image_width, image_height); + scaleFactor = (scaleFactor >= DCTSIZE) ? (scaleFactor / DCTSIZE) : 1; + ALOGW( + "configured gainmap scale factor is resulting in gainmap width and/or height to be zero, " + "image width %d, image height %d, scale factor %d. Modiyfing gainmap scale factor to %d ", + (int)image_width, (int)image_height, (int)mMapDimensionScaleFactor, scaleFactor); + setMapDimensionScaleFactor(scaleFactor); + map_width = image_width / mMapDimensionScaleFactor; + map_height = image_height / mMapDimensionScaleFactor; + } + dest->data = new uint8_t[map_width * map_height * gainMapChannelCount]; dest->width = map_width; dest->height = map_height; diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp index 6471501..c7ad668 100644 --- a/lib/src/ultrahdr_api.cpp +++ b/lib/src/ultrahdr_api.cpp @@ -495,33 +495,65 @@ void uhdr_release_encoder(uhdr_codec_private_t* enc) { } } -UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_using_multi_channel_gainmap(uhdr_codec_private_t* enc, - int use_multi_channel_gainmap) { +UHDR_EXTERN uhdr_error_info_t +uhdr_enc_set_using_multi_channel_gainmap(uhdr_codec_private_t* enc, int use_multi_channel_gainmap) { uhdr_error_info_t status = g_no_error; - uhdr_encoder_private* handle = dynamic_cast(enc); - if (handle == nullptr) { + + if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } + uhdr_encoder_private* handle = dynamic_cast(enc); + + if (handle->m_sailed) { + status.error_code = UHDR_CODEC_INVALID_OPERATION; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "An earlier call to uhdr_encode() has switched the context from configurable state to " + "end state. The context is no longer configurable. To reuse, call reset()"); + return status; + } + handle->m_use_multi_channel_gainmap = use_multi_channel_gainmap; + return status; } UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_gainmap_scale_factor(uhdr_codec_private_t* enc, int gainmap_scale_factor) { uhdr_error_info_t status = g_no_error; - uhdr_encoder_private* handle = dynamic_cast(enc); - if (handle == nullptr) { + + if (dynamic_cast(enc) == nullptr) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); return status; } + if (gainmap_scale_factor <= 0) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "unsupported gainmap scale factor %d, expects to be > 0", gainmap_scale_factor); + return status; + } + + uhdr_encoder_private* handle = dynamic_cast(enc); + + if (handle->m_sailed) { + status.error_code = UHDR_CODEC_INVALID_OPERATION; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "An earlier call to uhdr_encode() has switched the context from configurable state to " + "end state. The context is no longer configurable. To reuse, call reset()"); + return status; + } + handle->m_gainmap_scale_factor = gainmap_scale_factor; + return status; } diff --git a/ultrahdr_api.h b/ultrahdr_api.h index fd2eb33..f66ce51 100644 --- a/ultrahdr_api.h +++ b/ultrahdr_api.h @@ -314,7 +314,7 @@ UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_quality(uhdr_codec_private_t* enc, in UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc, uhdr_mem_block_t* exif); -/*!\brief Set flag of using multi-channel gainmap, default to false (use single channel gainmap) +/*!\brief Enable multi-channel gainmap, default to false (use single channel gainmap) * * \param[in] enc encoder instance. * \param[in] use_multi_channel_gainmap flag of using multi-channel gainmap. @@ -322,8 +322,8 @@ UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc, * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, * #UHDR_CODEC_INVALID_PARAM otherwise. */ -UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_using_multi_channel_gainmap(uhdr_codec_private_t* enc, - int use_multi_channel_gainmap); +UHDR_EXTERN uhdr_error_info_t +uhdr_enc_set_using_multi_channel_gainmap(uhdr_codec_private_t* enc, int use_multi_channel_gainmap); /*!\brief Set gain map scaling factor, default value is 4 (gain map dimension is 1/4 width and * 1/4 height in pixels of the primary image) @@ -364,6 +364,10 @@ UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* e * - uhdr_enc_set_quality() * - If the application wants to insert exif data * - uhdr_enc_set_exif_data() + * - If the application wants to set gainmap scale factor + * - uhdr_enc_set_gainmap_scale_factor() + * - If the application wants to enable multi channel gain map + * - uhdr_enc_set_using_multi_channel_gainmap() * - If the application wants to control target compression format * - uhdr_enc_set_output_format() * - The program calls uhdr_encode() to encode data. This call would initiate the process of @@ -403,6 +407,8 @@ UHDR_EXTERN uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* e * - uhdr_enc_set_quality() // optional * - uhdr_enc_set_exif_data() // optional * - uhdr_enc_set_output_format() // optional + * - uhdr_enc_set_gainmap_scale_factor() // optional + * - uhdr_enc_set_using_multi_channel_gainmap() // optional * - uhdr_encode() * - uhdr_get_encoded_stream() * - uhdr_release_encoder() @@ -632,47 +638,61 @@ UHDR_EXTERN uhdr_raw_image_t* uhdr_get_gain_map_image(uhdr_codec_private_t* dec) */ UHDR_EXTERN void uhdr_reset_decoder(uhdr_codec_private_t* dec); +// =============================================================================================== +// Common APIs +// =============================================================================================== + +/*!\brief Add image editing operations (pre-encode or post-decode). + * Below functions list the set of edits supported. Program can set any combination of these during + * initialization. Once the encode/decode process call is made, before encoding or after decoding + * the edits are applied in the order of configuration. + */ + /*!\brief Add mirror effect * - * \param[in] codec instance. - * \param[in] mirror directions. + * \param[in] codec codec instance. + * \param[in] direction mirror directions. * - * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, #UHDR_CODEC_INVALID_PARAM + * otherwise. */ UHDR_EXTERN uhdr_error_info_t uhdr_add_effect_mirror(uhdr_codec_private_t* codec, uhdr_mirror_direction_t direction); /*!\brief Add rotate effect * - * \param[in] codec instance. - * \param[in] clockwise degrees. + * \param[in] codec codec instance. + * \param[in] degrees clockwise degrees. * - * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, #UHDR_CODEC_INVALID_PARAM + * otherwise. */ UHDR_EXTERN uhdr_error_info_t uhdr_add_effect_rotate(uhdr_codec_private_t* codec, int degrees); /*!\brief Add crop effect * - * \param[in] codec instance. - * \param[in] crop coordinate left. - * \param[in] crop coordinate right. - * \param[in] crop coordinate top. - * \param[in] crop coordinate bottom. + * \param[in] codec codec instance. + * \param[in] left crop coordinate left in pixels. + * \param[in] right crop coordinate right in pixels. + * \param[in] top crop coordinate top in pixels. + * \param[in] bottom crop coordinate bottom in pixels. * - * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, #UHDR_CODEC_INVALID_PARAM + * otherwise. */ -UHDR_EXTERN uhdr_error_info_t uhdr_add_effect_crop(uhdr_codec_private_t* codec, int left, - int right, int top, int bottom); +UHDR_EXTERN uhdr_error_info_t uhdr_add_effect_crop(uhdr_codec_private_t* codec, int left, int right, + int top, int bottom); /*!\brief Add resize effect * - * \param[in] codec instance. - * \param[in] target width. - * \param[in] target height. + * \param[in] codec codec instance. + * \param[in] width target width. + * \param[in] height target height. * - * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, #UHDR_CODEC_INVALID_PARAM + * otherwise. */ -UHDR_EXTERN uhdr_error_info_t uhdr_add_effect_resize(uhdr_codec_private_t* codec, - int width, int height); +UHDR_EXTERN uhdr_error_info_t uhdr_add_effect_resize(uhdr_codec_private_t* codec, int width, + int height); #endif // ULTRAHDR_API_H From 39d11de749e836d74657bb014e38191ad4173f14 Mon Sep 17 00:00:00 2001 From: Ram Mohan M Date: Wed, 19 Jun 2024 13:37:00 +0530 Subject: [PATCH 2/2] Update library to use definitions of ultrahdr_api everywhere This is a major change but maintains bitexactness with previous commit. This change unifies legacy structure definitions with definitions of ultrahdr_api.h. This helps for better extensibility for new features and avoid redundancy. Legacy structures are moved to ultrahdr.h. These are deprecated and only retained for backward compatibility. Briefly, - unify ultrahdr_color_gamut with uhdr_color_gamut_t - unify ultrahdr_transfer_function with uhdr_color_transfer_t - unify ultrahdr_metadata_struct with uhdr_gainmap_metadata_t - unify jpegr_uncompressed_struct with uhdr_raw_image_t - unify jpegr_compressed_struct with uhdr_compressed_image_t - unify jpegr_exif_struct with uhdr_mem_block_t - unify status_t with uhdr_error_info_t - Deprecate ultrahdr_output_format - Added methods to Jpeg*Helper to simplify data translation between helper and its users - Improved error propogation across library Bug fix, - For images with multi channel gainmap, decoded gainmap is not copied completely for getter functions. This is fixed. - Add support for tonemapping linear transfer inputs - Fixes oss-fuzz: 69287 Test: ./ultrahdr_unit_test Test: ./ultrahdr_enc_fuzzer Test: ./ultrahdr_dec_fuzzer --- benchmark/benchmark_test.cpp | 15 +- fuzzer/ultrahdr_enc_fuzzer.cpp | 12 +- lib/include/ultrahdr/gainmapmath.h | 60 +- lib/include/ultrahdr/gainmapmetadata.h | 30 +- lib/include/ultrahdr/icc.h | 14 +- lib/include/ultrahdr/jpegdecoderhelper.h | 38 +- lib/include/ultrahdr/jpegencoderhelper.h | 41 +- lib/include/ultrahdr/jpegr.h | 698 ++--- lib/include/ultrahdr/jpegrutils.h | 17 +- lib/include/ultrahdr/multipictureformat.h | 1 - lib/include/ultrahdr/ultrahdr.h | 72 +- lib/include/ultrahdr/ultrahdrcommon.h | 36 +- lib/src/dsp/arm/gainmapmath_neon.cpp | 102 +- lib/src/gainmapmath.cpp | 461 +-- lib/src/gainmapmetadata.cpp | 299 +- lib/src/icc.cpp | 93 +- lib/src/jpegdecoderhelper.cpp | 253 +- lib/src/jpegencoderhelper.cpp | 84 +- lib/src/jpegr.cpp | 3264 +++++++++++---------- lib/src/jpegrutils.cpp | 174 +- lib/src/ultrahdr_api.cpp | 279 +- tests/gainmapmath_test.cpp | 286 +- tests/gainmapmetadata_test.cpp | 42 +- tests/icchelper_test.cpp | 19 +- tests/jpegdecoderhelper_test.cpp | 36 +- tests/jpegencoderhelper_test.cpp | 31 +- tests/jpegr_test.cpp | 34 +- ultrahdr_api.h | 3 + 28 files changed, 3526 insertions(+), 2968 deletions(-) diff --git a/benchmark/benchmark_test.cpp b/benchmark/benchmark_test.cpp index 4148802..4d5da3a 100644 --- a/benchmark/benchmark_test.cpp +++ b/benchmark/benchmark_test.cpp @@ -510,12 +510,21 @@ static void BM_Encode_Api4(benchmark::State& s) { gainmapImg.data = gainmapImgInfo.imgData.data(); gainmapImg.maxLength = gainmapImg.length = gainmapImgInfo.imgData.size(); gainmapImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - ultrahdr_metadata_struct uhdr_metadata; - if (!getMetadataFromXMP(gainmapImgInfo.xmpData.data(), gainmapImgInfo.xmpData.size(), - &uhdr_metadata)) { + uhdr_gainmap_metadata_ext_t meta; + if (getMetadataFromXMP(gainmapImgInfo.xmpData.data(), gainmapImgInfo.xmpData.size(), &meta) + .error_code != UHDR_CODEC_OK) { s.SkipWithError("getMetadataFromXMP returned with error"); return; } + ultrahdr_metadata_struct uhdr_metadata; + uhdr_metadata.version = meta.version; + uhdr_metadata.hdrCapacityMax = meta.hdr_capacity_max; + uhdr_metadata.hdrCapacityMin = meta.hdr_capacity_min; + uhdr_metadata.gamma = meta.gamma; + uhdr_metadata.offsetSdr = meta.offset_sdr; + uhdr_metadata.offsetHdr = meta.offset_hdr; + uhdr_metadata.maxContentBoost = meta.max_content_boost; + uhdr_metadata.minContentBoost = meta.min_content_boost; for (auto _ : s) { status = jpegHdr.encodeJPEGR(&primaryImg, &gainmapImg, &uhdr_metadata, &jpegImgR); if (JPEGR_NO_ERROR != status) { diff --git a/fuzzer/ultrahdr_enc_fuzzer.cpp b/fuzzer/ultrahdr_enc_fuzzer.cpp index 88da6d2..46db2c4 100644 --- a/fuzzer/ultrahdr_enc_fuzzer.cpp +++ b/fuzzer/ultrahdr_enc_fuzzer.cpp @@ -248,8 +248,10 @@ void UltraHdrEncFuzzer::process() { yuv420ImgCopy.chroma_stride * yuv420ImgCopy.height / 2}; const size_t strides[3]{yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride, yuv420ImgCopy.chroma_stride}; - if (encoder.compressImage(planes, strides, yuv420ImgCopy.width, yuv420ImgCopy.height, - UHDR_IMG_FMT_12bppYCbCr420, quality, nullptr, 0)) { + if (encoder + .compressImage(planes, strides, yuv420ImgCopy.width, yuv420ImgCopy.height, + UHDR_IMG_FMT_12bppYCbCr420, quality, nullptr, 0) + .error_code == UHDR_CODEC_OK) { jpegImg.length = encoder.getCompressedImageSize(); jpegImg.maxLength = jpegImg.length; jpegImg.data = encoder.getCompressedImagePtr(); @@ -266,8 +268,10 @@ void UltraHdrEncFuzzer::process() { JpegEncoderHelper gainMapEncoder; const uint8_t* planeGm[1]{reinterpret_cast(grayImg.data)}; const size_t strideGm[1]{grayImg.width}; - if (gainMapEncoder.compressImage(planeGm, strideGm, grayImg.width, grayImg.height, - UHDR_IMG_FMT_8bppYCbCr400, quality, nullptr, 0)) { + if (gainMapEncoder + .compressImage(planeGm, strideGm, grayImg.width, grayImg.height, + UHDR_IMG_FMT_8bppYCbCr400, quality, nullptr, 0) + .error_code == UHDR_CODEC_OK) { jpegGainMap.length = gainMapEncoder.getCompressedImageSize(); jpegGainMap.maxLength = jpegImg.length; jpegGainMap.data = gainMapEncoder.getCompressedImagePtr(); diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index bd13597..4eb79b7 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -23,7 +23,6 @@ #include "ultrahdr_api.h" #include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegr.h" #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) @@ -176,23 +175,23 @@ inline uint16_t floatToHalf(float f) { constexpr int32_t kGainFactorPrecision = 10; constexpr int32_t kGainFactorNumEntries = 1 << kGainFactorPrecision; struct GainLUT { - GainLUT(ultrahdr_metadata_ptr metadata) { + GainLUT(uhdr_gainmap_metadata_ext_t* metadata) { this->mGammaInv = 1.0f / metadata->gamma; for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + - log2(metadata->maxContentBoost) * value; + float logBoost = log2(metadata->min_content_boost) * (1.0f - value) + + log2(metadata->max_content_boost) * value; mGainTable[idx] = exp2(logBoost); } } - GainLUT(ultrahdr_metadata_ptr metadata, float displayBoost) { + GainLUT(uhdr_gainmap_metadata_ext_t* metadata, float displayBoost) { this->mGammaInv = 1.0f / metadata->gamma; - float boostFactor = displayBoost > 0 ? displayBoost / metadata->maxContentBoost : 1.0f; + float boostFactor = displayBoost > 0 ? displayBoost / metadata->max_content_boost : 1.0f; for (int32_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); - float logBoost = log2(metadata->minContentBoost) * (1.0f - value) + - log2(metadata->maxContentBoost) * value; + float logBoost = log2(metadata->min_content_boost) * (1.0f - value) + + log2(metadata->max_content_boost) * value; mGainTable[idx] = exp2(logBoost * boostFactor); } } @@ -432,7 +431,7 @@ inline Color identityConversion(Color e) { return e; } /* * Get the conversion to apply to the HDR image for gain map generation */ -ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut); +ColorTransformFn getHdrConversionFn(uhdr_color_gamut_t sdr_gamut, uhdr_color_gamut_t hdr_gamut); /* * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2. @@ -464,10 +463,10 @@ extern const int16_t kYuv2100To601_coeffs_neon[8]; */ int16x8x3_t yuvConversion_neon(uint8x8_t y, int16x8_t u, int16x8_t v, int16x8_t coeffs); -void transformYuv420_neon(jr_uncompressed_ptr image, const int16_t* coeffs_ptr); +void transformYuv420_neon(uhdr_raw_image_t* image, const int16_t* coeffs_ptr); -status_t convertYuv_neon(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dst_encoding); +uhdr_error_info_t convertYuv_neon(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding, + uhdr_color_gamut_t dst_encoding); #endif /* @@ -479,7 +478,7 @@ status_t convertYuv_neon(jr_uncompressed_ptr image, ultrahdr_color_gamut src_enc * The chroma channels should be less than or equal to half the image's width and height * respectively, since input is 4:2:0 subsampled. */ -void transformYuv420(jr_uncompressed_ptr image, const std::array& coeffs); +void transformYuv420(uhdr_raw_image_t* image, const std::array& coeffs); //////////////////////////////////////////////////////////////////////////////// // Gain map calculations @@ -492,8 +491,8 @@ void transformYuv420(jr_uncompressed_ptr image, const std::array& coef * offsetHdr of 0.0, this function doesn't handle different metadata values for * these fields. */ -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata); -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, +uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata); +uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata, float log2MinContentBoost, float log2MaxContentBoost); /* @@ -504,8 +503,8 @@ uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to * gainMapMax, as this library encodes. */ -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata); -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost); +Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata); +Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata, float displayBoost); Color applyGainLUT(Color e, float gain, GainLUT& gainLUT); /* @@ -516,27 +515,25 @@ Color applyGainLUT(Color e, float gain, GainLUT& gainLUT); * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to * gainMapMax, as this library encodes. */ -Color applyGain(Color e, Color gain, ultrahdr_metadata_ptr metadata); -Color applyGain(Color e, Color gain, ultrahdr_metadata_ptr metadata, float displayBoost); +Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata); +Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata, float displayBoost); Color applyGainLUT(Color e, Color gain, GainLUT& gainLUT); /* * Helper for sampling from YUV 420 images. */ -Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y); +Color getYuv420Pixel(uhdr_raw_image_t* image, size_t x, size_t y); /* * Helper for sampling from P010 images. - * - * Expect narrow-range image data for P010. */ -Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y); +Color getP010Pixel(uhdr_raw_image_t* image, size_t x, size_t y); /* * Sample the image at the provided location, with a weighting based on nearby * pixels and the map scale factor. */ -Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +Color sampleYuv420(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y); /* * Sample the image at the provided location, with a weighting based on nearby @@ -544,18 +541,18 @@ Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, s * * Expect narrow-range image data for P010. */ -Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y); +Color sampleP010(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y); /* * Sample the gain value for the map from a given x,y coordinate on a scale * that is map scale factor larger than the map size. */ -float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y); -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, +float sampleMap(uhdr_raw_image_t* map, float map_scale_factor, size_t x, size_t y); +float sampleMap(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y, ShepardsIDW& weightTables); -Color sampleMap3Channel(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y, +Color sampleMap3Channel(uhdr_raw_image_t* map, float map_scale_factor, size_t x, size_t y, bool has_alpha); -Color sampleMap3Channel(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, +Color sampleMap3Channel(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y, ShepardsIDW& weightTables, bool has_alpha); /* @@ -572,6 +569,11 @@ uint32_t colorToRgba1010102(Color e_gamma); */ uint64_t colorToRgbaF16(Color e_gamma); +/* + * Helper for copying raw image descriptor + */ +uhdr_error_info_t copy_raw_image(uhdr_raw_image_t* src, uhdr_raw_image_t* dst); + /* * Helper for preparing encoder raw inputs for encoding */ diff --git a/lib/include/ultrahdr/gainmapmetadata.h b/lib/include/ultrahdr/gainmapmetadata.h index 172bba0..5ba6200 100644 --- a/lib/include/ultrahdr/gainmapmetadata.h +++ b/lib/include/ultrahdr/gainmapmetadata.h @@ -18,24 +18,15 @@ #define ULTRAHDR_GAINMAPMETADATA_H #include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" #include #include namespace ultrahdr { -#define JPEGR_CHECK(x) \ - { \ - status_t status = (x); \ - if ((status) != JPEGR_NO_ERROR) { \ - return status; \ - } \ - } - // Gain map metadata, for tone mapping between SDR and HDR. -// This is the fraction version of {@code ultrahdr_metadata_struct}. -struct gain_map_metadata { +// This is the fraction version of {@code uhdr_gainmap_metadata_ext_t}. +struct uhdr_gainmap_metadata_frac { uint32_t gainMapMinN[3]; uint32_t gainMapMinD[3]; uint32_t gainMapMaxN[3]; @@ -56,17 +47,17 @@ struct gain_map_metadata { bool backwardDirection; bool useBaseColorSpace; - static status_t encodeGainmapMetadata(const gain_map_metadata* gain_map_metadata, - std::vector& out_data); + static uhdr_error_info_t encodeGainmapMetadata(const uhdr_gainmap_metadata_frac* in_metadata, + std::vector& out_data); - static status_t decodeGainmapMetadata(const std::vector& data, - gain_map_metadata* out_gain_map_metadata); + static uhdr_error_info_t decodeGainmapMetadata(const std::vector& in_data, + uhdr_gainmap_metadata_frac* out_metadata); - static status_t gainmapMetadataFractionToFloat(const gain_map_metadata* from, - ultrahdr_metadata_ptr to); + static uhdr_error_info_t gainmapMetadataFractionToFloat(const uhdr_gainmap_metadata_frac* from, + uhdr_gainmap_metadata_ext_t* to); - static status_t gainmapMetadataFloatToFraction(const ultrahdr_metadata_ptr from, - gain_map_metadata* to); + static uhdr_error_info_t gainmapMetadataFloatToFraction(const uhdr_gainmap_metadata_ext_t* from, + uhdr_gainmap_metadata_frac* to); void dump() const { ALOGD("GAIN MAP METADATA: \n"); @@ -98,6 +89,7 @@ struct gain_map_metadata { ALOGD("use base color space: %s\n", useBaseColorSpace ? "true" : "false"); } }; + } // namespace ultrahdr #endif // ULTRAHDR_GAINMAPMETADATA_H diff --git a/lib/include/ultrahdr/icc.h b/lib/include/ultrahdr/icc.h index 54a1639..be9d3d0 100644 --- a/lib/include/ultrahdr/icc.h +++ b/lib/include/ultrahdr/icc.h @@ -33,7 +33,6 @@ #define Endian_SwapBE16(n) (n) #endif -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegr.h" #include "ultrahdr/gainmapmath.h" #include "ultrahdr/jpegrutils.h" @@ -227,12 +226,12 @@ class IccHelper { static constexpr size_t kNumChannels = 3; static std::shared_ptr write_text_tag(const char* text); - static std::string get_desc_string(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut); + static std::string get_desc_string(const uhdr_color_transfer_t tf, + const uhdr_color_gamut_t gamut); static std::shared_ptr write_xyz_tag(float x, float y, float z); static std::shared_ptr write_trc_tag(const int table_entries, const void* table_16); static std::shared_ptr write_trc_tag(const TransferFunction& fn); - static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L); + static float compute_tone_map_gain(const uhdr_color_transfer_t tf, float L); static std::shared_ptr write_cicp_tag(uint32_t color_primaries, uint32_t transfer_characteristics); static std::shared_ptr write_mAB_or_mBA_tag(uint32_t type, bool has_a_curves, @@ -249,13 +248,14 @@ class IccHelper { public: // Output includes JPEG embedding identifier and chunk information, but not // APPx information. - static std::shared_ptr writeIccProfile(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut); + static std::shared_ptr writeIccProfile(const uhdr_color_transfer_t tf, + const uhdr_color_gamut_t gamut); // NOTE: this function is not robust; it can infer gamuts that IccHelper // writes out but should not be considered a reference implementation for // robust parsing of ICC profiles or their gamuts. - static ultrahdr_color_gamut readIccColorGamut(void* icc_data, size_t icc_size); + static uhdr_color_gamut_t readIccColorGamut(void* icc_data, size_t icc_size); }; + } // namespace ultrahdr #endif // ULTRAHDR_ICC_H diff --git a/lib/include/ultrahdr/jpegdecoderhelper.h b/lib/include/ultrahdr/jpegdecoderhelper.h index 352d427..c561807 100644 --- a/lib/include/ultrahdr/jpegdecoderhelper.h +++ b/lib/include/ultrahdr/jpegdecoderhelper.h @@ -56,7 +56,6 @@ typedef enum { /*!\brief Encapsulates a converter from JPEG to raw image format. This class is not thread-safe */ class JpegDecoderHelper { public: - JpegDecoderHelper() = default; ~JpegDecoderHelper() = default; @@ -67,9 +66,10 @@ class JpegDecoderHelper { * \param[in] length length of compressed image * \param[in] mode output decode format * - * \returns true if operation succeeds, false otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - bool decompressImage(const void* image, int length, decode_mode_t mode = DECODE_TO_YCBCR_CS); + uhdr_error_info_t decompressImage(const void* image, int length, + decode_mode_t mode = DECODE_TO_YCBCR_CS); /*!\brief This function parses the bitstream that is passed to it and makes image information * available to the client via getter() functions. It does not decompress the image. That is done @@ -78,24 +78,28 @@ class JpegDecoderHelper { * \param[in] image pointer to compressed image * \param[in] length length of compressed image * - * \returns true if operation succeeds, false otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - bool parseImage(const void* image, int length) { + uhdr_error_info_t parseImage(const void* image, int length) { return decompressImage(image, length, PARSE_STREAM); } /*! Below public methods are only effective if a call to decompressImage() is made and it returned * true. */ - /*!\brief returns pointer to decompressed image */ + /*!\brief returns decompressed image descriptor */ + uhdr_raw_image_t getDecompressedImage(); + + /*!\brief returns pointer to decompressed image + * \deprecated This function is deprecated instead use getDecompressedImage(). + */ void* getDecompressedImagePtr() { return mResultBuffer.data(); } - /*!\brief returns size of decompressed image */ + /*!\brief returns size of decompressed image + * \deprecated This function is deprecated instead use getDecompressedImage(). + */ size_t getDecompressedImageSize() { return mResultBuffer.size(); } - /*!\brief returns format of decompressed image */ - uhdr_img_fmt_t getDecompressedImageFormat() { return mOutFormat; } - /*! Below public methods are only effective if a call to parseImage() or decompressImage() is made * and it returned true. */ @@ -105,6 +109,9 @@ class JpegDecoderHelper { /*!\brief returns image height */ size_t getDecompressedImageHeight() { return mPlaneHeight[0]; } + /*!\brief returns number of components in image */ + size_t getNumComponentsInImage() { return mNumComponents; } + /*!\brief returns pointer to xmp block present in input image */ void* getXMPPtr() { return mXMPBuffer.data(); } @@ -139,10 +146,10 @@ class JpegDecoderHelper { // max number of components supported static constexpr int kMaxNumComponents = 3; - bool decode(const void* image, int length, decode_mode_t mode); - bool decode(jpeg_decompress_struct* cinfo, uint8_t* dest); - bool decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest); - bool decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest); + uhdr_error_info_t decode(const void* image, int length, decode_mode_t mode); + uhdr_error_info_t decode(jpeg_decompress_struct* cinfo, uint8_t* dest); + uhdr_error_info_t decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest); + uhdr_error_info_t decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest); // temporary storage std::unique_ptr mPlanesMCURow[kMaxNumComponents]; @@ -155,8 +162,11 @@ class JpegDecoderHelper { // image attributes uhdr_img_fmt_t mOutFormat; + size_t mNumComponents; size_t mPlaneWidth[kMaxNumComponents]; size_t mPlaneHeight[kMaxNumComponents]; + size_t mPlaneHStride[kMaxNumComponents]; + size_t mPlaneVStride[kMaxNumComponents]; int mExifPayLoadOffset; // Position of EXIF package, default value is -1 which means no EXIF // package appears. diff --git a/lib/include/ultrahdr/jpegencoderhelper.h b/lib/include/ultrahdr/jpegencoderhelper.h index 7a352c0..1335671 100644 --- a/lib/include/ultrahdr/jpegencoderhelper.h +++ b/lib/include/ultrahdr/jpegencoderhelper.h @@ -50,6 +50,19 @@ class JpegEncoderHelper { JpegEncoderHelper() = default; ~JpegEncoderHelper() = default; + /*!\brief This function encodes the raw image that is passed to it and stores the results + * internally. The result is accessible via getter functions. + * + * \param[in] img image to encode + * \param[in] qfactor quality factor [1 - 100, 1 being poorest and 100 being best quality] + * \param[in] iccBuffer pointer to icc segment that needs to be added to the compressed image + * \param[in] iccSize size of icc segment + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t compressImage(const uhdr_raw_image_t* img, const int qfactor, + const void* iccBuffer, const unsigned int iccSize); + /*!\brief This function encodes the raw image that is passed to it and stores the results * internally. The result is accessible via getter functions. * @@ -62,31 +75,39 @@ class JpegEncoderHelper { * \param[in] iccBuffer pointer to icc segment that needs to be added to the compressed image * \param[in] iccSize size of icc segment * - * \returns true if operation succeeds, false otherwise. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - bool compressImage(const uint8_t* planes[3], const size_t strides[3], const int width, - const int height, const uhdr_img_fmt_t format, const int qfactor, - const void* iccBuffer, const unsigned int iccSize); + uhdr_error_info_t compressImage(const uint8_t* planes[3], const size_t strides[3], + const int width, const int height, const uhdr_img_fmt_t format, + const int qfactor, const void* iccBuffer, + const unsigned int iccSize); /*! Below public methods are only effective if a call to compressImage() is made and it returned * true. */ /*!\brief returns pointer to compressed image output */ + uhdr_compressed_image_t getCompressedImage(); + + /*!\brief returns pointer to compressed image output + * \deprecated This function is deprecated instead use getCompressedImage(). + */ void* getCompressedImagePtr() { return mDestMgr.mResultBuffer.data(); } - /*!\brief returns size of compressed image */ + /*!\brief returns size of compressed image + * \deprecated This function is deprecated instead use getCompressedImage(). + */ size_t getCompressedImageSize() { return mDestMgr.mResultBuffer.size(); } private: // max number of components supported static constexpr int kMaxNumComponents = 3; - bool encode(const uint8_t* planes[3], const size_t strides[3], const int width, const int height, - const uhdr_img_fmt_t format, const int qfactor, const void* iccBuffer, - const unsigned int iccSize); + uhdr_error_info_t encode(const uint8_t* planes[3], const size_t strides[3], const int width, + const int height, const uhdr_img_fmt_t format, const int qfactor, + const void* iccBuffer, const unsigned int iccSize); - bool compressYCbCr(jpeg_compress_struct* cinfo, const uint8_t* planes[3], - const size_t strides[3]); + uhdr_error_info_t compressYCbCr(jpeg_compress_struct* cinfo, const uint8_t* planes[3], + const size_t strides[3]); destination_mgr_impl mDestMgr; // object for managing output diff --git a/lib/include/ultrahdr/jpegr.h b/lib/include/ultrahdr/jpegr.h index 0c6b2b3..079dabd 100644 --- a/lib/include/ultrahdr/jpegr.h +++ b/lib/include/ultrahdr/jpegr.h @@ -20,7 +20,9 @@ #include #include +#include "ultrahdr_api.h" #include "ultrahdr/ultrahdr.h" +#include "ultrahdr/ultrahdrcommon.h" #include "ultrahdr/jpegdecoderhelper.h" #include "ultrahdr/jpegencoderhelper.h" @@ -29,16 +31,16 @@ namespace ultrahdr { // Default configurations // Map is quarter res / sixteenth size static const size_t kMapDimensionScaleFactorDefault = 4; + // JPEG compress quality (0 ~ 100) for gain map static const int kMapCompressQualityDefault = 85; + // Gain map calculation static const bool kUseMultiChannelGainMapDefault = false; // The current JPEGR version that we encode to -static const char* const kJpegrVersion = kGainMapVersion; - -// Map is quarter res / sixteenth size -static const size_t kMapDimensionScaleFactor = 4; +static const char* const kGainMapVersion = "1.0"; +static const char* const kJpegrVersion = "1.0"; // Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to // compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale @@ -56,6 +58,7 @@ struct jpeg_info_struct { std::vector xmpData = std::vector(0); size_t width; size_t height; + size_t numComponents; }; /* @@ -68,228 +71,279 @@ struct jpegr_info_struct { jpeg_info_struct* gainmapImgInfo = nullptr; }; -/* - * Holds information for uncompressed image or gain map. - */ -struct jpegr_uncompressed_struct { - // Pointer to the data location. - void* data; - // Width of the gain map or the luma plane of the image in pixels. - size_t width; - // Height of the gain map or the luma plane of the image in pixels. - size_t height; - // Color gamut. - ultrahdr_color_gamut colorGamut; - - // Values below are optional - // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately - // after the luma plane. - void* chroma_data = nullptr; - // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If - // non-zero this value must be larger than or equal to luma width. If stride is - // uninitialized then it is assumed to be equal to luma width. - size_t luma_stride = 0; - // Stride of UV plane in number of pixels. - // 1. If this handle points to P010 image then this value must be larger than - // or equal to luma width. - // 2. If this handle points to 420 image then this value must be larger than - // or equal to (luma width / 2). - // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way, - // chroma_data is derived from luma ptr, chroma stride is derived from luma stride. - size_t chroma_stride = 0; - // Pixel format. - uhdr_img_fmt_t pixelFormat = UHDR_IMG_FMT_UNSPECIFIED; - // Color range. - uhdr_color_range_t colorRange = UHDR_CR_UNSPECIFIED; -}; - -/* - * Holds information for compressed image or gain map. - */ -struct jpegr_compressed_struct { - // Pointer to the data location. - void* data; - // Used data length in bytes. - int length; - // Maximum available data length in bytes. - int maxLength; - // Color gamut. - ultrahdr_color_gamut colorGamut; -}; - -/* - * Holds information for EXIF metadata. - */ -struct jpegr_exif_struct { - // Pointer to the data location. - void* data; - // Data length; - size_t length; -}; - -typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; -typedef struct jpegr_compressed_struct* jr_compressed_ptr; -typedef struct jpegr_exif_struct* jr_exif_ptr; typedef struct jpeg_info_struct* j_info_ptr; typedef struct jpegr_info_struct* jr_info_ptr; class JpegR { public: - JpegR(); - JpegR(size_t, int, bool); + JpegR(size_t mapDimensionScaleFactor = kMapDimensionScaleFactorDefault, + int mapCompressQuality = kMapCompressQualityDefault, + bool useMultiChannelGainMap = kUseMultiChannelGainMapDefault); - /* - * Experimental only + /*!\brief Encode API-0. * - * Encode API-0 - * Compress JPEGR image from 10-bit HDR YUV. + * Create ultrahdr jpeg image from raw hdr intent. * - * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images, - * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed - * JPEG. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the destination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @param exif pointer to the exif metadata. - * @return NO_ERROR if encoding succeeds, error code if error occurs. + * Experimental only. + * + * Input hdr image is tonemapped to sdr image. A gainmap coefficient is computed between hdr and + * sdr intent. sdr intent and gain map coefficient are compressed using jpeg encoding. compressed + * gainmap is appended at the end of compressed sdr image. + * + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr image + * \param[in] quality quality factor for sdr intent jpeg compression + * \param[in] exif optional exif metadata that needs to be inserted in + * compressed output + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest, + int quality, uhdr_mem_block_t* exif); + + /*!\brief Encode API-1. + * + * Create ultrahdr jpeg image from raw hdr intent and raw sdr intent. + * + * A gainmap coefficient is computed between hdr and sdr intent. sdr intent and gain map + * coefficient are compressed using jpeg encoding. compressed gainmap is appended at the end of + * compressed sdr image. + * NOTE: Color transfer of sdr intent is expected to be sRGB. + * + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in] sdr_intent sdr intent raw input image descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr image + * \param[in] quality quality factor for sdr intent jpeg compression + * \param[in] exif optional exif metadata that needs to be inserted in + * compressed output + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, + uhdr_compressed_image_t* dest, int quality, uhdr_mem_block_t* exif); + + /*!\brief Encode API-2. + * + * Create ultrahdr jpeg image from raw hdr intent, raw sdr intent and compressed sdr intent. + * + * A gainmap coefficient is computed between hdr and sdr intent. gain map coefficient is + * compressed using jpeg encoding. compressed gainmap is appended at the end of compressed sdr + * intent. ICC profile is added if one isn't present in the sdr intent JPEG image. + * NOTE: Color transfer of sdr intent is expected to be sRGB. + * NOTE: sdr intent raw and compressed inputs are expected to be related via compress/decompress + * operations. + * + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in] sdr_intent sdr intent raw input image descriptor + * \param[in] sdr_intent_compressed sdr intent compressed input image descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr + * image + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, + uhdr_compressed_image_t* sdr_intent_compressed, + uhdr_compressed_image_t* dest); + + /*!\brief Encode API-3. + * + * Create ultrahdr jpeg image from raw hdr intent and compressed sdr intent. + * + * The sdr intent is decoded and a gainmap coefficient is computed between hdr and sdr intent. + * gain map coefficient is compressed using jpeg encoding. compressed gainmap is appended at the + * end of compressed sdr image. ICC profile is added if one isn't present in the sdr intent JPEG + * image. + * NOTE: Color transfer of sdr intent is expected to be sRGB. + * + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in] sdr_intent_compressed sdr intent compressed input image descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr + * image + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeJPEGR(uhdr_raw_image_t* hdr_intent, + uhdr_compressed_image_t* sdr_intent_compressed, + uhdr_compressed_image_t* dest); + + /*!\brief Encode API-4. + * + * Create ultrahdr jpeg image from compressed sdr image and compressed gainmap image + * + * compressed gainmap image is added at the end of compressed sdr image. ICC profile is added if + * one isn't present in the sdr intent compressed image. + * + * \param[in] base_img_compressed sdr intent compressed input image descriptor + * \param[in] gainmap_img_compressed gainmap compressed image descriptor + * \param[in] metadata gainmap metadata descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr + * image + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t encodeJPEGR(uhdr_compressed_image_t* base_img_compressed, + uhdr_compressed_image_t* gainmap_img_compressed, + uhdr_gainmap_metadata_ext_t* metadata, + uhdr_compressed_image_t* dest); + + /*!\brief Decode API. + * + * Decompress ultrahdr jpeg image. + * + * NOTE: This method requires that the ultrahdr input image contains an ICC profile with primaries + * that match those of a color gamut that this library is aware of; Bt.709, Display-P3, or + * Bt.2100. It also assumes the base image color transfer characteristics are sRGB. + * + * \param[in] uhdr_compressed_img compressed ultrahdr image descriptor + * \param[in, out] dest output image descriptor to store decoded output + * \param[in] max_display_boost (optional) the maximum available boost supported by a + * display, the value must be greater than or equal + * to 1.0 + * \param[in] output_ct (optional) output color transfer + * \param[in] output_format (optional) output pixel format + * \param[in, out] gainmap_img (optional) output image descriptor to store decoded + * gainmap image + * \param[in, out] gainmap_metadata (optional) descriptor to store gainmap metadata + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + * + * NOTE: This method only supports single gain map metadata values for fields that allow + * multi-channel metadata values. + * + * NOTE: Not all combinations of output color transfer and output pixel format are supported. + * Refer below table for supported combinations. + * ---------------------------------------------------------------------- + * | color transfer | color format | + * ---------------------------------------------------------------------- + * | SDR | 32bppRGBA8888 | + * ---------------------------------------------------------------------- + * | HDR_LINEAR | 64bppRGBAHalfFloat | + * ---------------------------------------------------------------------- + * | HDR_PQ | 32bppRGBA1010102 | + * ---------------------------------------------------------------------- + * | HDR_HLG | 32bppRGBA1010102 | + * ---------------------------------------------------------------------- + */ + uhdr_error_info_t decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_img, + uhdr_raw_image_t* dest, float max_display_boost = FLT_MAX, + uhdr_color_transfer_t output_ct = UHDR_CT_LINEAR, + uhdr_img_fmt_t output_format = UHDR_IMG_FMT_64bppRGBAHalfFloat, + uhdr_raw_image_t* gainmap_img = nullptr, + uhdr_gainmap_metadata_t* gainmap_metadata = nullptr); + + /*!\brief This function parses the bitstream and returns information that is useful for actual + * decoding. This does not decode the image. That is handled by decodeJPEGR + * + * \param[in] uhdr_compressed_img compressed ultrahdr image descriptor + * \param[in, out] uhdr_image_info image info descriptor + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ + uhdr_error_info_t getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img, + jr_info_ptr uhdr_image_info); + + /*!\brief set gain map dimension scale factor + * + * NOTE: Applicable only in encoding scenario + * + * \param[in] mapDimensionScaleFactor scale factor + * + * \return none + */ + void setMapDimensionScaleFactor(size_t mapDimensionScaleFactor) { + this->mMapDimensionScaleFactor = mapDimensionScaleFactor; + } + + /*!\brief get gain map dimension scale factor + * + * NOTE: Applicable only in encoding scenario + * + * \return mapDimensionScaleFactor + */ + size_t getMapDimensionScaleFactor() { return this->mMapDimensionScaleFactor; } + + /*!\brief set gain map compression quality factor + * + * NOTE: Applicable only in encoding scenario + * + * \param[in] mapCompressQuality quality factor for gain map image compression + * + * \return none + */ + void setMapCompressQuality(int mapCompressQuality) { + this->mMapCompressQuality = mapCompressQuality; + } + + /*!\brief get gain map quality factor + * + * NOTE: Applicable only in encoding scenario + * + * \return quality factor + */ + int getMapCompressQuality() { return this->mMapCompressQuality; } + + /*!\brief enable / disable multi channel gain map + * + * NOTE: Applicable only in encoding scenario + * + * \param[in] useMultiChannelGainMap enable / disable multi channel gain map + * + * \return none + */ + void setUseMultiChannelGainMap(bool useMultiChannelGainMap) { + this->mUseMultiChannelGainMap = useMultiChannelGainMap; + } + + /*!\brief check if multi channel gain map is enabled + * + * NOTE: Applicable only in encoding scenario + * + * \return true if multi channel gain map is enabled, false otherwise + */ + bool isUsingMultiChannelGainMap() { return this->mUseMultiChannelGainMap; } + + /* \brief Alias of Encode API-0. + * + * \deprecated This function is deprecated. Use its alias */ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif); - /* - * Encode API-1 - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + /* \brief Alias of Encode API-1. * - * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append - * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same - * resolution. SDR input is assumed to use the sRGB transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is - * the highest quality - * @param exif pointer to the exif metadata. - * @return NO_ERROR if encoding succeeds, error code if error occurs. + * \deprecated This function is deprecated. Use its actual */ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality, jr_exif_ptr exif); - /* - * Encode API-2 - * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG. - * - * This method requires HAL Hardware JPEG encoder. + /* \brief Alias of Encode API-2. * - * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the - * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and - * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB - * transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. + * \deprecated This function is deprecated. Use its actual */ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); - /* - * Encode API-3 - * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV. + /* \brief Alias of Encode API-3. * - * This method requires HAL Hardware JPEG encoder. - * - * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input - * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an - * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same - * resolution. JPEG image is assumed to use the sRGB transfer function. - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param hdr_tf transfer function of the HDR image - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. + * \deprecated This function is deprecated. Use its actual */ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest); - /* - * Encode API-4 - * Assemble JPEGR image from SDR JPEG and gainmap JPEG. - * - * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC - * profile if one isn't present in the input JPEG image. - * @param yuv420jpg_image_ptr SDR image compressed in jpeg format - * @param gainmapjpg_image_ptr gain map image compressed in jpeg format - * @param metadata metadata to be written in XMP of the primary jpeg - * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength} - * represents the maximum available size of the desitination buffer, and it must be - * set before calling this method. If the encoded JPEGR size exceeds - * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}. - * @return NO_ERROR if encoding succeeds, error code if error occurs. + /* \brief Alias of Encode API-4. + * + * \deprecated This function is deprecated. Use its actual */ status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); - /* - * Decode API - * Decompress JPEGR image. - * - * This method assumes that the JPEGR image contains an ICC profile with primaries that match - * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also - * assumes the base image uses the sRGB transfer function. - * - * This method only supports single gain map metadata values for fields that allow multi-channel - * metadata values. - * @param jpegr_image_ptr compressed JPEGR image. - * @param dest destination of the uncompressed JPEGR image. - * @param max_display_boost (optional) the maximum available boost supported by a display, - * the value must be greater than or equal to 1.0. - * @param exif destination of the decoded EXIF metadata. The default value is NULL where the - decoder will do nothing about it. If configured not NULL the decoder will write - EXIF data into this structure. The format is defined in {@code jpegr_exif_struct} - * @param output_format flag for setting output color format. Its value configures the output - color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}. - ---------------------------------------------------------------------- - | output_format | decoded color format to be written | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_SDR | RGBA_8888 | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_LINEAR | (default)RGBA_F16 linear | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_PQ | RGBA_1010102 PQ | - ---------------------------------------------------------------------- - | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG | - ---------------------------------------------------------------------- - * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL - where the decoder will do nothing about it. If configured not NULL - the decoder will write the decoded gain_map data into this - structure. The format is defined in - {@code jpegr_uncompressed_struct}. - * @param metadata destination of the decoded metadata. The default value is NULL where the - decoder will do nothing about it. If configured not NULL the decoder will - write metadata into this structure. the format of metadata is defined in - {@code ultrahdr_metadata_struct}. - * @return NO_ERROR if decoding succeeds, error code if error occurs. + /* \brief Alias of Decode API + * + * \deprecated This function is deprecated. Use its actual */ status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr, @@ -297,170 +351,145 @@ class JpegR { jr_uncompressed_ptr gainmap_image_ptr = nullptr, ultrahdr_metadata_ptr metadata = nullptr); - /* - * Gets Info from JPEGR file without decoding it. + /* \brief Alias of getJPEGRInfo * - * This method only supports single gain map metadata values for fields that allow multi-channel - * metadata values. - * - * The output is filled jpegr_info structure - * @param jpegr_image_ptr compressed JPEGR image - * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info - * are owned by the caller - * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise + * \deprecated This function is deprecated. Use its actual */ - status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr); - - /* - * Getting / Setting configurations - */ - void setMapDimensionScaleFactor(size_t mapDimensionScaleFactor) { - this->mMapDimensionScaleFactor = mapDimensionScaleFactor; - } - - size_t getMapDimensionScaleFactor() { return this->mMapDimensionScaleFactor; } - - void setMapCompressQuality(int mapCompressQuality) { - this->mMapCompressQuality = mapCompressQuality; - } - - int getMapCompressQuality() { return this->mMapCompressQuality; } - - void setUseMultiChannelGainMap(bool useMultiChannelGainMap) { - this->mUseMultiChannelGainMap = useMultiChannelGainMap; - } - - bool isUsingMultiChannelGainMap() { return this->mUseMultiChannelGainMap; } + status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr); protected: - /* - * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and - * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images - * must be the same resolution. The SDR input is assumed to use the sRGB transfer function. + /*!\brief This method takes hdr intent and sdr intent and computes gainmap coefficient. * - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param p010_image_ptr uncompressed HDR image in P010 color format - * @param hdr_tf transfer function of the HDR image - * @param metadata everything but "version" is filled in this struct - * @param dest location at which gain map image is stored (caller responsible for memory - of data). - * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut - * @return NO_ERROR if calculation succeeds, error code if error occurs. + * This method is called in the encoding pipeline. It takes uncompressed 8-bit and 10-bit yuv + * images as input and calculates gainmap. + * + * NOTE: The input images must be the same resolution. + * NOTE: The SDR input is assumed to use the sRGB transfer function. + * + * \param[in] sdr_intent sdr intent raw input image descriptor + * \param[in] hdr_intent hdr intent raw input image descriptor + * \param[in, out] gainmap_metadata gainmap metadata descriptor + * \param[in, out] gainmap_img gainmap image descriptor + * \param[in] sdr_is_601 (optional) if sdr_is_601 is true, then use BT.601 + * gamut to represent sdr intent regardless of the value + * present in the sdr intent image descriptor + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, jr_uncompressed_ptr p010_image_ptr, - ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr dest, bool sdr_is_601 = false); - - /* - * This method is called in the decoding pipeline. It will take the uncompressed (decoded) - * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as - * input, and calculate the 10-bit recovered image. The recovered output image is the same - * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format. - * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to - * be a decoded JPEG for the purpose of YUV interpration. - * - * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format - * @param gainmap_image_ptr pointer to uncompressed gain map image struct. - * @param metadata JPEG/R metadata extracted from XMP. - * @param output_format flag for setting output color format. if set to - * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image - * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR. - * @param max_display_boost the maximum available boost supported by a display - * @param dest reconstructed HDR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. + uhdr_error_info_t generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* hdr_intent, + uhdr_gainmap_metadata_ext_t* gainmap_metadata, + std::unique_ptr& gainmap_img, + bool sdr_is_601 = false); + + /*!\brief This method takes sdr intent, gainmap image and gainmap metadata and computes hdr + * intent. This method is called in the decoding pipeline. The output hdr intent image will have + * same color gamut as sdr intent. + * + * NOTE: The SDR input is assumed to use the sRGB transfer function. + * + * \param[in] sdr_intent sdr intent raw input image descriptor + * \param[in] gainmap_img gainmap image descriptor + * \param[in] gainmap_metadata gainmap metadata descriptor + * \param[in] output_ct output color transfer + * \param[in] output_format output pixel format + * \param[in] max_display_boost the maximum available boost supported by a + * display, the value must be greater than or equal + * to 1.0 + * \param[in, out] dest output image descriptor to store output + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, jr_uncompressed_ptr gainmap_image_ptr, - ultrahdr_metadata_ptr metadata, ultrahdr_output_format output_format, - float max_display_boost, jr_uncompressed_ptr dest); + uhdr_error_info_t applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img, + uhdr_gainmap_metadata_ext_t* gainmap_metadata, + uhdr_color_transfer_t output_ct, uhdr_img_fmt_t output_format, + float max_display_boost, uhdr_raw_image_t* dest); private: - /* - * This method is called in the encoding pipeline. It will encode the gain map. + /*!\brief compress gainmap image + * + * \param[in] gainmap_img gainmap image descriptor + * \param[in] jpeg_enc_obj jpeg encoder object handle * - * @param gainmap_image_ptr pointer to uncompressed gain map image struct - * @param jpeg_enc_obj_ptr helper resource to compress gain map - * @return NO_ERROR if encoding succeeds, error code if error occurs. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, - JpegEncoderHelper* jpeg_enc_obj_ptr); + uhdr_error_info_t compressGainMap(uhdr_raw_image_t* gainmap_img, JpegEncoderHelper* jpeg_enc_obj); - /* - * This method is called to separate primary image and gain map image from JPEGR + /*!\brief This method is called to separate base image and gain map image from compressed + * ultrahdr image * - * @param jpegr_image_ptr pointer to compressed JPEGR image. - * @param primary_jpg_image_ptr destination of primary image - * @param gainmap_jpg_image_ptr destination of compressed gain map image - * @return NO_ERROR if calculation succeeds, error code if error occurs. + * \param[in] jpegr_image compressed ultrahdr image descriptor + * \param[in, out] primary_image sdr image descriptor + * \param[in, out] gainmap_image gainmap image descriptor + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, - jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr); + uhdr_error_info_t extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image, + uhdr_compressed_image_t* primary_image, + uhdr_compressed_image_t* gainmap_image); - /* - * Gets Info from JPEG image without decoding it. - * - * The output is filled jpeg_info structure - * @param jpegr_image_ptr compressed JPEG image - * @param jpeg_image_info_ptr pointer to jpeg info struct. Members of jpeg_info_struct - * are owned by the caller - * @param img_width (optional) pointer to store width of jpeg image - * @param img_height (optional) pointer to store height of jpeg image - * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise + /*!\brief This function parses the bitstream and returns metadata that is useful for actual + * decoding. This does not decode the image. That is handled by decompressImage(). + * + * \param[in] jpeg_image compressed jpeg image descriptor + * \param[in, out] image_info image info descriptor + * \param[in, out] img_width (optional) image width + * \param[in, out] img_height (optional) image height + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t parseJpegInfo(jr_compressed_ptr jpeg_image_ptr, j_info_ptr jpeg_image_info_ptr, - size_t* img_width = nullptr, size_t* img_height = nullptr); + uhdr_error_info_t parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info, + size_t* img_width = nullptr, size_t* img_height = nullptr); - /* - * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image, - * the compressed gain map and optionally the exif package as inputs, and generate the XMP - * metadata, and finally append everything in the order of: - * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map - * - * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following - * conditions is fulfilled: - * (1) EXIF package is available from outside input. I.e. pExif != nullptr. - * (2) Input JPEG has EXIF. - * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE - * - * @param primary_jpg_image_ptr destination of primary image - * @param gainmap_jpg_image_ptr destination of compressed gain map image - * @param (nullable) pExif EXIF package - * @param (nullable) pIcc ICC package - * @param icc_size length in bytes of ICC package - * @param metadata JPEG/R metadata to encode in XMP of the jpeg - * @param dest compressed JPEGR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. + /*!\brief This method takes compressed sdr intent, compressed gainmap coefficient, gainmap + * metadata and creates a ultrahdr image. This is done by first generating XMP packet from gainmap + * metadata, then appending in the order, + * SOI, APP2 (Exif is present), APP2 (XMP), base image, gain map image. + * + * NOTE: In the final output, EXIF package will appear if ONLY ONE of the following conditions is + * fulfilled: + * (1) EXIF package is available from outside input. I.e. pExif != nullptr. + * (2) Compressed sdr intent has EXIF. + * If both conditions are fulfilled, this method will return error indicating that it is unable to + * choose which exif to be placed in the bitstream. + * + * \param[in] sdr_intent_compressed sdr intent image descriptor + * \param[in] gainmap_compressed gainmap intent input image descriptor + * \param[in] pExif exif block to be placed in the bitstream + * \param[in] pIcc pointer to icc segment that needs to be added to the + * compressed image + * \param[in] icc_size size of icc segment + * \param[in] metadata gainmap metadata descriptor + * \param[in, out] dest output image descriptor to store compressed ultrahdr + * image + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc, - size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest); + uhdr_error_info_t appendGainMap(uhdr_compressed_image_t* sdr_intent_compressed, + uhdr_compressed_image_t* gainmap_compressed, + uhdr_mem_block_t* pExif, void* pIcc, size_t icc_size, + uhdr_gainmap_metadata_ext_t* metadata, + uhdr_compressed_image_t* dest); - /* - * This method will tone map a HDR image to an SDR image. + /*!\brief This method is used to tone map a hdr image * - * @param src pointer to uncompressed HDR image struct. HDR image is expected to be - * in p010 color format - * @param dest pointer to store tonemapped SDR image - * @param hdr_tf transfer function of the HDR image - * @return NO_ERROR if calculation succeeds, error code if error occurs. + * \param[in] hdr_intent hdr image descriptor + * \param[in, out] sdr_intent sdr image descriptor + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest, - ultrahdr_transfer_function hdr_tf); + uhdr_error_info_t toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent); - /* - * This method will convert a YUV420 image from one YUV encoding to another in-place (eg. - * Bt.709 to Bt.601 YUV encoding). + /*!\brief This method is used to convert a raw image from one gamut space to another gamut space + * in-place. * - * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that - * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data. + * \param[in, out] image raw image descriptor + * \param[in] src_encoding input gamut space + * \param[in] dst_encoding destination gamut space * - * @param image the YUV420 image to convert - * @param src_encoding input YUV encoding - * @param dest_encoding output YUV encoding - * @return NO_ERROR if calculation succeeds, error code if error occurs. + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. */ - status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dest_encoding); + uhdr_error_info_t convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding, + uhdr_color_gamut_t dst_encoding); /* * This method will check the validity of the input arguments. @@ -500,12 +529,9 @@ class JpegR { int quality); // Configurations - // Map is quarter res / sixteenth size - size_t mMapDimensionScaleFactor; - // JPEG compress quality (0 ~ 100) for gain map - int mMapCompressQuality; - // Gain map calculation - bool mUseMultiChannelGainMap; + size_t mMapDimensionScaleFactor; // gainmap scale factor + int mMapCompressQuality; // gainmap quality factor + bool mUseMultiChannelGainMap; // enable multichannel gainmap }; struct GlobalTonemapOutputs { diff --git a/lib/include/ultrahdr/jpegrutils.h b/lib/include/ultrahdr/jpegrutils.h index ce523c8..2ddcb74 100644 --- a/lib/include/ultrahdr/jpegrutils.h +++ b/lib/include/ultrahdr/jpegrutils.h @@ -17,7 +17,6 @@ #ifndef ULTRAHDR_JPEGRUTILS_H #define ULTRAHDR_JPEGRUTILS_H -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegr.h" // TODO (dichenzhang): This is old version metadata, new version can be found in @@ -34,7 +33,6 @@ static inline uint16_t EndianSwap16(uint16_t value) { return static_cast((value >> 8) | ((value & 0xFF) << 8)); } -struct ultrahdr_metadata_struct; /* * Mutable data structure. Holds information for metadata. */ @@ -64,9 +62,10 @@ class DataStruct { * @param source source of data being written. * @param length length of the data to be written. * @param position cursor in desitination where the data is to be written. - * @return status of succeed or error code. + * @return success or error code. */ -status_t Write(jr_compressed_ptr destination, const void* source, int length, int& position); +uhdr_error_info_t Write(uhdr_compressed_image_t* destination, const void* source, int length, + int& position); /* * Parses XMP packet and fills metadata with data from XMP @@ -74,9 +73,10 @@ status_t Write(jr_compressed_ptr destination, const void* source, int length, in * @param xmp_data pointer to XMP packet * @param xmp_size size of XMP packet * @param metadata place to store HDR metadata values - * @return true if metadata is successfully retrieved, false otherwise + * @return success or error code. */ -bool getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, ultrahdr_metadata_struct* metadata); +uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, + uhdr_gainmap_metadata_ext_t* metadata); /* * This method generates XMP metadata for the primary image. @@ -119,7 +119,7 @@ bool getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, ultrahdr_metadata_struc * @return XMP metadata in type of string */ std::string generateXmpForPrimaryImage(int secondary_image_length, - ultrahdr_metadata_struct& metadata); + uhdr_gainmap_metadata_ext_t& metadata); /* * This method generates XMP metadata for the recovery map image. @@ -151,7 +151,8 @@ std::string generateXmpForPrimaryImage(int secondary_image_length, * @param metadata JPEG/R metadata to encode as XMP * @return XMP metadata in type of string */ -std::string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata); +std::string generateXmpForSecondaryImage(uhdr_gainmap_metadata_ext_t& metadata); + } // namespace ultrahdr #endif // ULTRAHDR_JPEGRUTILS_H diff --git a/lib/include/ultrahdr/multipictureformat.h b/lib/include/ultrahdr/multipictureformat.h index 9a9141b..434b2ba 100644 --- a/lib/include/ultrahdr/multipictureformat.h +++ b/lib/include/ultrahdr/multipictureformat.h @@ -33,7 +33,6 @@ #define Endian_SwapBE16(n) (n) #endif -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegr.h" #include "ultrahdr/gainmapmath.h" #include "ultrahdr/jpegrutils.h" diff --git a/lib/include/ultrahdr/ultrahdr.h b/lib/include/ultrahdr/ultrahdr.h index 3170b1a..53617ba 100644 --- a/lib/include/ultrahdr/ultrahdr.h +++ b/lib/include/ultrahdr/ultrahdr.h @@ -21,8 +21,13 @@ namespace ultrahdr { -// The current JPEGR version that we encode to -static const char* const kGainMapVersion = "1.0"; +#define JPEGR_CHECK(x) \ + { \ + status_t status = (x); \ + if ((status) != JPEGR_NO_ERROR) { \ + return status; \ + } \ + } // TODO (dichenzhang): rename these to "ULTRAHDR". typedef enum { @@ -111,6 +116,69 @@ struct ultrahdr_metadata_struct { // HDR capacity to apply the map completely float hdrCapacityMax; }; + +/* + * Holds information for uncompressed image or gain map. + */ +struct jpegr_uncompressed_struct { + // Pointer to the data location. + void* data; + // Width of the gain map or the luma plane of the image in pixels. + size_t width; + // Height of the gain map or the luma plane of the image in pixels. + size_t height; + // Color gamut. + ultrahdr_color_gamut colorGamut; + + // Values below are optional + // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately + // after the luma plane. + void* chroma_data = nullptr; + // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If + // non-zero this value must be larger than or equal to luma width. If stride is + // uninitialized then it is assumed to be equal to luma width. + size_t luma_stride = 0; + // Stride of UV plane in number of pixels. + // 1. If this handle points to P010 image then this value must be larger than + // or equal to luma width. + // 2. If this handle points to 420 image then this value must be larger than + // or equal to (luma width / 2). + // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way, + // chroma_data is derived from luma ptr, chroma stride is derived from luma stride. + size_t chroma_stride = 0; + // Pixel format. + uhdr_img_fmt_t pixelFormat = UHDR_IMG_FMT_UNSPECIFIED; + // Color range. + uhdr_color_range_t colorRange = UHDR_CR_UNSPECIFIED; +}; + +/* + * Holds information for compressed image or gain map. + */ +struct jpegr_compressed_struct { + // Pointer to the data location. + void* data; + // Used data length in bytes. + int length; + // Maximum available data length in bytes. + int maxLength; + // Color gamut. + ultrahdr_color_gamut colorGamut; +}; + +/* + * Holds information for EXIF metadata. + */ +struct jpegr_exif_struct { + // Pointer to the data location. + void* data; + // Data length; + size_t length; +}; + +typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr; +typedef struct jpegr_compressed_struct* jr_compressed_ptr; +typedef struct jpegr_exif_struct* jr_exif_ptr; typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr; } // namespace ultrahdr diff --git a/lib/include/ultrahdr/ultrahdrcommon.h b/lib/include/ultrahdr/ultrahdrcommon.h index c501760..ec1de09 100644 --- a/lib/include/ultrahdr/ultrahdrcommon.h +++ b/lib/include/ultrahdr/ultrahdrcommon.h @@ -119,6 +119,14 @@ #define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m)) +#define UHDR_ERR_CHECK(x) \ + { \ + uhdr_error_info_t status = (x); \ + if (status.error_code != UHDR_CODEC_OK) { \ + return status; \ + } \ + } + #if defined(_MSC_VER) #define FORCE_INLINE __forceinline #define INLINE __inline @@ -127,6 +135,8 @@ #define INLINE inline #endif +static const uhdr_error_info_t g_no_error = {UHDR_CODEC_OK, 0, ""}; + namespace ultrahdr { // =============================================================================================== @@ -162,6 +172,26 @@ typedef struct uhdr_compressed_image_ext : uhdr_compressed_image_t { /*!\brief forward declaration for image effect descriptor */ typedef struct uhdr_effect_desc uhdr_effect_desc_t; +/**\brief Gain map metadata. */ +typedef struct uhdr_gainmap_metadata_ext : uhdr_gainmap_metadata { + uhdr_gainmap_metadata_ext() {} + + uhdr_gainmap_metadata_ext(std::string ver) { version = ver; } + + uhdr_gainmap_metadata_ext(uhdr_gainmap_metadata& metadata, std::string ver) { + max_content_boost = metadata.max_content_boost; + min_content_boost = metadata.min_content_boost; + gamma = metadata.gamma; + offset_sdr = metadata.offset_sdr; + offset_hdr = metadata.offset_hdr; + hdr_capacity_min = metadata.hdr_capacity_min; + hdr_capacity_max = metadata.hdr_capacity_max; + version = ver; + } + + std::string version; /**< Ultra HDR format version */ +} uhdr_gainmap_metadata_ext_t; /**< alias for struct uhdr_gainmap_metadata */ + } // namespace ultrahdr // =============================================================================================== @@ -183,11 +213,11 @@ struct uhdr_encoder_private : uhdr_codec_private { std::vector m_exif; uhdr_gainmap_metadata_t m_metadata; uhdr_codec_t m_output_format; + int m_gainmap_scale_factor; + bool m_use_multi_channel_gainmap; // internal data bool m_sailed; - int m_gainmap_scale_factor; - bool m_use_multi_channel_gainmap; std::unique_ptr m_compressed_output_buffer; uhdr_error_info_t m_encode_call_status; }; @@ -205,7 +235,7 @@ struct uhdr_decoder_private : uhdr_codec_private { std::unique_ptr m_decoded_img_buffer; std::unique_ptr m_gainmap_img_buffer; int m_img_wd, m_img_ht; - int m_gainmap_wd, m_gainmap_ht; + int m_gainmap_wd, m_gainmap_ht, m_gainmap_num_comp; std::vector m_exif; uhdr_mem_block_t m_exif_block; std::vector m_icc; diff --git a/lib/src/dsp/arm/gainmapmath_neon.cpp b/lib/src/dsp/arm/gainmapmath_neon.cpp index f23767e..27966b1 100644 --- a/lib/src/dsp/arm/gainmapmath_neon.cpp +++ b/lib/src/dsp/arm/gainmapmath_neon.cpp @@ -111,13 +111,13 @@ int16x8x3_t yuvConversion_neon(uint8x8_t y, int16x8_t u, int16x8_t v, int16x8_t return {y_output, u_output, v_output}; } -void transformYuv420_neon(jr_uncompressed_ptr image, const int16_t* coeffs_ptr) { +void transformYuv420_neon(uhdr_raw_image_t* image, const int16_t* coeffs_ptr) { // Implementation assumes image buffer is multiple of 16. assert(image->width % 16 == 0); - uint8_t* y0_ptr = static_cast(image->data); - uint8_t* y1_ptr = y0_ptr + image->luma_stride; - uint8_t* u_ptr = static_cast(image->chroma_data); - uint8_t* v_ptr = u_ptr + image->chroma_stride * (image->height / 2); + uint8_t* y0_ptr = static_cast(image->planes[UHDR_PLANE_Y]); + uint8_t* y1_ptr = y0_ptr + image->stride[UHDR_PLANE_Y]; + uint8_t* u_ptr = static_cast(image->planes[UHDR_PLANE_U]); + uint8_t* v_ptr = static_cast(image->planes[UHDR_PLANE_V]); const int16x8_t coeffs = vld1q_s16(coeffs_ptr); const uint16x8_t uv_bias = vreinterpretq_u16_s16(vdupq_n_s16(-128)); @@ -160,83 +160,85 @@ void transformYuv420_neon(jr_uncompressed_ptr image, const int16_t* coeffs_ptr) vst1_u8(v_ptr + w, v_output); w += 8; - } while (w < image->width / 2); - y0_ptr += image->luma_stride * 2; - y1_ptr += image->luma_stride * 2; - u_ptr += image->chroma_stride; - v_ptr += image->chroma_stride; - } while (++h < image->height / 2); + } while (w < image->w / 2); + y0_ptr += image->stride[UHDR_PLANE_Y] * 2; + y1_ptr += image->stride[UHDR_PLANE_Y] * 2; + u_ptr += image->stride[UHDR_PLANE_U]; + v_ptr += image->stride[UHDR_PLANE_V]; + } while (++h < image->h / 2); } -status_t convertYuv_neon(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dst_encoding) { - if (image == nullptr) { - return ERROR_JPEGR_BAD_PTR; - } - if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED || - dst_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - +uhdr_error_info_t convertYuv_neon(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding, + uhdr_color_gamut_t dst_encoding) { + uhdr_error_info_t status = g_no_error; const int16_t* coeffs = nullptr; + switch (src_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: switch (dst_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - return JPEGR_NO_ERROR; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_BT_709: + return status; + case UHDR_CG_DISPLAY_P3: coeffs = kYuv709To601_coeffs_neon; break; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: coeffs = kYuv709To2100_coeffs_neon; break; default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", + dst_encoding); + return status; } break; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: switch (dst_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: coeffs = kYuv601To709_coeffs_neon; break; - case ULTRAHDR_COLORGAMUT_P3: - return JPEGR_NO_ERROR; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_DISPLAY_P3: + return status; + case UHDR_CG_BT_2100: coeffs = kYuv601To2100_coeffs_neon; break; default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", + dst_encoding); + return status; } break; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: switch (dst_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: coeffs = kYuv2100To709_coeffs_neon; break; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: coeffs = kYuv2100To601_coeffs_neon; break; - case ULTRAHDR_COLORGAMUT_BT2100: - return JPEGR_NO_ERROR; + case UHDR_CG_BT_2100: + return status; default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", + dst_encoding); + return status; } break; default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - if (coeffs == nullptr) { - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized src color gamut %d", + src_encoding); + return status; } transformYuv420_neon(image, coeffs); - return JPEGR_NO_ERROR; + + return status; } } // namespace ultrahdr diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index ff25b9c..5f5e4c1 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -15,6 +15,7 @@ */ #include + #include "ultrahdr/gainmapmath.h" namespace ultrahdr { @@ -403,46 +404,45 @@ Color bt2100ToP3(Color e) { // TODO: confirm we always want to convert like this before calculating // luminance. -ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, - ultrahdr_color_gamut hdr_gamut) { +ColorTransformFn getHdrConversionFn(uhdr_color_gamut_t sdr_gamut, uhdr_color_gamut_t hdr_gamut) { switch (sdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: switch (hdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: return identityConversion; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: return p3ToBt709; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: return bt2100ToBt709; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: + case UHDR_CG_UNSPECIFIED: return nullptr; } break; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: switch (hdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: return bt709ToP3; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: return identityConversion; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: return bt2100ToP3; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: + case UHDR_CG_UNSPECIFIED: return nullptr; } break; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: switch (hdr_gamut) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: return bt709ToBt2100; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: return p3ToBt2100; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: return identityConversion; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: + case UHDR_CG_UNSPECIFIED: return nullptr; } break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: + case UHDR_CG_UNSPECIFIED: return nullptr; } return nullptr; @@ -505,9 +505,9 @@ Color yuvColorGamutConversion(Color e_gamma, const std::array& coeffs) return {{{y, u, v}}}; } -void transformYuv420(jr_uncompressed_ptr image, const std::array& coeffs) { - for (size_t y = 0; y < image->height / 2; ++y) { - for (size_t x = 0; x < image->width / 2; ++x) { +void transformYuv420(uhdr_raw_image_t* image, const std::array& coeffs) { + for (size_t y = 0; y < image->h / 2; ++y) { + for (size_t x = 0; x < image->w / 2; ++x) { Color yuv1 = getYuv420Pixel(image, x * 2, y * 2); Color yuv2 = getYuv420Pixel(image, x * 2 + 1, y * 2); Color yuv3 = getYuv420Pixel(image, x * 2, y * 2 + 1); @@ -520,21 +520,21 @@ void transformYuv420(jr_uncompressed_ptr image, const std::array& coef Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f; - size_t pixel_y1_idx = x * 2 + y * 2 * image->luma_stride; - size_t pixel_y2_idx = (x * 2 + 1) + y * 2 * image->luma_stride; - size_t pixel_y3_idx = x * 2 + (y * 2 + 1) * image->luma_stride; - size_t pixel_y4_idx = (x * 2 + 1) + (y * 2 + 1) * image->luma_stride; + size_t pixel_y1_idx = x * 2 + y * 2 * image->stride[UHDR_PLANE_Y]; + size_t pixel_y2_idx = (x * 2 + 1) + y * 2 * image->stride[UHDR_PLANE_Y]; + size_t pixel_y3_idx = x * 2 + (y * 2 + 1) * image->stride[UHDR_PLANE_Y]; + size_t pixel_y4_idx = (x * 2 + 1) + (y * 2 + 1) * image->stride[UHDR_PLANE_Y]; - uint8_t& y1_uint = reinterpret_cast(image->data)[pixel_y1_idx]; - uint8_t& y2_uint = reinterpret_cast(image->data)[pixel_y2_idx]; - uint8_t& y3_uint = reinterpret_cast(image->data)[pixel_y3_idx]; - uint8_t& y4_uint = reinterpret_cast(image->data)[pixel_y4_idx]; + uint8_t& y1_uint = reinterpret_cast(image->planes[UHDR_PLANE_Y])[pixel_y1_idx]; + uint8_t& y2_uint = reinterpret_cast(image->planes[UHDR_PLANE_Y])[pixel_y2_idx]; + uint8_t& y3_uint = reinterpret_cast(image->planes[UHDR_PLANE_Y])[pixel_y3_idx]; + uint8_t& y4_uint = reinterpret_cast(image->planes[UHDR_PLANE_Y])[pixel_y4_idx]; - size_t pixel_count = image->chroma_stride * image->height / 2; - size_t pixel_uv_idx = x + y * (image->chroma_stride); + size_t pixel_u_idx = x + y * image->stride[UHDR_PLANE_U]; + uint8_t& u_uint = reinterpret_cast(image->planes[UHDR_PLANE_U])[pixel_u_idx]; - uint8_t& u_uint = reinterpret_cast(image->chroma_data)[pixel_uv_idx]; - uint8_t& v_uint = reinterpret_cast(image->chroma_data)[pixel_count + pixel_uv_idx]; + size_t pixel_v_idx = x + y * image->stride[UHDR_PLANE_V]; + uint8_t& v_uint = reinterpret_cast(image->planes[UHDR_PLANE_V])[pixel_v_idx]; y1_uint = static_cast(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255)); y2_uint = static_cast(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255)); @@ -549,12 +549,12 @@ void transformYuv420(jr_uncompressed_ptr image, const std::array& coef //////////////////////////////////////////////////////////////////////////////// // Gain map calculations -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) { - return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->minContentBoost), - log2(metadata->maxContentBoost)); +uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata) { + return encodeGain(y_sdr, y_hdr, metadata, log2(metadata->min_content_boost), + log2(metadata->max_content_boost)); } -uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, +uint8_t encodeGain(float y_sdr, float y_hdr, uhdr_gainmap_metadata_ext_t* metadata, float log2MinContentBoost, float log2MaxContentBoost) { float gain = 1.0f; if (y_sdr > 0.0f) { @@ -569,19 +569,19 @@ uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata, (log2MaxContentBoost - log2MinContentBoost) * 255.0f); } -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) { +Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata) { gain = pow(gain, 1.0f / metadata->gamma); float logBoost = - log2(metadata->minContentBoost) * (1.0f - gain) + log2(metadata->maxContentBoost) * gain; + log2(metadata->min_content_boost) * (1.0f - gain) + log2(metadata->max_content_boost) * gain; float gainFactor = exp2(logBoost); return e * gainFactor; } -Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) { +Color applyGain(Color e, float gain, uhdr_gainmap_metadata_ext_t* metadata, float displayBoost) { gain = pow(gain, 1.0f / metadata->gamma); float logBoost = - log2(metadata->minContentBoost) * (1.0f - gain) + log2(metadata->maxContentBoost) * gain; - float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost); + log2(metadata->min_content_boost) * (1.0f - gain) + log2(metadata->max_content_boost) * gain; + float gainFactor = exp2(logBoost * displayBoost / metadata->max_content_boost); return e * gainFactor; } @@ -590,29 +590,29 @@ Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) { return e * gainFactor; } -Color applyGain(Color e, Color gain, ultrahdr_metadata_ptr metadata) { - float logBoostR = - log2(metadata->minContentBoost) * (1.0f - gain.r) + log2(metadata->maxContentBoost) * gain.r; - float logBoostG = - log2(metadata->minContentBoost) * (1.0f - gain.g) + log2(metadata->maxContentBoost) * gain.g; - float logBoostB = - log2(metadata->minContentBoost) * (1.0f - gain.b) + log2(metadata->maxContentBoost) * gain.b; +Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata) { + float logBoostR = log2(metadata->min_content_boost) * (1.0f - gain.r) + + log2(metadata->max_content_boost) * gain.r; + float logBoostG = log2(metadata->min_content_boost) * (1.0f - gain.g) + + log2(metadata->max_content_boost) * gain.g; + float logBoostB = log2(metadata->min_content_boost) * (1.0f - gain.b) + + log2(metadata->max_content_boost) * gain.b; float gainFactorR = exp2(logBoostR); float gainFactorG = exp2(logBoostG); float gainFactorB = exp2(logBoostB); return {{{e.r * gainFactorR, e.g * gainFactorG, e.b * gainFactorB}}}; } -Color applyGain(Color e, Color gain, ultrahdr_metadata_ptr metadata, float displayBoost) { - float logBoostR = - log2(metadata->minContentBoost) * (1.0f - gain.r) + log2(metadata->maxContentBoost) * gain.r; - float logBoostG = - log2(metadata->minContentBoost) * (1.0f - gain.g) + log2(metadata->maxContentBoost) * gain.g; - float logBoostB = - log2(metadata->minContentBoost) * (1.0f - gain.b) + log2(metadata->maxContentBoost) * gain.b; - float gainFactorR = exp2(logBoostR * displayBoost / metadata->maxContentBoost); - float gainFactorG = exp2(logBoostG * displayBoost / metadata->maxContentBoost); - float gainFactorB = exp2(logBoostB * displayBoost / metadata->maxContentBoost); +Color applyGain(Color e, Color gain, uhdr_gainmap_metadata_ext_t* metadata, float displayBoost) { + float logBoostR = log2(metadata->min_content_boost) * (1.0f - gain.r) + + log2(metadata->max_content_boost) * gain.r; + float logBoostG = log2(metadata->min_content_boost) * (1.0f - gain.g) + + log2(metadata->max_content_boost) * gain.g; + float logBoostB = log2(metadata->min_content_boost) * (1.0f - gain.b) + + log2(metadata->max_content_boost) * gain.b; + float gainFactorR = exp2(logBoostR * displayBoost / metadata->max_content_boost); + float gainFactorG = exp2(logBoostG * displayBoost / metadata->max_content_boost); + float gainFactorB = exp2(logBoostB * displayBoost / metadata->max_content_boost); return {{{e.r * gainFactorR, e.g * gainFactorG, e.b * gainFactorB}}}; } @@ -623,19 +623,21 @@ Color applyGainLUT(Color e, Color gain, GainLUT& gainLUT) { return {{{e.r * gainFactorR, e.g * gainFactorG, e.b * gainFactorB}}}; } -Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { - uint8_t* luma_data = reinterpret_cast(image->data); - size_t luma_stride = image->luma_stride; - uint8_t* chroma_data = reinterpret_cast(image->chroma_data); - size_t chroma_stride = image->chroma_stride; +Color getYuv420Pixel(uhdr_raw_image_t* image, size_t x, size_t y) { + uint8_t* luma_data = reinterpret_cast(image->planes[UHDR_PLANE_Y]); + size_t luma_stride = image->stride[UHDR_PLANE_Y]; + uint8_t* cb_data = reinterpret_cast(image->planes[UHDR_PLANE_U]); + size_t cb_stride = image->stride[UHDR_PLANE_U]; + uint8_t* cr_data = reinterpret_cast(image->planes[UHDR_PLANE_V]); + size_t cr_stride = image->stride[UHDR_PLANE_V]; - size_t offset_cr = chroma_stride * (image->height / 2); size_t pixel_y_idx = x + y * luma_stride; - size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride; + size_t pixel_cb_idx = x / 2 + (y / 2) * cb_stride; + size_t pixel_cr_idx = x / 2 + (y / 2) * cr_stride; uint8_t y_uint = luma_data[pixel_y_idx]; - uint8_t u_uint = chroma_data[pixel_chroma_idx]; - uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx]; + uint8_t u_uint = cb_data[pixel_cb_idx]; + uint8_t v_uint = cr_data[pixel_cr_idx]; // 128 bias for UV given we are using jpeglib; see: // https://github.com/kornelski/libjpeg/blob/master/structure.doc @@ -644,11 +646,11 @@ Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { static_cast(v_uint - 128) * (1 / 255.0f)}}}; } -Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { - uint16_t* luma_data = reinterpret_cast(image->data); - size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride; - uint16_t* chroma_data = reinterpret_cast(image->chroma_data); - size_t chroma_stride = image->chroma_stride; +Color getP010Pixel(uhdr_raw_image_t* image, size_t x, size_t y) { + uint16_t* luma_data = reinterpret_cast(image->planes[UHDR_PLANE_Y]); + size_t luma_stride = image->stride[UHDR_PLANE_Y]; + uint16_t* chroma_data = reinterpret_cast(image->planes[UHDR_PLANE_UV]); + size_t chroma_stride = image->stride[UHDR_PLANE_UV]; size_t pixel_y_idx = y * luma_stride + x; size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1); @@ -658,10 +660,9 @@ Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { uint16_t u_uint = chroma_data[pixel_u_idx] >> 6; uint16_t v_uint = chroma_data[pixel_v_idx] >> 6; - if (image->colorRange == UHDR_CR_FULL_RANGE) { - return {{{static_cast(y_uint) / 1023.0f, - static_cast(u_uint) / 1023.0f - 0.5f, - static_cast(v_uint) / 1023.0f - 0.5f }}}; + if (image->range == UHDR_CR_FULL_RANGE) { + return {{{static_cast(y_uint) / 1023.0f, static_cast(u_uint) / 1023.0f - 0.5f, + static_cast(v_uint) / 1023.0f - 0.5f}}}; } // Conversions include taking narrow-range into account. @@ -670,9 +671,9 @@ Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) { static_cast(v_uint - 64) * (1 / 896.0f) - 0.5f}}}; } -typedef Color (*getPixelFn)(jr_uncompressed_ptr, size_t, size_t); +typedef Color (*getPixelFn)(uhdr_raw_image_t*, size_t, size_t); -static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y, +static Color samplePixels(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y, getPixelFn get_pixel_fn) { Color e = {{{0.0f, 0.0f, 0.0f}}}; for (size_t dy = 0; dy < map_scale_factor; ++dy) { @@ -684,11 +685,11 @@ static Color samplePixels(jr_uncompressed_ptr image, size_t map_scale_factor, si return e / static_cast(map_scale_factor * map_scale_factor); } -Color sampleYuv420(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { +Color sampleYuv420(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) { return samplePixels(image, map_scale_factor, x, y, getYuv420Pixel); } -Color sampleP010(jr_uncompressed_ptr image, size_t map_scale_factor, size_t x, size_t y) { +Color sampleP010(uhdr_raw_image_t* image, size_t map_scale_factor, size_t x, size_t y) { return samplePixels(image, map_scale_factor, x, y, getP010Pixel); } @@ -706,7 +707,7 @@ static float pythDistance(float x_diff, float y_diff) { } // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following. -float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y) { +float sampleMap(uhdr_raw_image_t* map, float map_scale_factor, size_t x, size_t y) { float x_map = static_cast(x) / map_scale_factor; float y_map = static_cast(y) / map_scale_factor; @@ -715,30 +716,32 @@ float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_ size_t y_lower = static_cast(floor(y_map)); size_t y_upper = y_lower + 1; - x_lower = clamp(x_lower, 0, map->width - 1); - x_upper = clamp(x_upper, 0, map->width - 1); - y_lower = clamp(y_lower, 0, map->height - 1); - y_upper = clamp(y_upper, 0, map->height - 1); + x_lower = clamp(x_lower, 0, map->w - 1); + x_upper = clamp(x_upper, 0, map->w - 1); + y_lower = clamp(y_lower, 0, map->h - 1); + y_upper = clamp(y_upper, 0, map->h - 1); // Use Shepard's method for inverse distance weighting. For more information: // en.wikipedia.org/wiki/Inverse_distance_weighting#Shepard's_method + uint8_t* data = reinterpret_cast(map->planes[UHDR_PLANE_Y]); + size_t stride = map->stride[UHDR_PLANE_Y]; - float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); + float e1 = mapUintToFloat(data[x_lower + y_lower * stride]); float e1_dist = pythDistance(x_map - static_cast(x_lower), y_map - static_cast(y_lower)); if (e1_dist == 0.0f) return e1; - float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); + float e2 = mapUintToFloat(data[x_lower + y_upper * stride]); float e2_dist = pythDistance(x_map - static_cast(x_lower), y_map - static_cast(y_upper)); if (e2_dist == 0.0f) return e2; - float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); + float e3 = mapUintToFloat(data[x_upper + y_lower * stride]); float e3_dist = pythDistance(x_map - static_cast(x_upper), y_map - static_cast(y_lower)); if (e3_dist == 0.0f) return e3; - float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); + float e4 = mapUintToFloat(data[x_upper + y_upper * stride]); float e4_dist = pythDistance(x_map - static_cast(x_upper), y_map - static_cast(y_upper)); if (e4_dist == 0.0f) return e2; @@ -753,7 +756,7 @@ float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_ e3 * (e3_weight / total_weight) + e4 * (e4_weight / total_weight); } -float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, +float sampleMap(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y, ShepardsIDW& weightTables) { // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor) @@ -762,15 +765,17 @@ float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size size_t y_lower = y / map_scale_factor; size_t y_upper = y_lower + 1; - x_lower = std::min(x_lower, map->width - 1); - x_upper = std::min(x_upper, map->width - 1); - y_lower = std::min(y_lower, map->height - 1); - y_upper = std::min(y_upper, map->height - 1); + x_lower = std::min(x_lower, (size_t)map->w - 1); + x_upper = std::min(x_upper, (size_t)map->w - 1); + y_lower = std::min(y_lower, (size_t)map->h - 1); + y_upper = std::min(y_upper, (size_t)map->h - 1); - float e1 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_lower * map->width]); - float e2 = mapUintToFloat(reinterpret_cast(map->data)[x_lower + y_upper * map->width]); - float e3 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_lower * map->width]); - float e4 = mapUintToFloat(reinterpret_cast(map->data)[x_upper + y_upper * map->width]); + uint8_t* data = reinterpret_cast(map->planes[UHDR_PLANE_Y]); + size_t stride = map->stride[UHDR_PLANE_Y]; + float e1 = mapUintToFloat(data[x_lower + y_lower * stride]); + float e2 = mapUintToFloat(data[x_lower + y_upper * stride]); + float e3 = mapUintToFloat(data[x_upper + y_lower * stride]); + float e4 = mapUintToFloat(data[x_upper + y_upper * stride]); // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the // following by using & (map_scale_factor - 1) @@ -789,7 +794,7 @@ float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size return e1 * weights[0] + e2 * weights[1] + e3 * weights[2] + e4 * weights[3]; } -Color sampleMap3Channel(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y, +Color sampleMap3Channel(uhdr_raw_image_t* map, float map_scale_factor, size_t x, size_t y, bool has_alpha) { float x_map = static_cast(x) / map_scale_factor; float y_map = static_cast(y) / map_scale_factor; @@ -799,39 +804,30 @@ Color sampleMap3Channel(jr_uncompressed_ptr map, float map_scale_factor, size_t size_t y_lower = static_cast(floor(y_map)); size_t y_upper = y_lower + 1; - x_lower = std::min(x_lower, map->width - 1); - x_upper = std::min(x_upper, map->width - 1); - y_lower = std::min(y_lower, map->height - 1); - y_upper = std::min(y_upper, map->height - 1); + x_lower = std::min(x_lower, (size_t)map->w - 1); + x_upper = std::min(x_upper, (size_t)map->w - 1); + y_lower = std::min(y_lower, (size_t)map->h - 1); + y_upper = std::min(y_upper, (size_t)map->h - 1); int factor = has_alpha ? 4 : 3; - float r1 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_lower * map->width) * factor]); - float r2 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_upper * map->width) * factor]); - float r3 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_lower * map->width) * factor]); - float r4 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_upper * map->width) * factor]); - - float g1 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_lower * map->width) * factor + 1]); - float g2 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_upper * map->width) * factor + 1]); - float g3 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_lower * map->width) * factor + 1]); - float g4 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_upper * map->width) * factor + 1]); - - float b1 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_lower * map->width) * factor + 2]); - float b2 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_upper * map->width) * factor + 2]); - float b3 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_lower * map->width) * factor + 2]); - float b4 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_upper * map->width) * factor + 2]); + uint8_t* data = reinterpret_cast(map->planes[UHDR_PLANE_PACKED]); + size_t stride = map->stride[UHDR_PLANE_PACKED]; + + float r1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor]); + float r2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor]); + float r3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor]); + float r4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor]); + + float g1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 1]); + float g2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 1]); + float g3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 1]); + float g4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 1]); + + float b1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 2]); + float b2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 2]); + float b3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 2]); + float b4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 2]); Color rgb1 = {{{r1, g1, b1}}}; Color rgb2 = {{{r2, g2, b2}}}; @@ -866,7 +862,7 @@ Color sampleMap3Channel(jr_uncompressed_ptr map, float map_scale_factor, size_t rgb3 * (e3_weight / total_weight) + rgb4 * (e4_weight / total_weight); } -Color sampleMap3Channel(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y, +Color sampleMap3Channel(uhdr_raw_image_t* map, size_t map_scale_factor, size_t x, size_t y, ShepardsIDW& weightTables, bool has_alpha) { // TODO: If map_scale_factor is guaranteed to be an integer power of 2, then optimize the // following by computing log2(map_scale_factor) once and then using >> log2(map_scale_factor) @@ -875,39 +871,30 @@ Color sampleMap3Channel(jr_uncompressed_ptr map, size_t map_scale_factor, size_t size_t y_lower = y / map_scale_factor; size_t y_upper = y_lower + 1; - x_lower = std::min(x_lower, map->width - 1); - x_upper = std::min(x_upper, map->width - 1); - y_lower = std::min(y_lower, map->height - 1); - y_upper = std::min(y_upper, map->height - 1); + x_lower = std::min(x_lower, (size_t)map->w - 1); + x_upper = std::min(x_upper, (size_t)map->w - 1); + y_lower = std::min(y_lower, (size_t)map->h - 1); + y_upper = std::min(y_upper, (size_t)map->h - 1); int factor = has_alpha ? 4 : 3; - float r1 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_lower * map->width) * factor]); - float r2 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_upper * map->width) * factor]); - float r3 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_lower * map->width) * factor]); - float r4 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_upper * map->width) * factor]); - - float g1 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_lower * map->width) * factor + 1]); - float g2 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_upper * map->width) * factor + 1]); - float g3 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_lower * map->width) * factor + 1]); - float g4 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_upper * map->width) * factor + 1]); - - float b1 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_lower * map->width) * factor + 2]); - float b2 = mapUintToFloat( - reinterpret_cast(map->data)[(x_lower + y_upper * map->width) * factor + 2]); - float b3 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_lower * map->width) * factor + 2]); - float b4 = mapUintToFloat( - reinterpret_cast(map->data)[(x_upper + y_upper * map->width) * factor + 2]); + uint8_t* data = reinterpret_cast(map->planes[UHDR_PLANE_PACKED]); + size_t stride = map->stride[UHDR_PLANE_PACKED]; + + float r1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor]); + float r2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor]); + float r3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor]); + float r4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor]); + + float g1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 1]); + float g2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 1]); + float g3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 1]); + float g4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 1]); + + float b1 = mapUintToFloat(data[(x_lower + y_lower * stride) * factor + 2]); + float b2 = mapUintToFloat(data[(x_lower + y_upper * stride) * factor + 2]); + float b3 = mapUintToFloat(data[(x_upper + y_lower * stride) * factor + 2]); + float b4 = mapUintToFloat(data[(x_upper + y_upper * stride) * factor + 2]); Color rgb1 = {{{r1, g1, b1}}}; Color rgb2 = {{{r2, g2, b2}}}; @@ -1072,57 +1059,123 @@ std::unique_ptr convert_raw_input_to_ycbcr(uhdr_raw_image_ vData[dst->stride[UHDR_PLANE_V] * (i / 2) + (j / 2)] = uint8_t(pixel[0].v); } } - } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) { + } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420 || src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { dst = std::make_unique(src->fmt, src->cg, src->ct, src->range, src->w, src->h, 64); + auto status = copy_raw_image(src, dst.get()); + if (status.error_code != UHDR_CODEC_OK) return nullptr; + } + return dst; +} - uint8_t* y_dst = static_cast(dst->planes[UHDR_PLANE_Y]); - uint8_t* y_src = static_cast(src->planes[UHDR_PLANE_Y]); - uint8_t* u_dst = static_cast(dst->planes[UHDR_PLANE_U]); - uint8_t* u_src = static_cast(src->planes[UHDR_PLANE_U]); - uint8_t* v_dst = static_cast(dst->planes[UHDR_PLANE_V]); - uint8_t* v_src = static_cast(src->planes[UHDR_PLANE_V]); - - // copy y - for (size_t i = 0; i < src->h; i++) { - memcpy(y_dst, y_src, src->w); - y_dst += dst->stride[UHDR_PLANE_Y]; - y_src += src->stride[UHDR_PLANE_Y]; - } - // copy cb & cr - for (size_t i = 0; i < src->h / 2; i++) { - memcpy(u_dst, u_src, src->w / 2); - memcpy(v_dst, v_src, src->w / 2); - u_dst += dst->stride[UHDR_PLANE_U]; - v_dst += dst->stride[UHDR_PLANE_V]; - u_src += src->stride[UHDR_PLANE_U]; - v_src += src->stride[UHDR_PLANE_V]; - } - } else if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { - dst = std::make_unique(src->fmt, src->cg, src->ct, src->range, - src->w, src->h, 64); +uhdr_error_info_t copy_raw_image(uhdr_raw_image_t* src, uhdr_raw_image_t* dst) { + if (dst->w != src->w || dst->h != src->h) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "destination image dimensions %dx%d and source image dimensions %dx%d are not " + "identical for copy_raw_image", + dst->w, dst->h, src->w, src->h); + return status; + } - int bpp = 2; - uint8_t* y_dst = static_cast(dst->planes[UHDR_PLANE_Y]); - uint8_t* y_src = static_cast(src->planes[UHDR_PLANE_Y]); - uint8_t* uv_dst = static_cast(dst->planes[UHDR_PLANE_UV]); - uint8_t* uv_src = static_cast(src->planes[UHDR_PLANE_UV]); - - // copy y - for (size_t i = 0; i < src->h; i++) { - memcpy(y_dst, y_src, src->w * bpp); - y_dst += (dst->stride[UHDR_PLANE_Y] * bpp); - y_src += (src->stride[UHDR_PLANE_Y] * bpp); + dst->cg = src->cg; + dst->ct = src->ct; + dst->range = src->range; + if (dst->fmt == src->fmt) { + if (src->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + int bpp = 2; + uint8_t* y_dst = static_cast(dst->planes[UHDR_PLANE_Y]); + uint8_t* y_src = static_cast(src->planes[UHDR_PLANE_Y]); + uint8_t* uv_dst = static_cast(dst->planes[UHDR_PLANE_UV]); + uint8_t* uv_src = static_cast(src->planes[UHDR_PLANE_UV]); + + // copy y + for (size_t i = 0; i < src->h; i++) { + memcpy(y_dst, y_src, src->w * bpp); + y_dst += (dst->stride[UHDR_PLANE_Y] * bpp); + y_src += (src->stride[UHDR_PLANE_Y] * bpp); + } + // copy cbcr + for (size_t i = 0; i < src->h / 2; i++) { + memcpy(uv_dst, uv_src, src->w * bpp); + uv_dst += (dst->stride[UHDR_PLANE_UV] * bpp); + uv_src += (src->stride[UHDR_PLANE_UV] * bpp); + } + return g_no_error; + } else if (src->fmt == UHDR_IMG_FMT_12bppYCbCr420) { + uint8_t* y_dst = static_cast(dst->planes[UHDR_PLANE_Y]); + uint8_t* y_src = static_cast(src->planes[UHDR_PLANE_Y]); + uint8_t* u_dst = static_cast(dst->planes[UHDR_PLANE_U]); + uint8_t* u_src = static_cast(src->planes[UHDR_PLANE_U]); + uint8_t* v_dst = static_cast(dst->planes[UHDR_PLANE_V]); + uint8_t* v_src = static_cast(src->planes[UHDR_PLANE_V]); + + // copy y + for (size_t i = 0; i < src->h; i++) { + memcpy(y_dst, y_src, src->w); + y_dst += dst->stride[UHDR_PLANE_Y]; + y_src += src->stride[UHDR_PLANE_Y]; + } + // copy cb & cr + for (size_t i = 0; i < src->h / 2; i++) { + memcpy(u_dst, u_src, src->w / 2); + memcpy(v_dst, v_src, src->w / 2); + u_dst += dst->stride[UHDR_PLANE_U]; + v_dst += dst->stride[UHDR_PLANE_V]; + u_src += src->stride[UHDR_PLANE_U]; + v_src += src->stride[UHDR_PLANE_V]; + } + return g_no_error; + } else if (src->fmt == UHDR_IMG_FMT_8bppYCbCr400 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888 || + src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat || + src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_24bppRGB888) { + uint8_t* plane_dst = static_cast(dst->planes[UHDR_PLANE_PACKED]); + uint8_t* plane_src = static_cast(src->planes[UHDR_PLANE_PACKED]); + int bpp = 1; + + if (src->fmt == UHDR_IMG_FMT_32bppRGBA1010102 || src->fmt == UHDR_IMG_FMT_32bppRGBA8888) + bpp = 4; + else if (src->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) + bpp = 8; + else if (src->fmt == UHDR_IMG_FMT_24bppRGB888) + bpp = 3; + for (size_t i = 0; i < src->h; i++) { + memcpy(plane_dst, plane_src, src->w * bpp); + plane_dst += (bpp * dst->stride[UHDR_PLANE_PACKED]); + plane_src += (bpp * src->stride[UHDR_PLANE_PACKED]); + } + return g_no_error; } - // copy cbcr - for (size_t i = 0; i < src->h / 2; i++) { - memcpy(uv_dst, uv_src, src->w * bpp); - uv_dst += (dst->stride[UHDR_PLANE_UV] * bpp); - uv_src += (src->stride[UHDR_PLANE_UV] * bpp); + } else { + if (src->fmt == UHDR_IMG_FMT_24bppRGB888 && dst->fmt == UHDR_IMG_FMT_32bppRGBA8888) { + uint32_t* plane_dst = static_cast(dst->planes[UHDR_PLANE_PACKED]); + uint8_t* plane_src = static_cast(src->planes[UHDR_PLANE_PACKED]); + for (size_t i = 0; i < src->h; i++) { + uint32_t* pixel_dst = plane_dst; + uint8_t* pixel_src = plane_src; + for (size_t j = 0; j < src->w; j++) { + *pixel_dst = pixel_src[0] | (pixel_src[1] << 8) | (pixel_src[2] << 16) | (0xff << 24); + pixel_src += 3; + pixel_dst += 1; + } + plane_dst += dst->stride[UHDR_PLANE_PACKED]; + plane_src += 3 * src->stride[UHDR_PLANE_PACKED]; + } + return g_no_error; } } - return dst; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf( + status.detail, sizeof status.detail, + "unsupported source / destinations color formats in copy_raw_image, src fmt %d, dst fmt %d", + src->fmt, dst->fmt); + return status; } + // Use double type for intermediate results for better precision. static bool floatToUnsignedFractionImpl(float v, uint32_t maxNumerator, uint32_t* numerator, uint32_t* denominator) { diff --git a/lib/src/gainmapmetadata.cpp b/lib/src/gainmapmetadata.cpp index a2ab54c..a9c5504 100644 --- a/lib/src/gainmapmetadata.cpp +++ b/lib/src/gainmapmetadata.cpp @@ -19,40 +19,53 @@ namespace ultrahdr { -status_t streamWriteU8(std::vector &data, uint8_t value) { - data.push_back(value); - return JPEGR_NO_ERROR; -} +void streamWriteU8(std::vector &data, uint8_t value) { data.push_back(value); } -status_t streamWriteU32(std::vector &data, uint32_t value) { +void streamWriteU32(std::vector &data, uint32_t value) { data.push_back((value >> 24) & 0xff); data.push_back((value >> 16) & 0xff); data.push_back((value >> 8) & 0xff); data.push_back(value & 0xff); - return JPEGR_NO_ERROR; } -status_t streamReadU8(const std::vector &data, uint8_t &value, size_t &pos) { +uhdr_error_info_t streamReadU8(const std::vector &data, uint8_t &value, size_t &pos) { if (pos >= data.size()) { - return ERROR_JPEGR_METADATA_ERROR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "attempting to read byte at position %d when the buffer size is %d", (int)pos, + (int)data.size()); + return status; } value = data[pos++]; - return JPEGR_NO_ERROR; + return g_no_error; } -status_t streamReadU32(const std::vector &data, uint32_t &value, size_t &pos) { +uhdr_error_info_t streamReadU32(const std::vector &data, uint32_t &value, size_t &pos) { if (pos + 3 >= data.size()) { - return ERROR_JPEGR_METADATA_ERROR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "attempting to read 4 bytes from position %d when the buffer size is %d", (int)pos, + (int)data.size()); + return status; } value = (data[pos] << 24 | data[pos + 1] << 16 | data[pos + 2] << 8 | data[pos + 3]); pos += 4; - return JPEGR_NO_ERROR; + return g_no_error; } -status_t gain_map_metadata::encodeGainmapMetadata(const gain_map_metadata *metadata, - std::vector &out_data) { - if (metadata == nullptr) { - return ERROR_JPEGR_METADATA_ERROR; +uhdr_error_info_t uhdr_gainmap_metadata_frac::encodeGainmapMetadata( + const uhdr_gainmap_metadata_frac *in_metadata, std::vector &out_data) { + if (in_metadata == nullptr) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for gain map metadata descriptor"); + return status; } const uint8_t version = 0; @@ -64,47 +77,47 @@ status_t gain_map_metadata::encodeGainmapMetadata(const gain_map_metadata *metad // gain map. But tone mapping is done in RGB space so there are always three // channels, even if the gain map is grayscale. Should this be revised? const bool allChannelsIdentical = - metadata->gainMapMinN[0] == metadata->gainMapMinN[1] && - metadata->gainMapMinN[0] == metadata->gainMapMinN[2] && - metadata->gainMapMinD[0] == metadata->gainMapMinD[1] && - metadata->gainMapMinD[0] == metadata->gainMapMinD[2] && - metadata->gainMapMaxN[0] == metadata->gainMapMaxN[1] && - metadata->gainMapMaxN[0] == metadata->gainMapMaxN[2] && - metadata->gainMapMaxD[0] == metadata->gainMapMaxD[1] && - metadata->gainMapMaxD[0] == metadata->gainMapMaxD[2] && - metadata->gainMapGammaN[0] == metadata->gainMapGammaN[1] && - metadata->gainMapGammaN[0] == metadata->gainMapGammaN[2] && - metadata->gainMapGammaD[0] == metadata->gainMapGammaD[1] && - metadata->gainMapGammaD[0] == metadata->gainMapGammaD[2] && - metadata->baseOffsetN[0] == metadata->baseOffsetN[1] && - metadata->baseOffsetN[0] == metadata->baseOffsetN[2] && - metadata->baseOffsetD[0] == metadata->baseOffsetD[1] && - metadata->baseOffsetD[0] == metadata->baseOffsetD[2] && - metadata->alternateOffsetN[0] == metadata->alternateOffsetN[1] && - metadata->alternateOffsetN[0] == metadata->alternateOffsetN[2] && - metadata->alternateOffsetD[0] == metadata->alternateOffsetD[1] && - metadata->alternateOffsetD[0] == metadata->alternateOffsetD[2]; + in_metadata->gainMapMinN[0] == in_metadata->gainMapMinN[1] && + in_metadata->gainMapMinN[0] == in_metadata->gainMapMinN[2] && + in_metadata->gainMapMinD[0] == in_metadata->gainMapMinD[1] && + in_metadata->gainMapMinD[0] == in_metadata->gainMapMinD[2] && + in_metadata->gainMapMaxN[0] == in_metadata->gainMapMaxN[1] && + in_metadata->gainMapMaxN[0] == in_metadata->gainMapMaxN[2] && + in_metadata->gainMapMaxD[0] == in_metadata->gainMapMaxD[1] && + in_metadata->gainMapMaxD[0] == in_metadata->gainMapMaxD[2] && + in_metadata->gainMapGammaN[0] == in_metadata->gainMapGammaN[1] && + in_metadata->gainMapGammaN[0] == in_metadata->gainMapGammaN[2] && + in_metadata->gainMapGammaD[0] == in_metadata->gainMapGammaD[1] && + in_metadata->gainMapGammaD[0] == in_metadata->gainMapGammaD[2] && + in_metadata->baseOffsetN[0] == in_metadata->baseOffsetN[1] && + in_metadata->baseOffsetN[0] == in_metadata->baseOffsetN[2] && + in_metadata->baseOffsetD[0] == in_metadata->baseOffsetD[1] && + in_metadata->baseOffsetD[0] == in_metadata->baseOffsetD[2] && + in_metadata->alternateOffsetN[0] == in_metadata->alternateOffsetN[1] && + in_metadata->alternateOffsetN[0] == in_metadata->alternateOffsetN[2] && + in_metadata->alternateOffsetD[0] == in_metadata->alternateOffsetD[1] && + in_metadata->alternateOffsetD[0] == in_metadata->alternateOffsetD[2]; const uint8_t channelCount = allChannelsIdentical ? 1u : 3u; if (channelCount == 3) { flags |= 1; } - if (metadata->useBaseColorSpace) { + if (in_metadata->useBaseColorSpace) { flags |= 2; } - if (metadata->backwardDirection) { + if (in_metadata->backwardDirection) { flags |= 4; } - const uint32_t denom = metadata->baseHdrHeadroomD; + const uint32_t denom = in_metadata->baseHdrHeadroomD; bool useCommonDenominator = true; - if (metadata->baseHdrHeadroomD != denom || metadata->alternateHdrHeadroomD != denom) { + if (in_metadata->baseHdrHeadroomD != denom || in_metadata->alternateHdrHeadroomD != denom) { useCommonDenominator = false; } for (int c = 0; c < channelCount; ++c) { - if (metadata->gainMapMinD[c] != denom || metadata->gainMapMaxD[c] != denom || - metadata->gainMapGammaD[c] != denom || metadata->baseOffsetD[c] != denom || - metadata->alternateOffsetD[c] != denom) { + if (in_metadata->gainMapMinD[c] != denom || in_metadata->gainMapMaxD[c] != denom || + in_metadata->gainMapGammaD[c] != denom || in_metadata->baseOffsetD[c] != denom || + in_metadata->alternateOffsetD[c] != denom) { useCommonDenominator = false; } } @@ -115,58 +128,70 @@ status_t gain_map_metadata::encodeGainmapMetadata(const gain_map_metadata *metad if (useCommonDenominator) { streamWriteU32(out_data, denom); - streamWriteU32(out_data, metadata->baseHdrHeadroomN); - streamWriteU32(out_data, metadata->alternateHdrHeadroomN); + streamWriteU32(out_data, in_metadata->baseHdrHeadroomN); + streamWriteU32(out_data, in_metadata->alternateHdrHeadroomN); for (int c = 0; c < channelCount; ++c) { - streamWriteU32(out_data, (uint32_t)metadata->gainMapMinN[c]); - streamWriteU32(out_data, (uint32_t)metadata->gainMapMaxN[c]); - streamWriteU32(out_data, metadata->gainMapGammaN[c]); - streamWriteU32(out_data, (uint32_t)metadata->baseOffsetN[c]); - streamWriteU32(out_data, (uint32_t)metadata->alternateOffsetN[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->gainMapMinN[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->gainMapMaxN[c]); + streamWriteU32(out_data, in_metadata->gainMapGammaN[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->baseOffsetN[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->alternateOffsetN[c]); } } else { - streamWriteU32(out_data, metadata->baseHdrHeadroomN); - streamWriteU32(out_data, metadata->baseHdrHeadroomD); - streamWriteU32(out_data, metadata->alternateHdrHeadroomN); - streamWriteU32(out_data, metadata->alternateHdrHeadroomD); + streamWriteU32(out_data, in_metadata->baseHdrHeadroomN); + streamWriteU32(out_data, in_metadata->baseHdrHeadroomD); + streamWriteU32(out_data, in_metadata->alternateHdrHeadroomN); + streamWriteU32(out_data, in_metadata->alternateHdrHeadroomD); for (int c = 0; c < channelCount; ++c) { - streamWriteU32(out_data, (uint32_t)metadata->gainMapMinN[c]); - streamWriteU32(out_data, metadata->gainMapMinD[c]); - streamWriteU32(out_data, (uint32_t)metadata->gainMapMaxN[c]); - streamWriteU32(out_data, metadata->gainMapMaxD[c]); - streamWriteU32(out_data, metadata->gainMapGammaN[c]); - streamWriteU32(out_data, metadata->gainMapGammaD[c]); - streamWriteU32(out_data, (uint32_t)metadata->baseOffsetN[c]); - streamWriteU32(out_data, metadata->baseOffsetD[c]); - streamWriteU32(out_data, (uint32_t)metadata->alternateOffsetN[c]); - streamWriteU32(out_data, metadata->alternateOffsetD[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->gainMapMinN[c]); + streamWriteU32(out_data, in_metadata->gainMapMinD[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->gainMapMaxN[c]); + streamWriteU32(out_data, in_metadata->gainMapMaxD[c]); + streamWriteU32(out_data, in_metadata->gainMapGammaN[c]); + streamWriteU32(out_data, in_metadata->gainMapGammaD[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->baseOffsetN[c]); + streamWriteU32(out_data, in_metadata->baseOffsetD[c]); + streamWriteU32(out_data, (uint32_t)in_metadata->alternateOffsetN[c]); + streamWriteU32(out_data, in_metadata->alternateOffsetD[c]); } } - return JPEGR_NO_ERROR; + return g_no_error; } -status_t gain_map_metadata::decodeGainmapMetadata(const std::vector &data, - gain_map_metadata *out_metadata) { +uhdr_error_info_t uhdr_gainmap_metadata_frac::decodeGainmapMetadata( + const std::vector &in_data, uhdr_gainmap_metadata_frac *out_metadata) { if (out_metadata == nullptr) { - return ERROR_JPEGR_BAD_PTR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for gain map metadata descriptor"); + return status; } size_t pos = 0; uint8_t version = 0xff; - JPEGR_CHECK(streamReadU8(data, version, pos)) - + UHDR_ERR_CHECK(streamReadU8(in_data, version, pos)) if (version != 0) { - return ERROR_JPEGR_UNSUPPORTED_FEATURE; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received unexpected version %d, expected 0", + version); + return status; } uint8_t flags = 0xff; - JPEGR_CHECK(streamReadU8(data, flags, pos)) - + UHDR_ERR_CHECK(streamReadU8(in_data, flags, pos)) uint8_t channelCount = (flags & 1) * 2 + 1; - if (!(channelCount == 1 || channelCount == 3)) { - return ERROR_JPEGR_UNSUPPORTED_FEATURE; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received unexpected channel count %d, expects one of {1, 3}", channelCount); + return status; } out_metadata->useBaseColorSpace = (flags & 2) != 0; out_metadata->backwardDirection = (flags & 4) != 0; @@ -174,41 +199,41 @@ status_t gain_map_metadata::decodeGainmapMetadata(const std::vector &da if (useCommonDenominator) { uint32_t commonDenominator; - JPEGR_CHECK(streamReadU32(data, commonDenominator, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, commonDenominator, pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->baseHdrHeadroomN, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseHdrHeadroomN, pos)) out_metadata->baseHdrHeadroomD = commonDenominator; - JPEGR_CHECK(streamReadU32(data, out_metadata->alternateHdrHeadroomN, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateHdrHeadroomN, pos)) out_metadata->alternateHdrHeadroomD = commonDenominator; for (int c = 0; c < channelCount; ++c) { - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapMinN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMinN[c], pos)) out_metadata->gainMapMinD[c] = commonDenominator; - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapMaxN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMaxN[c], pos)) out_metadata->gainMapMaxD[c] = commonDenominator; - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapGammaN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapGammaN[c], pos)) out_metadata->gainMapGammaD[c] = commonDenominator; - JPEGR_CHECK(streamReadU32(data, out_metadata->baseOffsetN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseOffsetN[c], pos)) out_metadata->baseOffsetD[c] = commonDenominator; - JPEGR_CHECK(streamReadU32(data, out_metadata->alternateOffsetN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateOffsetN[c], pos)) out_metadata->alternateOffsetD[c] = commonDenominator; } } else { - JPEGR_CHECK(streamReadU32(data, out_metadata->baseHdrHeadroomN, pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->baseHdrHeadroomD, pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->alternateHdrHeadroomN, pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->alternateHdrHeadroomD, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseHdrHeadroomN, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseHdrHeadroomD, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateHdrHeadroomN, pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateHdrHeadroomD, pos)) for (int c = 0; c < channelCount; ++c) { - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapMinN[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapMinD[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapMaxN[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapMaxD[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapGammaN[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->gainMapGammaD[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->baseOffsetN[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->baseOffsetD[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->alternateOffsetN[c], pos)) - JPEGR_CHECK(streamReadU32(data, out_metadata->alternateOffsetD[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMinN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMinD[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMaxN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapMaxD[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapGammaN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->gainMapGammaD[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseOffsetN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->baseOffsetD[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateOffsetN[c], pos)) + UHDR_ERR_CHECK(streamReadU32(in_data, out_metadata->alternateOffsetD[c], pos)) } } @@ -226,59 +251,71 @@ status_t gain_map_metadata::decodeGainmapMetadata(const std::vector &da out_metadata->alternateOffsetD[c] = out_metadata->alternateOffsetD[0]; } - return JPEGR_NO_ERROR; + return g_no_error; } -#define CHECK_NOT_ZERO(x) \ - do { \ - if (x == 0) { \ - return ERROR_JPEGR_METADATA_ERROR; \ - } \ - } while (0) +#define UHDR_CHECK_NON_ZERO(x, message) \ + if (x == 0) { \ + uhdr_error_info_t status; \ + status.error_code = UHDR_CODEC_INVALID_PARAM; \ + status.has_detail = 1; \ + snprintf(status.detail, sizeof status.detail, "received 0 (bad value) for field %s", message); \ + return status; \ + } -status_t gain_map_metadata::gainmapMetadataFractionToFloat(const gain_map_metadata *from, - ultrahdr_metadata_ptr to) { +uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat( + const uhdr_gainmap_metadata_frac *from, uhdr_gainmap_metadata_ext_t *to) { if (from == nullptr || to == nullptr) { - return ERROR_JPEGR_BAD_PTR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for gain map metadata descriptor"); + return status; } - CHECK_NOT_ZERO(from->baseHdrHeadroomD); - CHECK_NOT_ZERO(from->alternateHdrHeadroomD); + UHDR_CHECK_NON_ZERO(from->baseHdrHeadroomD, "baseHdrHeadroom denominator"); + UHDR_CHECK_NON_ZERO(from->alternateHdrHeadroomD, "alternateHdrHeadroom denominator"); for (int i = 0; i < 3; ++i) { - CHECK_NOT_ZERO(from->gainMapMaxD[i]); - CHECK_NOT_ZERO(from->gainMapGammaD[i]); - CHECK_NOT_ZERO(from->gainMapMinD[i]); - CHECK_NOT_ZERO(from->baseOffsetD[i]); - CHECK_NOT_ZERO(from->alternateOffsetD[i]); + UHDR_CHECK_NON_ZERO(from->gainMapMaxD[i], "gainMapMax denominator"); + UHDR_CHECK_NON_ZERO(from->gainMapGammaD[i], "gainMapGamma denominator"); + UHDR_CHECK_NON_ZERO(from->gainMapMinD[i], "gainMapMin denominator"); + UHDR_CHECK_NON_ZERO(from->baseOffsetD[i], "baseOffset denominator"); + UHDR_CHECK_NON_ZERO(from->alternateOffsetD[i], "alternateOffset denominator"); } to->version = kGainMapVersion; - to->maxContentBoost = (float)from->gainMapMaxN[0] / from->gainMapMaxD[0]; - to->minContentBoost = (float)from->gainMapMinN[0] / from->gainMapMinD[0]; + to->max_content_boost = (float)from->gainMapMaxN[0] / from->gainMapMaxD[0]; + to->min_content_boost = (float)from->gainMapMinN[0] / from->gainMapMinD[0]; to->gamma = (float)from->gainMapGammaN[0] / from->gainMapGammaD[0]; // BaseRenditionIsHDR is false - to->offsetSdr = (float)from->baseOffsetN[0] / from->baseOffsetD[0]; - to->offsetHdr = (float)from->alternateOffsetN[0] / from->alternateOffsetD[0]; - to->hdrCapacityMax = (float)from->alternateHdrHeadroomN / from->alternateHdrHeadroomD; - to->hdrCapacityMin = (float)from->baseHdrHeadroomN / from->baseHdrHeadroomD; + to->offset_sdr = (float)from->baseOffsetN[0] / from->baseOffsetD[0]; + to->offset_hdr = (float)from->alternateOffsetN[0] / from->alternateOffsetD[0]; + to->hdr_capacity_max = (float)from->alternateHdrHeadroomN / from->alternateHdrHeadroomD; + to->hdr_capacity_min = (float)from->baseHdrHeadroomN / from->baseHdrHeadroomD; - return JPEGR_NO_ERROR; + return g_no_error; } -status_t gain_map_metadata::gainmapMetadataFloatToFraction(const ultrahdr_metadata_ptr from, - gain_map_metadata *to) { +uhdr_error_info_t uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction( + const uhdr_gainmap_metadata_ext_t *from, uhdr_gainmap_metadata_frac *to) { if (from == nullptr || to == nullptr) { - return ERROR_JPEGR_BAD_PTR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for gain map metadata descriptor"); + return status; } to->backwardDirection = false; to->useBaseColorSpace = true; - floatToUnsignedFraction(from->maxContentBoost, &to->gainMapMaxN[0], &to->gainMapMaxD[0]); + floatToUnsignedFraction(from->max_content_boost, &to->gainMapMaxN[0], &to->gainMapMaxD[0]); to->gainMapMaxN[2] = to->gainMapMaxN[1] = to->gainMapMaxN[0]; to->gainMapMaxD[2] = to->gainMapMaxD[1] = to->gainMapMaxD[0]; - floatToUnsignedFraction(from->minContentBoost, &to->gainMapMinN[0], &to->gainMapMinD[0]); + floatToUnsignedFraction(from->min_content_boost, &to->gainMapMinN[0], &to->gainMapMinD[0]); to->gainMapMinN[2] = to->gainMapMinN[1] = to->gainMapMinN[0]; to->gainMapMinD[2] = to->gainMapMinD[1] = to->gainMapMinD[0]; @@ -286,20 +323,20 @@ status_t gain_map_metadata::gainmapMetadataFloatToFraction(const ultrahdr_metada to->gainMapGammaN[2] = to->gainMapGammaN[1] = to->gainMapGammaN[0]; to->gainMapGammaD[2] = to->gainMapGammaD[1] = to->gainMapGammaD[0]; - floatToUnsignedFraction(from->offsetSdr, &to->baseOffsetN[0], &to->baseOffsetD[0]); + floatToUnsignedFraction(from->offset_sdr, &to->baseOffsetN[0], &to->baseOffsetD[0]); to->baseOffsetN[2] = to->baseOffsetN[1] = to->baseOffsetN[0]; to->baseOffsetD[2] = to->baseOffsetD[1] = to->baseOffsetD[0]; - floatToUnsignedFraction(from->offsetHdr, &to->alternateOffsetN[0], &to->alternateOffsetD[0]); + floatToUnsignedFraction(from->offset_hdr, &to->alternateOffsetN[0], &to->alternateOffsetD[0]); to->alternateOffsetN[2] = to->alternateOffsetN[1] = to->alternateOffsetN[0]; to->alternateOffsetD[2] = to->alternateOffsetD[1] = to->alternateOffsetD[0]; - floatToUnsignedFraction(from->hdrCapacityMin, &to->baseHdrHeadroomN, &to->baseHdrHeadroomD); + floatToUnsignedFraction(from->hdr_capacity_min, &to->baseHdrHeadroomN, &to->baseHdrHeadroomD); - floatToUnsignedFraction(from->hdrCapacityMax, &to->alternateHdrHeadroomN, + floatToUnsignedFraction(from->hdr_capacity_max, &to->alternateHdrHeadroomN, &to->alternateHdrHeadroomD); - return JPEGR_NO_ERROR; + return g_no_error; } } // namespace ultrahdr diff --git a/lib/src/icc.cpp b/lib/src/icc.cpp index b489816..7e8aaae 100644 --- a/lib/src/icc.cpp +++ b/lib/src/icc.cpp @@ -122,17 +122,17 @@ static void float_XYZD50_to_grid16_lab(const float* xyz_float, uint8_t* grid16_l } } -std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf, - const ultrahdr_color_gamut gamut) { +std::string IccHelper::get_desc_string(const uhdr_color_transfer_t tf, + const uhdr_color_gamut_t gamut) { std::string result; switch (gamut) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: result += "sRGB"; break; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: result += "Display P3"; break; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: result += "Rec2020"; break; default: @@ -141,16 +141,16 @@ std::string IccHelper::get_desc_string(const ultrahdr_transfer_function tf, } result += " Gamut with "; switch (tf) { - case ULTRAHDR_TF_SRGB: + case UHDR_CT_SRGB: result += "sRGB"; break; - case ULTRAHDR_TF_LINEAR: + case UHDR_CT_LINEAR: result += "Linear"; break; - case ULTRAHDR_TF_PQ: + case UHDR_CT_PQ: result += "PQ"; break; - case ULTRAHDR_TF_HLG: + case UHDR_CT_HLG: result += "HLG"; break; default: @@ -245,11 +245,11 @@ std::shared_ptr IccHelper::write_trc_tag(const TransferFunction& fn) return dataStruct; } -float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) { +float IccHelper::compute_tone_map_gain(const uhdr_color_transfer_t tf, float L) { if (L <= 0.f) { return 1.f; } - if (tf == ULTRAHDR_TF_PQ) { + if (tf == UHDR_CT_PQ) { // The PQ transfer function will map to the range [0, 1]. Linearly scale // it up to the range [0, 10,000/203]. We will then tone map that back // down to [0, 1]. @@ -262,7 +262,7 @@ float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, floa constexpr float kToneMapB = 1.f / kOutputMaxLuminance; return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L); } - if (tf == ULTRAHDR_TF_HLG) { + if (tf == UHDR_CT_HLG) { // Let Lw be the brightness of the display in nits. constexpr float Lw = 203.f; const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f); @@ -306,7 +306,7 @@ void IccHelper::compute_lut_entry(const Matrix3x3& src_to_XYZD50, float rgb[3]) float L = bt2100Luminance({{{rgb[0], rgb[1], rgb[2]}}}); // Compute the tone map gain based on the luminance. - float tone_map_gain = compute_tone_map_gain(ULTRAHDR_TF_PQ, L); + float tone_map_gain = compute_tone_map_gain(UHDR_CT_PQ, L); // Apply the tone map gain. for (size_t i = 0; i < kNumChannels; ++i) { @@ -408,8 +408,8 @@ std::shared_ptr IccHelper::write_mAB_or_mBA_tag(uint32_t type, bool return dataStruct; } -std::shared_ptr IccHelper::writeIccProfile(ultrahdr_transfer_function tf, - ultrahdr_color_gamut gamut) { +std::shared_ptr IccHelper::writeIccProfile(uhdr_color_transfer_t tf, + uhdr_color_gamut_t gamut) { ICCHeader header; std::vector>> tags; @@ -421,13 +421,13 @@ std::shared_ptr IccHelper::writeIccProfile(ultrahdr_transfer_functio Matrix3x3 toXYZD50; switch (gamut) { - case ULTRAHDR_COLORGAMUT_BT709: + case UHDR_CG_BT_709: toXYZD50 = kSRGB; break; - case ULTRAHDR_COLORGAMUT_P3: + case UHDR_CG_DISPLAY_P3: toXYZD50 = kDisplayP3; break; - case ULTRAHDR_COLORGAMUT_BT2100: + case UHDR_CG_BT_2100: toXYZD50 = kRec2020; break; default: @@ -449,8 +449,8 @@ std::shared_ptr IccHelper::writeIccProfile(ultrahdr_transfer_functio tags.emplace_back(kTAG_wtpt, write_xyz_tag(kD50_x, kD50_y, kD50_z)); // Compute transfer curves. - if (tf != ULTRAHDR_TF_PQ) { - if (tf == ULTRAHDR_TF_HLG) { + if (tf != UHDR_CT_PQ) { + if (tf == UHDR_CT_HLG) { std::vector trc_table; trc_table.resize(kTrcTableSize * 2); for (uint32_t i = 0; i < kTrcTableSize; ++i) { @@ -474,32 +474,32 @@ std::shared_ptr IccHelper::writeIccProfile(ultrahdr_transfer_functio } // Compute CICP. - if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) { + if (tf == UHDR_CT_HLG || tf == UHDR_CT_PQ) { // The CICP tag is present in ICC 4.4, so update the header's version. header.version = Endian_SwapBE32(0x04400000); uint32_t color_primaries = 0; - if (gamut == ULTRAHDR_COLORGAMUT_BT709) { + if (gamut == UHDR_CG_BT_709) { color_primaries = kCICPPrimariesSRGB; - } else if (gamut == ULTRAHDR_COLORGAMUT_P3) { + } else if (gamut == UHDR_CG_DISPLAY_P3) { color_primaries = kCICPPrimariesP3; } uint32_t transfer_characteristics = 0; - if (tf == ULTRAHDR_TF_SRGB) { + if (tf == UHDR_CT_SRGB) { transfer_characteristics = kCICPTrfnSRGB; - } else if (tf == ULTRAHDR_TF_LINEAR) { + } else if (tf == UHDR_CT_LINEAR) { transfer_characteristics = kCICPTrfnLinear; - } else if (tf == ULTRAHDR_TF_PQ) { + } else if (tf == UHDR_CT_PQ) { transfer_characteristics = kCICPTrfnPQ; - } else if (tf == ULTRAHDR_TF_HLG) { + } else if (tf == UHDR_CT_HLG) { transfer_characteristics = kCICPTrfnHLG; } tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics)); } // Compute A2B0. - if (tf == ULTRAHDR_TF_PQ) { + if (tf == UHDR_CT_PQ) { std::vector a2b_grid; a2b_grid.resize(kGridSize * kGridSize * kGridSize * kNumChannels * 2); size_t a2b_grid_index = 0; @@ -530,7 +530,7 @@ std::shared_ptr IccHelper::writeIccProfile(ultrahdr_transfer_functio } // Compute B2A0. - if (tf == ULTRAHDR_TF_PQ) { + if (tf == UHDR_CT_PQ) { auto b2a_data = write_mAB_or_mBA_tag(kTAG_mBAType, /* has_a_curves */ false, /* grid_points */ nullptr, @@ -561,7 +561,7 @@ std::shared_ptr IccHelper::writeIccProfile(ultrahdr_transfer_functio // Write the header. header.data_color_space = Endian_SwapBE32(Signature_RGB); - header.pcs = Endian_SwapBE32(tf == ULTRAHDR_TF_PQ ? Signature_Lab : Signature_XYZ); + header.pcs = Endian_SwapBE32(tf == UHDR_CT_PQ ? Signature_Lab : Signature_XYZ); header.size = Endian_SwapBE32(profile_size); header.tag_count = Endian_SwapBE32(tags.size()); @@ -609,9 +609,8 @@ bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_ta float r_x = FixedToFloat(r_x_fixed); float r_y = FixedToFloat(r_y_fixed); float r_z = FixedToFloat(r_z_fixed); - if (fabs(r_x - matrix.vals[0][0]) > tolerance || - fabs(r_y - matrix.vals[1][0]) > tolerance || - fabs(r_z - matrix.vals[2][0]) > tolerance) { + if (fabs(r_x - matrix.vals[0][0]) > tolerance || fabs(r_y - matrix.vals[1][0]) > tolerance || + fabs(r_z - matrix.vals[2][0]) > tolerance) { return false; } @@ -621,9 +620,8 @@ bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_ta float g_x = FixedToFloat(g_x_fixed); float g_y = FixedToFloat(g_y_fixed); float g_z = FixedToFloat(g_z_fixed); - if (fabs(g_x - matrix.vals[0][1]) > tolerance || - fabs(g_y - matrix.vals[1][1]) > tolerance || - fabs(g_z - matrix.vals[2][1]) > tolerance) { + if (fabs(g_x - matrix.vals[0][1]) > tolerance || fabs(g_y - matrix.vals[1][1]) > tolerance || + fabs(g_z - matrix.vals[2][1]) > tolerance) { return false; } @@ -633,25 +631,24 @@ bool IccHelper::tagsEqualToMatrix(const Matrix3x3& matrix, const uint8_t* red_ta float b_x = FixedToFloat(b_x_fixed); float b_y = FixedToFloat(b_y_fixed); float b_z = FixedToFloat(b_z_fixed); - if (fabs(b_x - matrix.vals[0][2]) > tolerance || - fabs(b_y - matrix.vals[1][2]) > tolerance || - fabs(b_z - matrix.vals[2][2]) > tolerance) { + if (fabs(b_x - matrix.vals[0][2]) > tolerance || fabs(b_y - matrix.vals[1][2]) > tolerance || + fabs(b_z - matrix.vals[2][2]) > tolerance) { return false; } return true; } -ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) { +uhdr_color_gamut_t IccHelper::readIccColorGamut(void* icc_data, size_t icc_size) { // Each tag table entry consists of 3 fields of 4 bytes each. static const size_t kTagTableEntrySize = 12; if (icc_data == nullptr || icc_size < sizeof(ICCHeader) + kICCIdentifierSize) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + return UHDR_CG_UNSPECIFIED; } if (memcmp(icc_data, kICCIdentifier, sizeof(kICCIdentifier)) != 0) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + return UHDR_CG_UNSPECIFIED; } uint8_t* icc_bytes = reinterpret_cast(icc_data) + kICCIdentifierSize; @@ -668,7 +665,7 @@ ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_siz "Insufficient buffer size during icc parsing. tag index %zu, header %zu, tag size %zu, " "icc size %zu", tag_idx, kICCIdentifierSize + sizeof(ICCHeader), kTagTableEntrySize, icc_size); - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + return UHDR_CG_UNSPECIFIED; } uint32_t* tag_entry_start = reinterpret_cast(icc_bytes + sizeof(ICCHeader) + tag_idx * kTagTableEntrySize); @@ -692,7 +689,7 @@ ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_siz kICCIdentifierSize + green_primary_offset + green_primary_size > icc_size || blue_primary_offset == 0 || blue_primary_size != kColorantTagSize || kICCIdentifierSize + blue_primary_offset + blue_primary_size > icc_size) { - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + return UHDR_CG_UNSPECIFIED; } uint8_t* red_tag = icc_bytes + red_primary_offset; @@ -702,16 +699,16 @@ ultrahdr_color_gamut IccHelper::readIccColorGamut(void* icc_data, size_t icc_siz // Serialize tags as we do on encode and compare what we find to that to // determine the gamut (since we don't have a need yet for full deserialize). if (tagsEqualToMatrix(kSRGB, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_BT709; + return UHDR_CG_BT_709; } else if (tagsEqualToMatrix(kDisplayP3, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_P3; + return UHDR_CG_DISPLAY_P3; } else if (tagsEqualToMatrix(kRec2020, red_tag, green_tag, blue_tag)) { - return ULTRAHDR_COLORGAMUT_BT2100; + return UHDR_CG_BT_2100; } // Didn't find a match to one of the profiles we write; indicate the gamut // is unspecified since we don't understand it. - return ULTRAHDR_COLORGAMUT_UNSPECIFIED; + return UHDR_CG_UNSPECIFIED; } } // namespace ultrahdr diff --git a/lib/src/jpegdecoderhelper.cpp b/lib/src/jpegdecoderhelper.cpp index e5c3f81..a5ede3a 100644 --- a/lib/src/jpegdecoderhelper.cpp +++ b/lib/src/jpegdecoderhelper.cpp @@ -21,7 +21,6 @@ #include #include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegdecoderhelper.h" using namespace std; @@ -153,14 +152,21 @@ static uhdr_img_fmt_t getOutputSamplingFormat(const j_decompress_ptr cinfo) { return UHDR_IMG_FMT_UNSPECIFIED; } -bool JpegDecoderHelper::decompressImage(const void* image, int length, decode_mode_t mode) { +uhdr_error_info_t JpegDecoderHelper::decompressImage(const void* image, int length, + decode_mode_t mode) { if (image == nullptr) { - ALOGE("received nullptr for compressed image data"); - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image data"); + return status; } if (length <= 0) { - ALOGE("received bad compressed image size %d", length); - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received bad compressed image size %d", length); + return status; } // reset context @@ -170,20 +176,24 @@ bool JpegDecoderHelper::decompressImage(const void* image, int length, decode_mo mICCBuffer.clear(); mIsoMetadataBuffer.clear(); mOutFormat = UHDR_IMG_FMT_UNSPECIFIED; + mNumComponents = 1; for (int i = 0; i < kMaxNumComponents; i++) { mPlanesMCURow[i].reset(); mPlaneWidth[i] = 0; mPlaneHeight[i] = 0; + mPlaneHStride[i] = 0; + mPlaneVStride[i] = 0; } mExifPayLoadOffset = -1; return decode(image, length, mode); } -bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode) { +uhdr_error_info_t JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode) { jpeg_source_mgr_impl mgr(static_cast(image), length); jpeg_decompress_struct cinfo; jpeg_error_mgr_impl myerr; + uhdr_error_info_t status = g_no_error; cinfo.err = jpeg_std_error(&myerr); myerr.error_exit = jpegrerror_exit; @@ -197,9 +207,12 @@ bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); int ret_val = jpeg_read_header(&cinfo, TRUE /* require an image to be present */); if (JPEG_HEADER_OK != ret_val) { - ALOGE("jpeg_read_header(...) returned %d, expected %d", ret_val, JPEG_HEADER_OK); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "jpeg_read_header(...) returned %d, expected %d", ret_val, JPEG_HEADER_OK); jpeg_destroy_decompress(&cinfo); - return false; + return status; } int payloadOffset = -1; jpeg_extract_marker_payload(&cinfo, kAPP1Marker, kXmpNameSpace, @@ -215,75 +228,108 @@ bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode mIsoMetadataBuffer, payloadOffset); if (cinfo.image_width < 1 || cinfo.image_height < 1) { - ALOGE("received bad image width or height, wd = %d, ht = %d. wd and height shall be >= 1", - cinfo.image_width, cinfo.image_height); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad image width or height, wd = %d, ht = %d. wd and height shall be >= 1", + cinfo.image_width, cinfo.image_height); jpeg_destroy_decompress(&cinfo); - return false; + return status; } if (cinfo.image_width > kMaxWidth || cinfo.image_height > kMaxHeight) { - ALOGE( + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf( + status.detail, sizeof status.detail, "max width, max supported by library are %d, %d respectively. Current image width and " "height are %d, %d. Recompile library with updated max supported dimensions to proceed", kMaxWidth, kMaxHeight, cinfo.image_width, cinfo.image_height); jpeg_destroy_decompress(&cinfo); - return false; + return status; } if (cinfo.num_components != 1 && cinfo.num_components != 3) { - ALOGE( + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf( + status.detail, sizeof status.detail, "ultrahdr primary image and supplimentary images are images encoded with 1 component " "(grayscale) or 3 components (YCbCr / RGB). Unrecognized number of components %d", cinfo.num_components); jpeg_destroy_decompress(&cinfo); - return false; + return status; } for (int i = 0, product = 0; i < cinfo.num_components; i++) { if (cinfo.comp_info[i].h_samp_factor < 1 || cinfo.comp_info[i].h_samp_factor > 4) { - ALOGE( - "received bad horizontal sampling factor for component index %d, sample factor h = %d, " - "this is expected to be with in range [1-4]", - i, cinfo.comp_info[i].h_samp_factor); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad horizontal sampling factor for component index %d, sample factor h " + "= %d, this is expected to be with in range [1-4]", + i, cinfo.comp_info[i].h_samp_factor); jpeg_destroy_decompress(&cinfo); - return false; + return status; } if (cinfo.comp_info[i].v_samp_factor < 1 || cinfo.comp_info[i].v_samp_factor > 4) { - ALOGE( - "received bad vertical sampling factor for component index %d, sample factor v = %d, " - "this is expected to be with in range [1-4]", - i, cinfo.comp_info[i].v_samp_factor); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad vertical sampling factor for component index %d, sample factor v = " + "%d, this is expected to be with in range [1-4]", + i, cinfo.comp_info[i].v_samp_factor); jpeg_destroy_decompress(&cinfo); - return false; + return status; } product += cinfo.comp_info[i].h_samp_factor * cinfo.comp_info[i].v_samp_factor; if (product > 10) { - ALOGE( - "received bad sampling factors for components, sum of product of h_samp_factor, " - "v_samp_factor across all components exceeds 10"); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad sampling factors for components, sum of product of h_samp_factor, " + "v_samp_factor across all components exceeds 10"); jpeg_destroy_decompress(&cinfo); - return false; + return status; } } + mNumComponents = cinfo.num_components; for (int i = 0; i < cinfo.num_components; i++) { mPlaneWidth[i] = std::ceil(((float)cinfo.image_width * cinfo.comp_info[i].h_samp_factor) / cinfo.max_h_samp_factor); + mPlaneHStride[i] = mPlaneWidth[i]; mPlaneHeight[i] = std::ceil(((float)cinfo.image_height * cinfo.comp_info[i].v_samp_factor) / cinfo.max_v_samp_factor); + mPlaneVStride[i] = mPlaneHeight[i]; } - if (cinfo.num_components == 3 && - (mPlaneWidth[1] != mPlaneWidth[2] || mPlaneHeight[1] != mPlaneHeight[2])) { - ALOGE( - "cb, cr planes are not sampled identically. cb width %d, cb height %d, cr width %d, cr " - "height %d", - (int)mPlaneWidth[1], (int)mPlaneWidth[2], (int)mPlaneHeight[1], (int)mPlaneHeight[2]); - jpeg_destroy_decompress(&cinfo); - return false; + if (cinfo.num_components == 3) { + if (mPlaneWidth[1] > mPlaneWidth[0] || mPlaneHeight[2] > mPlaneHeight[0]) { + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "cb, cr planes are upsampled wrt luma plane. luma width %d, luma height %d, cb " + "width %d, cb height %d, cr width %d, cr height %d", + (int)mPlaneWidth[0], (int)mPlaneHeight[0], (int)mPlaneWidth[1], + (int)mPlaneHeight[1], (int)mPlaneWidth[2], (int)mPlaneHeight[2]); + jpeg_destroy_decompress(&cinfo); + return status; + } + if (mPlaneWidth[1] != mPlaneWidth[2] || mPlaneHeight[1] != mPlaneHeight[2]) { + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "cb, cr planes are not sampled identically. cb width %d, cb height %d, cr width " + "%d, cr height %d", + (int)mPlaneWidth[1], (int)mPlaneHeight[1], (int)mPlaneWidth[2], + (int)mPlaneHeight[2]); + jpeg_destroy_decompress(&cinfo); + return status; + } } if (PARSE_STREAM == mode) { jpeg_destroy_decompress(&cinfo); - return true; + return status; } if (DECODE_STREAM == mode) { @@ -292,37 +338,54 @@ bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode if (DECODE_TO_RGB_CS == mode) { if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_RGB) { - ALOGE("expected input color space to be JCS_YCbCr or JCS_RGB but got %d", - cinfo.jpeg_color_space); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "expected input color space to be JCS_YCbCr or JCS_RGB but got %d", + cinfo.jpeg_color_space); jpeg_destroy_decompress(&cinfo); - return false; + return status; + } + mPlaneHStride[0] = cinfo.image_width; + mPlaneVStride[0] = cinfo.image_height; + for (int i = 1; i < kMaxNumComponents; i++) { + mPlaneHStride[i] = 0; + mPlaneVStride[i] = 0; } #ifdef JCS_ALPHA_EXTENSIONS - mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); + mResultBuffer.resize(mPlaneHStride[0] * mPlaneVStride[0] * 4); cinfo.out_color_space = JCS_EXT_RGBA; #else - mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3); + mResultBuffer.resize(mPlaneHStride[0] * mPlaneVStride[0] * 3); cinfo.out_color_space = JCS_RGB; #endif } else if (DECODE_TO_YCBCR_CS == mode) { if (cinfo.jpeg_color_space != JCS_YCbCr && cinfo.jpeg_color_space != JCS_GRAYSCALE) { - ALOGE("expected input color space to be JCS_YCbCr or JCS_GRAYSCALE but got %d", - cinfo.jpeg_color_space); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "expected input color space to be JCS_YCbCr or JCS_GRAYSCALE but got %d", + cinfo.jpeg_color_space); jpeg_destroy_decompress(&cinfo); - return false; + return status; } if (cinfo.jpeg_color_space == JCS_YCbCr) { if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 || cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 || cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) { - ALOGE("apply gainmap supports only 4:2:0 sub sampling format, stopping image decode"); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "apply gainmap supports only 4:2:0 sub sampling format, stopping image decode"); jpeg_destroy_decompress(&cinfo); - return false; + return status; } } int size = 0; for (int i = 0; i < cinfo.num_components; i++) { - size += mPlaneWidth[i] * mPlaneHeight[i]; + mPlaneHStride[i] = ALIGNM(mPlaneWidth[i], cinfo.max_h_samp_factor); + mPlaneVStride[i] = ALIGNM(mPlaneHeight[i], cinfo.max_v_samp_factor); + size += mPlaneHStride[i] * mPlaneVStride[i]; } mResultBuffer.resize(size); cinfo.out_color_space = cinfo.jpeg_color_space; @@ -330,21 +393,25 @@ bool JpegDecoderHelper::decode(const void* image, int length, decode_mode_t mode } cinfo.dct_method = JDCT_ISLOW; jpeg_start_decompress(&cinfo); - if (!decode(&cinfo, static_cast(mResultBuffer.data()))) { + status = decode(&cinfo, static_cast(mResultBuffer.data())); + if (status.error_code != UHDR_CODEC_OK) { jpeg_destroy_decompress(&cinfo); - return false; + return status; } } else { - cinfo.err->output_message((j_common_ptr)&cinfo); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + cinfo.err->format_message((j_common_ptr)&cinfo, status.detail); jpeg_destroy_decompress(&cinfo); - return false; + return status; } jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); - return true; + return status; } -bool JpegDecoderHelper::decode(jpeg_decompress_struct* cinfo, uint8_t* dest) { +uhdr_error_info_t JpegDecoderHelper::decode(jpeg_decompress_struct* cinfo, uint8_t* dest) { + uhdr_error_info_t status = g_no_error; switch (cinfo->out_color_space) { case JCS_GRAYSCALE: [[fallthrough]]; @@ -360,30 +427,37 @@ bool JpegDecoderHelper::decode(jpeg_decompress_struct* cinfo, uint8_t* dest) { mOutFormat = UHDR_IMG_FMT_24bppRGB888; return decodeToCSRGB(cinfo, dest); default: - ALOGE("unrecognized output color space %d", cinfo->out_color_space); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "unrecognized output color space %d", + cinfo->out_color_space); } - return false; + return status; } -bool JpegDecoderHelper::decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest) { +uhdr_error_info_t JpegDecoderHelper::decodeToCSRGB(jpeg_decompress_struct* cinfo, uint8_t* dest) { JSAMPLE* out = (JSAMPLE*)dest; while (cinfo->output_scanline < cinfo->image_height) { JDIMENSION read_lines = jpeg_read_scanlines(cinfo, &out, 1); if (1 != read_lines) { - ALOGE("jpeg_read_scanlines returned %d, expected %d", read_lines, 1); - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "jpeg_read_scanlines returned %d, expected %d", + read_lines, 1); + return status; } #ifdef JCS_ALPHA_EXTENSIONS - out += cinfo->image_width * 4; + out += mPlaneHStride[0] * 4; #else - out += cinfo->image_width * 3; + out += mPlaneHStride[0] * 3; #endif } - return true; + return g_no_error; } -bool JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest) { +uhdr_error_info_t JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* dest) { JSAMPROW mcuRows[kMaxNumComponents][4 * DCTSIZE]; JSAMPROW mcuRowsTmp[kMaxNumComponents][4 * DCTSIZE]; uint8_t* planes[kMaxNumComponents]{}; @@ -392,9 +466,9 @@ bool JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* for (int i = 0, plane_offset = 0; i < cinfo->num_components; i++) { planes[i] = dest + plane_offset; - plane_offset += mPlaneWidth[i] * mPlaneHeight[i]; - alignedPlaneWidth[i] = ALIGNM(mPlaneWidth[i], DCTSIZE); - if (mPlaneWidth[i] != alignedPlaneWidth[i]) { + plane_offset += mPlaneHStride[i] * mPlaneVStride[i]; + alignedPlaneWidth[i] = ALIGNM(mPlaneHStride[i], DCTSIZE); + if (mPlaneHStride[i] != alignedPlaneWidth[i]) { mPlanesMCURow[i] = std::make_unique(alignedPlaneWidth[i] * DCTSIZE * cinfo->comp_info[i].v_samp_factor); uint8_t* mem = mPlanesMCURow[i].get(); @@ -402,10 +476,10 @@ bool JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* j++, mem += alignedPlaneWidth[i]) { mcuRowsTmp[i][j] = mem; } - } else if (mPlaneHeight[i] % DCTSIZE != 0) { + } else if (mPlaneVStride[i] % DCTSIZE != 0) { mPlanesMCURow[i] = std::make_unique(alignedPlaneWidth[i]); } - subImage[i] = mPlaneWidth[i] == alignedPlaneWidth[i] ? mcuRows[i] : mcuRowsTmp[i]; + subImage[i] = mPlaneHStride[i] == alignedPlaneWidth[i] ? mcuRows[i] : mcuRowsTmp[i]; } while (cinfo->output_scanline < cinfo->image_height) { @@ -419,8 +493,8 @@ bool JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) { JDIMENSION scanline = mcu_scanline_start[i] + j; - if (scanline < mPlaneHeight[i]) { - mcuRows[i][j] = planes[i] + scanline * mPlaneWidth[i]; + if (scanline < mPlaneVStride[i]) { + mcuRows[i][j] = planes[i] + scanline * mPlaneHStride[i]; } else { mcuRows[i][j] = mPlanesMCURow[i].get(); } @@ -429,23 +503,46 @@ bool JpegDecoderHelper::decodeToCSYCbCr(jpeg_decompress_struct* cinfo, uint8_t* int processed = jpeg_read_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor); if (processed != DCTSIZE * cinfo->max_v_samp_factor) { - ALOGE("number of scan lines read %d does not equal requested scan lines %d ", processed, - DCTSIZE * cinfo->max_v_samp_factor); - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "number of scan lines read %d does not equal requested scan lines %d ", processed, + DCTSIZE * cinfo->max_v_samp_factor); + return status; } for (int i = 0; i < cinfo->num_components; i++) { - if (mPlaneWidth[i] != alignedPlaneWidth[i]) { + if (mPlaneHStride[i] != alignedPlaneWidth[i]) { for (int j = 0; j < cinfo->comp_info[i].v_samp_factor * DCTSIZE; j++) { JDIMENSION scanline = mcu_scanline_start[i] + j; - if (scanline < mPlaneHeight[i]) { + if (scanline < mPlaneVStride[i]) { memcpy(mcuRows[i][j], mcuRowsTmp[i][j], mPlaneWidth[i]); } } } } } - return true; + return g_no_error; +} + +uhdr_raw_image_t JpegDecoderHelper::getDecompressedImage() { + uhdr_raw_image_t img; + + img.fmt = mOutFormat; + img.cg = UHDR_CG_UNSPECIFIED; + img.ct = UHDR_CT_UNSPECIFIED; + img.range = UHDR_CR_FULL_RANGE; + img.w = mPlaneWidth[0]; + img.h = mPlaneHeight[0]; + uint8_t* data = mResultBuffer.data(); + for (int i = 0; i < 3; i++) { + img.planes[i] = data; + img.stride[i] = mPlaneHStride[i]; + data += mPlaneHStride[i] * mPlaneVStride[i]; + } + + return img; } } // namespace ultrahdr diff --git a/lib/src/jpegencoderhelper.cpp b/lib/src/jpegencoderhelper.cpp index 7e6510f..043c570 100644 --- a/lib/src/jpegencoderhelper.cpp +++ b/lib/src/jpegencoderhelper.cpp @@ -24,7 +24,6 @@ #include #include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegencoderhelper.h" namespace ultrahdr { @@ -105,22 +104,50 @@ static void outputErrorMessage(j_common_ptr cinfo) { ALOGE("%s\n", buffer); } -bool JpegEncoderHelper::compressImage(const uint8_t* planes[3], const size_t strides[3], - const int width, const int height, - const uhdr_img_fmt_t format, const int qfactor, - const void* iccBuffer, const unsigned int iccSize) { +uhdr_error_info_t JpegEncoderHelper::compressImage(const uhdr_raw_image_t* img, const int qfactor, + const void* iccBuffer, + const unsigned int iccSize) { + const uint8_t* planes[3]{reinterpret_cast(img->planes[UHDR_PLANE_Y]), + reinterpret_cast(img->planes[UHDR_PLANE_U]), + reinterpret_cast(img->planes[UHDR_PLANE_V])}; + const size_t strides[3]{img->stride[UHDR_PLANE_Y], img->stride[UHDR_PLANE_U], + img->stride[UHDR_PLANE_V]}; + return compressImage(planes, strides, img->w, img->h, img->fmt, qfactor, iccBuffer, iccSize); +} + +uhdr_error_info_t JpegEncoderHelper::compressImage(const uint8_t* planes[3], + const size_t strides[3], const int width, + const int height, const uhdr_img_fmt_t format, + const int qfactor, const void* iccBuffer, + const unsigned int iccSize) { return encode(planes, strides, width, height, format, qfactor, iccBuffer, iccSize); } -bool JpegEncoderHelper::encode(const uint8_t* planes[3], const size_t strides[3], const int width, - const int height, const uhdr_img_fmt_t format, const int qfactor, - const void* iccBuffer, const unsigned int iccSize) { +uhdr_compressed_image_t JpegEncoderHelper::getCompressedImage() { + uhdr_compressed_image_t img; + + img.data = mDestMgr.mResultBuffer.data(); + img.capacity = img.data_sz = mDestMgr.mResultBuffer.size(); + img.cg = UHDR_CG_UNSPECIFIED; + img.ct = UHDR_CT_UNSPECIFIED; + img.range = UHDR_CR_UNSPECIFIED; + + return img; +} + +uhdr_error_info_t JpegEncoderHelper::encode(const uint8_t* planes[3], const size_t strides[3], + const int width, const int height, + const uhdr_img_fmt_t format, const int qfactor, + const void* iccBuffer, const unsigned int iccSize) { jpeg_compress_struct cinfo; jpeg_error_mgr_impl myerr; + uhdr_error_info_t status = g_no_error; if (sample_factors.find(format) == sample_factors.end()) { - ALOGE("unrecognized format %d", format); - return false; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "unrecognized input format %d", format); + return status; } std::vector& factors = sample_factors.find(format)->second; @@ -177,30 +204,37 @@ bool JpegEncoderHelper::encode(const uint8_t* planes[3], const size_t strides[3] const_cast(&planes[0][cinfo.next_scanline * strides[0] * 3])}; JDIMENSION processed = jpeg_write_scanlines(&cinfo, row_pointer, 1); if (1 != processed) { - ALOGE("jpeg_read_scanlines returned %d, expected %d", processed, 1); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "jpeg_read_scanlines returned %d, expected %d", processed, 1); jpeg_destroy_compress(&cinfo); - return false; + return status; } } } else { - if (!compressYCbCr(&cinfo, planes, strides)) { + status = compressYCbCr(&cinfo, planes, strides); + if (status.error_code != UHDR_CODEC_OK) { jpeg_destroy_compress(&cinfo); - return false; + return status; } } } else { - cinfo.err->output_message((j_common_ptr)&cinfo); + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + cinfo.err->format_message((j_common_ptr)&cinfo, status.detail); jpeg_destroy_compress(&cinfo); - return false; + return status; } jpeg_finish_compress(&cinfo); jpeg_destroy_compress(&cinfo); - return true; + return status; } -bool JpegEncoderHelper::compressYCbCr(jpeg_compress_struct* cinfo, const uint8_t* planes[3], - const size_t strides[3]) { +uhdr_error_info_t JpegEncoderHelper::compressYCbCr(jpeg_compress_struct* cinfo, + const uint8_t* planes[3], + const size_t strides[3]) { JSAMPROW mcuRows[kMaxNumComponents][2 * DCTSIZE]; JSAMPROW mcuRowsTmp[kMaxNumComponents][2 * DCTSIZE]; size_t alignedPlaneWidth[kMaxNumComponents]{}; @@ -251,12 +285,16 @@ bool JpegEncoderHelper::compressYCbCr(jpeg_compress_struct* cinfo, const uint8_t } int processed = jpeg_write_raw_data(cinfo, subImage, DCTSIZE * cinfo->max_v_samp_factor); if (processed != DCTSIZE * cinfo->max_v_samp_factor) { - ALOGE("number of scan lines processed %d does not equal requested scan lines %d ", processed, - DCTSIZE * cinfo->max_v_samp_factor); - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "number of scan lines processed %d does not equal requested scan lines %d ", + processed, DCTSIZE * cinfo->max_v_samp_factor); + return status; } } - return true; + return g_no_error; } } // namespace ultrahdr diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index 06344c3..93d15d0 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -58,6 +58,77 @@ static const float kGainMapGammaDefault = 1.0f; static const bool kWriteXmpMetadata = true; static const bool kWriteIso21496_1Metadata = false; +static const string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/"; +static const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1"; + +static_assert(kWriteXmpMetadata || kWriteIso21496_1Metadata, + "Must write gain map metadata in XMP format, or iso 21496-1 format, or both."); + +class JobQueue { + public: + bool dequeueJob(size_t& rowStart, size_t& rowEnd); + void enqueueJob(size_t rowStart, size_t rowEnd); + void markQueueForEnd(); + void reset(); + + private: + bool mQueuedAllJobs = false; + std::deque> mJobs; + std::mutex mMutex; + std::condition_variable mCv; +}; + +bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) { + std::unique_lock lock{mMutex}; + while (true) { + if (mJobs.empty()) { + if (mQueuedAllJobs) { + return false; + } else { + mCv.wait_for(lock, std::chrono::milliseconds(100)); + } + } else { + auto it = mJobs.begin(); + rowStart = std::get<0>(*it); + rowEnd = std::get<1>(*it); + mJobs.erase(it); + return true; + } + } + return false; +} + +void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) { + std::unique_lock lock{mMutex}; + mJobs.push_back(std::make_tuple(rowStart, rowEnd)); + lock.unlock(); + mCv.notify_one(); +} + +void JobQueue::markQueueForEnd() { + std::unique_lock lock{mMutex}; + mQueuedAllJobs = true; + lock.unlock(); + mCv.notify_all(); +} + +void JobQueue::reset() { + std::unique_lock lock{mMutex}; + mJobs.clear(); + mQueuedAllJobs = false; +} + +/* + * MessageWriter implementation for ALOG functions. + */ +class AlogMessageWriter : public MessageWriter { + public: + void WriteMessage(const Message& message) override { + std::string log = GetFormattedMessage(message); + ALOGD("%s", log.c_str()); + } +}; + int GetCPUCoreCount() { int cpuCoreCount = 1; @@ -77,19 +148,11 @@ int GetCPUCoreCount() { return cpuCoreCount; } -/* - * MessageWriter implementation for ALOG functions. - */ -class AlogMessageWriter : public MessageWriter { - public: - void WriteMessage(const Message& message) override { - std::string log = GetFormattedMessage(message); - ALOGD("%s", log.c_str()); - } -}; - -const string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/"; -const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1"; +JpegR::JpegR(size_t mapDimensionScaleFactor, int mapCompressQuality, bool useMultiChannelGainMap) { + mMapDimensionScaleFactor = mapDimensionScaleFactor; + mMapCompressQuality = mapCompressQuality; + mUseMultiChannelGainMap = useMultiChannelGainMap; +} /* * Helper function copies the JPEG image from without EXIF. @@ -100,1115 +163,928 @@ const string kIsoNameSpace = "urn:iso:std:iso:ts:21496:-1"; * (4 bytes offset to FF sign, the byte after FF E1 XX XX ). * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize(). */ -static void copyJpegWithoutExif(jr_compressed_ptr pDest, jr_compressed_ptr pSource, size_t exif_pos, - size_t exif_size) { +static void copyJpegWithoutExif(uhdr_compressed_image_t* pDest, uhdr_compressed_image_t* pSource, + size_t exif_pos, size_t exif_size) { const size_t exif_offset = 4; // exif_pos has 4 bytes offset to the FF sign - pDest->length = pSource->length - exif_size - exif_offset; - pDest->data = new uint8_t[pDest->length]; - pDest->maxLength = pDest->length; - pDest->colorGamut = pSource->colorGamut; + pDest->data_sz = pSource->data_sz - exif_size - exif_offset; + pDest->data = new uint8_t[pDest->data_sz]; + pDest->capacity = pDest->data_sz; + pDest->cg = pSource->cg; + pDest->ct = pSource->ct; + pDest->range = pSource->range; memcpy(pDest->data, pSource->data, exif_pos - exif_offset); memcpy((uint8_t*)pDest->data + exif_pos - exif_offset, - (uint8_t*)pSource->data + exif_pos + exif_size, pSource->length - exif_pos - exif_size); -} - -status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest_ptr) { - if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) { - ALOGE("Received nullptr for input p010 image"); - return ERROR_JPEGR_BAD_PTR; - } - if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) { - ALOGE("Image dimensions cannot be odd, image dimensions %zux%zu", p010_image_ptr->width, - p010_image_ptr->height); - return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; - } - if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) { - ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %zux%zu", kMinWidth, - kMinHeight, p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; - } - if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) { - ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %zux%zu", kMaxWidth, - kMaxHeight, p010_image_ptr->width, p010_image_ptr->height); - return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; - } - if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || - p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { - ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) { - ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu", - p010_image_ptr->luma_stride, p010_image_ptr->width); - return ERROR_JPEGR_INVALID_STRIDE; - } - if (p010_image_ptr->chroma_data != nullptr && - p010_image_ptr->chroma_stride < p010_image_ptr->width) { - ALOGE("Chroma stride must not be smaller than width, stride=%zu, width=%zu", - p010_image_ptr->chroma_stride, p010_image_ptr->width); - return ERROR_JPEGR_INVALID_STRIDE; - } - if (dest_ptr == nullptr || dest_ptr->data == nullptr) { - ALOGE("Received nullptr for destination"); - return ERROR_JPEGR_BAD_PTR; - } - if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) { - ALOGE("Invalid hdr transfer function %d", hdr_tf); - return ERROR_JPEGR_INVALID_TRANS_FUNC; - } - if (yuv420_image_ptr == nullptr) { - return JPEGR_NO_ERROR; - } - if (yuv420_image_ptr->data == nullptr) { - ALOGE("Received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_BAD_PTR; - } - if (yuv420_image_ptr->luma_stride != 0 && - yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) { - ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu", - yuv420_image_ptr->luma_stride, yuv420_image_ptr->width); - return ERROR_JPEGR_INVALID_STRIDE; - } - if (yuv420_image_ptr->chroma_data != nullptr && - yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) { - ALOGE("Chroma stride must not be smaller than (width / 2), stride=%zu, width=%zu", - yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width); - return ERROR_JPEGR_INVALID_STRIDE; - } - if (p010_image_ptr->width != yuv420_image_ptr->width || - p010_image_ptr->height != yuv420_image_ptr->height) { - ALOGE("Image resolutions mismatch: P010: %zux%zu, YUV420: %zux%zu", p010_image_ptr->width, - p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height); - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || - yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { - ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - return JPEGR_NO_ERROR; -} - -status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest_ptr, int quality) { - if (quality < 0 || quality > 100) { - ALOGE("quality factor is out side range [0-100], quality factor : %d", quality); - return ERROR_JPEGR_INVALID_QUALITY_FACTOR; - } - return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr); -} - -JpegR::JpegR() { - mMapDimensionScaleFactor = kMapDimensionScaleFactorDefault; - mMapCompressQuality = kMapCompressQualityDefault; - mUseMultiChannelGainMap = kUseMultiChannelGainMapDefault; -} - -JpegR::JpegR(size_t mapDimensionScaleFactor, int mapCompressQuality, - bool useMultiChannelGainMap) { - mMapDimensionScaleFactor = mapDimensionScaleFactor; - mMapCompressQuality = mapCompressQuality; - mUseMultiChannelGainMap = useMultiChannelGainMap; + (uint8_t*)pSource->data + exif_pos + exif_size, pSource->data_sz - exif_pos - exif_size); } /* Encode API-0 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { - // validate input arguments - JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality)); - if (exif != nullptr && exif->data == nullptr) { - ALOGE("received nullptr for exif metadata"); - return ERROR_JPEGR_BAD_PTR; - } - - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - - const size_t yu420_luma_stride = ALIGNM(p010_image.width, 16); - unique_ptr yuv420_image_data = - make_unique(yu420_luma_stride * p010_image.height * 3 / 2); - jpegr_uncompressed_struct yuv420_image; - yuv420_image.data = yuv420_image_data.get(); - yuv420_image.width = p010_image.width; - yuv420_image.height = p010_image.height; - yuv420_image.colorGamut = p010_image.colorGamut; - yuv420_image.chroma_data = nullptr; - yuv420_image.luma_stride = yu420_luma_stride; - yuv420_image.chroma_stride = yu420_luma_stride >> 1; - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; +uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_compressed_image_t* dest, + int quality, uhdr_mem_block_t* exif) { + std::unique_ptr sdr_intent = std::make_unique( + UHDR_IMG_FMT_12bppYCbCr420, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, + hdr_intent->w, hdr_intent->h, 64); // tone map - JPEGR_CHECK(toneMap(&p010_image, &yuv420_image, hdr_tf)); + UHDR_ERR_CHECK(toneMap(hdr_intent, sdr_intent.get())); + + // encode + return encodeJPEGR(hdr_intent, sdr_intent.get(), dest, quality, exif); +} - // gain map - ultrahdr_metadata_struct metadata; - metadata.version = kJpegrVersion; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); +/* Encode API-1 */ +uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, + uhdr_compressed_image_t* dest, int quality, + uhdr_mem_block_t* exif) { + // generate gain map + uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); + std::unique_ptr gainmap; + UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap)); // compress gain map JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct compressed_map; - compressed_map.data = jpeg_enc_obj_gm.getCompressedImagePtr(); - compressed_map.length = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - compressed_map.maxLength = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - - std::shared_ptr icc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); - - // convert to Bt601 YUV encoding for JPEG encode - if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) { + UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); + uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); + + std::shared_ptr icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, sdr_intent->cg); + + // convert to bt601 YUV encoding for JPEG encode #if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)) && \ defined(__aarch64__)) - JPEGR_CHECK(convertYuv_neon(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3)); + UHDR_ERR_CHECK(convertYuv_neon(sdr_intent, sdr_intent->cg, UHDR_CG_DISPLAY_P3)); #else - JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3)); + UHDR_ERR_CHECK(convertYuv(sdr_intent, sdr_intent->cg, UHDR_CG_DISPLAY_P3)); #endif - } - // compress 420 image - JpegEncoderHelper jpeg_enc_obj_yuv420; - const uint8_t* planes[3]{reinterpret_cast(yuv420_image.data), - reinterpret_cast(yuv420_image.chroma_data), - reinterpret_cast(yuv420_image.chroma_data) + - yuv420_image.chroma_stride * yuv420_image.height / 2}; - const size_t strides[3]{yuv420_image.luma_stride, yuv420_image.chroma_stride, - yuv420_image.chroma_stride}; - if (!jpeg_enc_obj_yuv420.compressImage(planes, strides, yuv420_image.width, yuv420_image.height, - UHDR_IMG_FMT_12bppYCbCr420, quality, icc->getData(), - icc->getLength())) { - return ERROR_JPEGR_ENCODE_ERROR; - } - jpegr_compressed_struct jpeg; - jpeg.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(); - jpeg.length = static_cast(jpeg_enc_obj_yuv420.getCompressedImageSize()); - jpeg.maxLength = static_cast(jpeg_enc_obj_yuv420.getCompressedImageSize()); - jpeg.colorGamut = yuv420_image.colorGamut; + // compress sdr image + JpegEncoderHelper jpeg_enc_obj_sdr; + UHDR_ERR_CHECK( + jpeg_enc_obj_sdr.compressImage(sdr_intent, quality, icc->getData(), icc->getLength())); + uhdr_compressed_image_t sdr_intent_compressed = jpeg_enc_obj_sdr.getCompressedImage(); + sdr_intent_compressed.cg = sdr_intent->cg; // append gain map, no ICC since JPEG encode already did it - JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, - &metadata, dest)); + UHDR_ERR_CHECK(appendGainMap(&sdr_intent_compressed, &gainmap_compressed, exif, /* icc */ nullptr, + /* icc size */ 0, &metadata, dest)); + return g_no_error; +} - return JPEGR_NO_ERROR; +/* Encode API-2 */ +uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent, + uhdr_compressed_image_t* sdr_intent_compressed, + uhdr_compressed_image_t* dest) { + // generate gain map + uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); + std::unique_ptr gainmap; + UHDR_ERR_CHECK(generateGainMap(sdr_intent, hdr_intent, &metadata, gainmap)); + + // compress gain map + JpegEncoderHelper jpeg_enc_obj_gm; + UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); + uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); + + return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest); } -/* Encode API-1 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf, - jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { - // validate input arguments - if (yuv420_image_ptr == nullptr) { - ALOGE("received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_BAD_PTR; - } - if (exif != nullptr && exif->data == nullptr) { - ALOGE("received nullptr for exif metadata"); - return ERROR_JPEGR_BAD_PTR; +/* Encode API-3 */ +uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, + uhdr_compressed_image_t* sdr_intent_compressed, + uhdr_compressed_image_t* dest) { + // decode input jpeg, gamut is going to be bt601. + JpegDecoderHelper jpeg_dec_obj_sdr; + UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(sdr_intent_compressed->data, + sdr_intent_compressed->data_sz)); + + uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage(); + if (jpeg_dec_obj_sdr.getICCSize() > 0) { + uhdr_color_gamut_t cg = + IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize()); + if (cg == UHDR_CG_UNSPECIFIED || + (sdr_intent_compressed->cg != UHDR_CG_UNSPECIFIED && sdr_intent_compressed->cg != cg)) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "configured color gamut %d does not match with color gamut specified in icc box %d", + sdr_intent_compressed->cg, cg); + return status; + } + sdr_intent.cg = cg; + } else { + if (sdr_intent_compressed->cg <= UHDR_CG_UNSPECIFIED || + sdr_intent_compressed->cg > UHDR_CG_BT_2100) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d", + sdr_intent_compressed->cg); + return status; + } + sdr_intent.cg = sdr_intent_compressed->cg; } - JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality)) - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; - if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; - if (!yuv420_image.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; - yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; + if (hdr_intent->w != sdr_intent.w || hdr_intent->h != sdr_intent.h) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "sdr intent resolution %dx%d and hdr intent resolution %dx%d do not match", + sdr_intent.w, sdr_intent.h, hdr_intent->w, hdr_intent->h); + return status; } - // gain map - ultrahdr_metadata_struct metadata; - metadata.version = kJpegrVersion; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); + // generate gain map + uhdr_gainmap_metadata_ext_t metadata(kJpegrVersion); + std::unique_ptr gainmap; + UHDR_ERR_CHECK( + generateGainMap(&sdr_intent, hdr_intent, &metadata, gainmap, true /* sdr_is_601 */)); // compress gain map JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct compressed_map; - compressed_map.data = jpeg_enc_obj_gm.getCompressedImagePtr(); - compressed_map.length = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - compressed_map.maxLength = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - compressed_map.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - - std::shared_ptr icc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut); - - jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image; - unique_ptr yuv_420_bt601_data; - // Convert to bt601 YUV encoding for JPEG encode - if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) { - const size_t yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, 16); - yuv_420_bt601_data = - make_unique(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2); - yuv420_bt601_image.data = yuv_420_bt601_data.get(); - yuv420_bt601_image.colorGamut = yuv420_image.colorGamut; - yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride; - uint8_t* data = reinterpret_cast(yuv420_bt601_image.data); - yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height; - yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1; - - { - // copy luma - uint8_t* y_dst = reinterpret_cast(yuv420_bt601_image.data); - uint8_t* y_src = reinterpret_cast(yuv420_image.data); - if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) { - memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height); - } else { - for (size_t i = 0; i < yuv420_image.height; i++) { - memcpy(y_dst, y_src, yuv420_image.width); - if (yuv420_image.width != yuv420_bt601_image.luma_stride) { - memset(y_dst + yuv420_image.width, 0, - yuv420_bt601_image.luma_stride - yuv420_image.width); - } - y_dst += yuv420_bt601_image.luma_stride; - y_src += yuv420_image.luma_stride; - } - } - } + UHDR_ERR_CHECK(compressGainMap(gainmap.get(), &jpeg_enc_obj_gm)); + uhdr_compressed_image_t gainmap_compressed = jpeg_enc_obj_gm.getCompressedImage(); - if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) { - // copy luma - uint8_t* ch_dst = reinterpret_cast(yuv420_bt601_image.chroma_data); - uint8_t* ch_src = reinterpret_cast(yuv420_image.chroma_data); - memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height); - } else { - // copy cb & cr - uint8_t* cb_dst = reinterpret_cast(yuv420_bt601_image.chroma_data); - uint8_t* cb_src = reinterpret_cast(yuv420_image.chroma_data); - uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2); - uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2); - for (size_t i = 0; i < yuv420_image.height / 2; i++) { - memcpy(cb_dst, cb_src, yuv420_image.width / 2); - memcpy(cr_dst, cr_src, yuv420_image.width / 2); - if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) { - memset(cb_dst + yuv420_image.width / 2, 0, - yuv420_bt601_image.chroma_stride - yuv420_image.width / 2); - memset(cr_dst + yuv420_image.width / 2, 0, - yuv420_bt601_image.chroma_stride - yuv420_image.width / 2); - } - cb_dst += yuv420_bt601_image.chroma_stride; - cb_src += yuv420_image.chroma_stride; - cr_dst += yuv420_bt601_image.chroma_stride; - cr_src += yuv420_image.chroma_stride; - } - } + return encodeJPEGR(sdr_intent_compressed, &gainmap_compressed, &metadata, dest); +} -#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON)) && \ - defined(__aarch64__)) - JPEGR_CHECK( - convertYuv_neon(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3)); -#else - JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3)); -#endif - } +/* Encode API-4 */ +uhdr_error_info_t JpegR::encodeJPEGR(uhdr_compressed_image_t* base_img_compressed, + uhdr_compressed_image_t* gainmap_img_compressed, + uhdr_gainmap_metadata_ext_t* metadata, + uhdr_compressed_image_t* dest) { + // We just want to check if ICC is present, so don't do a full decode. Note, + // this doesn't verify that the ICC is valid. + JpegDecoderHelper decoder; + UHDR_ERR_CHECK(decoder.parseImage(base_img_compressed->data, base_img_compressed->data_sz)); - // compress 420 image - JpegEncoderHelper jpeg_enc_obj_yuv420; - const uint8_t* planes[3]{reinterpret_cast(yuv420_bt601_image.data), - reinterpret_cast(yuv420_bt601_image.chroma_data), - reinterpret_cast(yuv420_bt601_image.chroma_data) + - yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2}; - const size_t strides[3]{yuv420_bt601_image.luma_stride, yuv420_bt601_image.chroma_stride, - yuv420_bt601_image.chroma_stride}; - if (!jpeg_enc_obj_yuv420.compressImage(planes, strides, yuv420_bt601_image.width, - yuv420_bt601_image.height, UHDR_IMG_FMT_12bppYCbCr420, - quality, icc->getData(), icc->getLength())) { - return ERROR_JPEGR_ENCODE_ERROR; + // Add ICC if not already present. + if (decoder.getICCSize() > 0) { + UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr, + /* icc */ nullptr, /* icc size */ 0, metadata, dest)); + } else { + if (base_img_compressed->cg <= UHDR_CG_UNSPECIFIED || + base_img_compressed->cg > UHDR_CG_BT_2100) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized 420 color gamut %d", + base_img_compressed->cg); + return status; + } + std::shared_ptr newIcc = + IccHelper::writeIccProfile(UHDR_CT_SRGB, base_img_compressed->cg); + UHDR_ERR_CHECK(appendGainMap(base_img_compressed, gainmap_img_compressed, /* exif */ nullptr, + newIcc->getData(), newIcc->getLength(), metadata, dest)); } - jpegr_compressed_struct jpeg; - jpeg.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(); - jpeg.length = static_cast(jpeg_enc_obj_yuv420.getCompressedImageSize()); - jpeg.maxLength = static_cast(jpeg_enc_obj_yuv420.getCompressedImageSize()); - jpeg.colorGamut = yuv420_image.colorGamut; - - // append gain map, no ICC since JPEG encode already did it - JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0, - &metadata, dest)); - return JPEGR_NO_ERROR; + return g_no_error; } -/* Encode API-2 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, - jr_uncompressed_ptr yuv420_image_ptr, - jr_compressed_ptr yuv420jpg_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { - // validate input arguments - if (yuv420_image_ptr == nullptr) { - ALOGE("received nullptr for uncompressed 420 image"); - return ERROR_JPEGR_BAD_PTR; - } - if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_BAD_PTR; - } - JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest)) +uhdr_error_info_t JpegR::convertYuv(uhdr_raw_image_t* image, uhdr_color_gamut_t src_encoding, + uhdr_color_gamut_t dst_encoding) { + const std::array* coeffs_ptr = nullptr; + uhdr_error_info_t status = g_no_error; - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; - } - jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; - if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; - if (!yuv420_image.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height; - yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; + switch (src_encoding) { + case UHDR_CG_BT_709: + switch (dst_encoding) { + case UHDR_CG_BT_709: + return status; + case UHDR_CG_DISPLAY_P3: + coeffs_ptr = &kYuvBt709ToBt601; + break; + case UHDR_CG_BT_2100: + coeffs_ptr = &kYuvBt709ToBt2100; + break; + default: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", + dst_encoding); + return status; + } + break; + case UHDR_CG_DISPLAY_P3: + switch (dst_encoding) { + case UHDR_CG_BT_709: + coeffs_ptr = &kYuvBt601ToBt709; + break; + case UHDR_CG_DISPLAY_P3: + return status; + case UHDR_CG_BT_2100: + coeffs_ptr = &kYuvBt601ToBt2100; + break; + default: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", + dst_encoding); + return status; + } + break; + case UHDR_CG_BT_2100: + switch (dst_encoding) { + case UHDR_CG_BT_709: + coeffs_ptr = &kYuvBt2100ToBt709; + break; + case UHDR_CG_DISPLAY_P3: + coeffs_ptr = &kYuvBt2100ToBt601; + break; + case UHDR_CG_BT_2100: + return status; + default: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized dest color gamut %d", + dst_encoding); + return status; + } + break; + default: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized src color gamut %d", + src_encoding); + return status; } - // gain map - ultrahdr_metadata_struct metadata; - metadata.version = kJpegrVersion; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); + transformYuv420(image, *coeffs_ptr); - // compress gain map - JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct gainmapjpg_image; - gainmapjpg_image.data = jpeg_enc_obj_gm.getCompressedImagePtr(); - gainmapjpg_image.length = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - gainmapjpg_image.maxLength = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - gainmapjpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - - return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest); + return status; } -/* Encode API-3 */ -status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, - jr_compressed_ptr yuv420jpg_image_ptr, - ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { - // validate input arguments - if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_BAD_PTR; - } - JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest)) +uhdr_error_info_t JpegR::compressGainMap(uhdr_raw_image_t* gainmap_img, + JpegEncoderHelper* jpeg_enc_obj) { + return jpeg_enc_obj->compressImage(gainmap_img, mMapCompressQuality, nullptr, 0); +} - // clean up input structure for later usage - jpegr_uncompressed_struct p010_image = *p010_image_ptr; - if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; - if (!p010_image.chroma_data) { - uint16_t* data = reinterpret_cast(p010_image.data); - p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; - p010_image.chroma_stride = p010_image.luma_stride; +uhdr_error_info_t JpegR::generateGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* hdr_intent, + uhdr_gainmap_metadata_ext_t* gainmap_metadata, + std::unique_ptr& gainmap_img, + bool sdr_is_601) { + uhdr_error_info_t status = g_no_error; + + if (sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "generate gainmap method expects sdr intent color format to be one of " + "{UHDR_IMG_FMT_12bppYCbCr420}. Received %d", + sdr_intent->fmt); + return status; + } + if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "generate gainmap method expects hdr intent color format to be one of " + "{UHDR_IMG_FMT_24bppYCbCrP010}. Received %d", + hdr_intent->fmt); + return status; } - // decode input jpeg, gamut is going to be bt601. - JpegDecoderHelper jpeg_dec_obj_yuv420; - if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data, - yuv420jpg_image_ptr->length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - jpegr_uncompressed_struct yuv420_image{}; - yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr(); - yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth(); - yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); - if (jpeg_dec_obj_yuv420.getICCSize() > 0) { - ultrahdr_color_gamut cg = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(), - jpeg_dec_obj_yuv420.getICCSize()); - if (cg == ULTRAHDR_COLORGAMUT_UNSPECIFIED || - (yuv420jpg_image_ptr->colorGamut != ULTRAHDR_COLORGAMUT_UNSPECIFIED && - yuv420jpg_image_ptr->colorGamut != cg)) { - ALOGE("configured color gamut %d does not match with color gamut specified in icc box %d", - yuv420jpg_image_ptr->colorGamut, cg); - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - yuv420_image.colorGamut = cg; - } else { - if (yuv420jpg_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || - yuv420jpg_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { - ALOGE("Unrecognized 420 color gamut %d", yuv420jpg_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_COLORGAMUT; + /*if (mUseMultiChannelGainMap) { + if (!kWriteIso21496_1Metadata || kWriteXmpMetadata) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "Multi-channel gain map is only supported for ISO 21496-1 metadata"); + return status; } - yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut; - } - if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; - if (!yuv420_image.chroma_data) { - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height; - yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; - } + }*/ - if (p010_image_ptr->width != yuv420_image.width || - p010_image_ptr->height != yuv420_image.height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; + ColorTransformFn hdrInvOetf = nullptr; + float hdr_white_nits; + switch (hdr_intent->ct) { + case UHDR_CT_LINEAR: + hdrInvOetf = identityConversion; + hdr_white_nits = kHlgMaxNits; // TODO: configure maxCLL correctly for linear tf + break; + case UHDR_CT_HLG: +#if USE_HLG_INVOETF_LUT + hdrInvOetf = hlgInvOetfLUT; +#else + hdrInvOetf = hlgInvOetf; +#endif + hdr_white_nits = kHlgMaxNits; + break; + case UHDR_CT_PQ: +#if USE_PQ_INVOETF_LUT + hdrInvOetf = pqInvOetfLUT; +#else + hdrInvOetf = pqInvOetf; +#endif + hdr_white_nits = kPqMaxNits; + break; + default: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "Unrecognized hdr intent color transfer characteristics %d", hdr_intent->ct); + return status; } - // gain map - ultrahdr_metadata_struct metadata; - metadata.version = kJpegrVersion; - jpegr_uncompressed_struct gainmap_image; - JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image, - true /* sdr_is_601 */)); - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(gainmap_image.data)); + gainmap_metadata->max_content_boost = hdr_white_nits / kSdrWhiteNits; + gainmap_metadata->min_content_boost = 1.0f; + gainmap_metadata->gamma = kGainMapGammaDefault; + gainmap_metadata->offset_sdr = 0.0f; + gainmap_metadata->offset_hdr = 0.0f; + gainmap_metadata->hdr_capacity_min = 1.0f; + gainmap_metadata->hdr_capacity_max = gainmap_metadata->max_content_boost; - // compress gain map - JpegEncoderHelper jpeg_enc_obj_gm; - JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm)); - jpegr_compressed_struct gainmapjpg_image; - gainmapjpg_image.data = jpeg_enc_obj_gm.getCompressedImagePtr(); - gainmapjpg_image.length = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - gainmapjpg_image.maxLength = static_cast(jpeg_enc_obj_gm.getCompressedImageSize()); - gainmapjpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - - return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest); -} + float log2MinBoost = log2(gainmap_metadata->min_content_boost); + float log2MaxBoost = log2(gainmap_metadata->max_content_boost); -/* Encode API-4 */ -status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, - jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, - jr_compressed_ptr dest) { - if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpeg image"); - return ERROR_JPEGR_BAD_PTR; - } - if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed gain map"); - return ERROR_JPEGR_BAD_PTR; - } - if (dest == nullptr || dest->data == nullptr) { - ALOGE("received nullptr for destination"); - return ERROR_JPEGR_BAD_PTR; - } + ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(sdr_intent->cg, hdr_intent->cg); - // We just want to check if ICC is present, so don't do a full decode. Note, - // this doesn't verify that the ICC is valid. - JpegDecoderHelper decoder; - if (!decoder.parseImage(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length)) { - return ERROR_JPEGR_DECODE_ERROR; + ColorCalculationFn luminanceFn = nullptr; + ColorTransformFn sdrYuvToRgbFn = nullptr; + switch (sdr_intent->cg) { + case UHDR_CG_BT_709: + luminanceFn = srgbLuminance; + sdrYuvToRgbFn = srgbYuvToRgb; + break; + case UHDR_CG_DISPLAY_P3: + luminanceFn = p3Luminance; + sdrYuvToRgbFn = p3YuvToRgb; + break; + case UHDR_CG_BT_2100: + luminanceFn = bt2100Luminance; + sdrYuvToRgbFn = bt2100YuvToRgb; + break; + case UHDR_CG_UNSPECIFIED: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized sdr intent color gamut %d", + sdr_intent->cg); + return status; } - // Add ICC if not already present. - if (decoder.getICCSize() > 0) { - JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr, - /* icc */ nullptr, /* icc size */ 0, metadata, dest)); - } else { - if (yuv420jpg_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || - yuv420jpg_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { - ALOGE("Unrecognized 420 color gamut %d", yuv420jpg_image_ptr->colorGamut); - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - std::shared_ptr newIcc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut); - JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr, - newIcc->getData(), newIcc->getLength(), metadata, dest)); + if (sdr_is_601) { + sdrYuvToRgbFn = p3YuvToRgb; } - return JPEGR_NO_ERROR; -} - -status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr) { - if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpegr image"); - return ERROR_JPEGR_BAD_PTR; - } - if (jpegr_image_info_ptr == nullptr) { - ALOGE("received nullptr for compressed jpegr info struct"); - return ERROR_JPEGR_BAD_PTR; + ColorTransformFn hdrYuvToRgbFn = nullptr; + switch (hdr_intent->cg) { + case UHDR_CG_BT_709: + hdrYuvToRgbFn = srgbYuvToRgb; + break; + case UHDR_CG_DISPLAY_P3: + hdrYuvToRgbFn = p3YuvToRgb; + break; + case UHDR_CG_BT_2100: + hdrYuvToRgbFn = bt2100YuvToRgb; + break; + case UHDR_CG_UNSPECIFIED: + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized hdr intent color gamut %d", + hdr_intent->cg); + return status; } - jpegr_compressed_struct primary_image, gainmap_image; - JPEGR_CHECK(extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image)) - - JPEGR_CHECK(parseJpegInfo(&primary_image, jpegr_image_info_ptr->primaryImgInfo, - &jpegr_image_info_ptr->width, &jpegr_image_info_ptr->height)) - if (jpegr_image_info_ptr->gainmapImgInfo != nullptr) { - JPEGR_CHECK(parseJpegInfo(&gainmap_image, jpegr_image_info_ptr->gainmapImgInfo)) + size_t image_width = sdr_intent->w; + size_t image_height = sdr_intent->h; + size_t map_width = image_width / mMapDimensionScaleFactor; + size_t map_height = image_height / mMapDimensionScaleFactor; + if (map_width == 0 || map_height == 0) { + int scaleFactor = (std::min)(image_width, image_height); + scaleFactor = (scaleFactor >= DCTSIZE) ? (scaleFactor / DCTSIZE) : 1; + ALOGW( + "configured gainmap scale factor is resulting in gainmap width and/or height to be zero, " + "image width %d, image height %d, scale factor %d. Modiyfing gainmap scale factor to %d ", + (int)image_width, (int)image_height, (int)mMapDimensionScaleFactor, scaleFactor); + setMapDimensionScaleFactor(scaleFactor); + map_width = image_width / mMapDimensionScaleFactor; + map_height = image_height / mMapDimensionScaleFactor; } - return JPEGR_NO_ERROR; -} + gainmap_img = std::make_unique( + mUseMultiChannelGainMap ? UHDR_IMG_FMT_24bppRGB888 : UHDR_IMG_FMT_8bppYCbCr400, + UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, map_width, map_height, 64); + uhdr_raw_image_ext_t* dest = gainmap_img.get(); -/* Decode API */ -status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, - float max_display_boost, jr_exif_ptr exif, - ultrahdr_output_format output_format, - jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) { - if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { - ALOGE("received nullptr for compressed jpegr image"); - return ERROR_JPEGR_BAD_PTR; - } - if (dest == nullptr || dest->data == nullptr) { - ALOGE("received nullptr for dest image"); - return ERROR_JPEGR_BAD_PTR; - } - if (max_display_boost < 1.0f) { - ALOGE("received bad value for max_display_boost %f", max_display_boost); - return ERROR_JPEGR_INVALID_DISPLAY_BOOST; - } - if (exif != nullptr && exif->data == nullptr) { - ALOGE("received nullptr address for exif data"); - return ERROR_JPEGR_BAD_PTR; - } - if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) { - ALOGE("received nullptr address for gainmap data"); - return ERROR_JPEGR_BAD_PTR; - } - if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) { - ALOGE("received bad value for output format %d", output_format); - return ERROR_JPEGR_INVALID_OUTPUT_FORMAT; - } + const int threads = (std::min)(GetCPUCoreCount(), 4); + const int jobSizeInRows = mMapDimensionScaleFactor; + size_t rowStep = threads == 1 ? image_height : jobSizeInRows; + JobQueue jobQueue; + std::function generateMap = [this, sdr_intent, hdr_intent, gainmap_metadata, dest, + hdrInvOetf, hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, + hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost, + &jobQueue]() -> void { + size_t rowStart, rowEnd; + while (jobQueue.dequeueJob(rowStart, rowEnd)) { + for (size_t y = rowStart; y < rowEnd; ++y) { + for (size_t x = 0; x < dest->w; ++x) { + Color sdr_yuv_gamma = sampleYuv420(sdr_intent, mMapDimensionScaleFactor, x, y); + Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); + // We are assuming the SDR input is always sRGB transfer. +#if USE_SRGB_INVOETF_LUT + Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); +#else + Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); +#endif - jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image; - JPEGR_CHECK( - extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image)) + Color hdr_yuv_gamma = sampleP010(hdr_intent, mMapDimensionScaleFactor, x, y); + Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); + Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); + hdr_rgb = hdrGamutConversionFn(hdr_rgb); - JpegDecoderHelper jpeg_dec_obj_yuv420; - if (!jpeg_dec_obj_yuv420.decompressImage( - primary_jpeg_image.data, primary_jpeg_image.length, - (output_format == ULTRAHDR_OUTPUT_SDR) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS)) { - return ERROR_JPEGR_DECODE_ERROR; - } + if (mUseMultiChannelGainMap) { + Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits; + Color hdr_rgb_nits = hdr_rgb * hdr_white_nits; + size_t pixel_idx = (x + y * dest->stride[UHDR_PLANE_PACKED]) * 3; + + reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = encodeGain( + sdr_rgb_nits.r, hdr_rgb_nits.r, gainmap_metadata, log2MinBoost, log2MaxBoost); + reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 1] = encodeGain( + sdr_rgb_nits.g, hdr_rgb_nits.g, gainmap_metadata, log2MinBoost, log2MaxBoost); + reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx + 2] = encodeGain( + sdr_rgb_nits.b, hdr_rgb_nits.b, gainmap_metadata, log2MinBoost, log2MaxBoost); + } else { + float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; + float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; + size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_Y]; - if (output_format == ULTRAHDR_OUTPUT_SDR) { -#ifdef JCS_ALPHA_EXTENSIONS - if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * - jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) > - jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_DECODE_ERROR; - } -#else - if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * - jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3) > - jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_DECODE_ERROR; - } -#endif - } else { - if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() * - jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) > - jpeg_dec_obj_yuv420.getDecompressedImageSize()) { - return ERROR_JPEGR_DECODE_ERROR; + reinterpret_cast(dest->planes[UHDR_PLANE_Y])[pixel_idx] = + encodeGain(sdr_y_nits, hdr_y_nits, gainmap_metadata, log2MinBoost, log2MaxBoost); + } + } + } } - } + }; - if (exif != nullptr) { - if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; - } - memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize()); - exif->length = jpeg_dec_obj_yuv420.getEXIFSize(); + // generate map + std::vector workers; + for (int th = 0; th < threads - 1; th++) { + workers.push_back(std::thread(generateMap)); } - JpegDecoderHelper jpeg_dec_obj_gm; - jpegr_uncompressed_struct gainmap_image; - if (gainmap_image_ptr != nullptr || output_format != ULTRAHDR_OUTPUT_SDR) { - if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length, - DECODE_STREAM)) { - return ERROR_JPEGR_DECODE_ERROR; - } - gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr(); - gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth(); - gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight(); - gainmap_image.pixelFormat = jpeg_dec_obj_gm.getDecompressedImageFormat(); - - if (gainmap_image_ptr != nullptr) { - gainmap_image_ptr->width = gainmap_image.width; - gainmap_image_ptr->height = gainmap_image.height; - gainmap_image_ptr->pixelFormat = gainmap_image.pixelFormat; - memcpy(gainmap_image_ptr->data, gainmap_image.data, - gainmap_image_ptr->width * gainmap_image_ptr->height); + for (size_t rowStart = 0; rowStart < map_height;) { + size_t rowEnd = (std::min)(rowStart + rowStep, map_height); + jobQueue.enqueueJob(rowStart, rowEnd); + rowStart = rowEnd; + } + jobQueue.markQueueForEnd(); + generateMap(); + std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); + + return status; +} + +// JPEG/R structure: +// SOI (ff d8) +// +// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents +// in the JPEG input (Encode API-2, API-3, API-4)) +// APP1 (ff e1) +// 2 bytes of length (2 + length of exif package) +// EXIF package (this includes the first two bytes representing the package length) +// +// (Required, XMP package) APP1 (ff e1) +// 2 bytes of length (2 + 29 + length of xmp package) +// name space ("http://ns.adobe.com/xap/1.0/\0") +// XMP +// +// (Required, ISO 21496-1 metadata, version only) APP2 (ff e2) +// 2 bytes of length +// name space (""urn:iso:std:iso:ts:21496:-1\0") +// 2 bytes minimum_version: (00 00) +// 2 bytes writer_version: (00 00) +// +// (Required, MPF package) APP2 (ff e2) +// 2 bytes of length +// MPF +// +// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages) +// +// SOI (ff d8) +// +// (Required, XMP package) APP1 (ff e1) +// 2 bytes of length (2 + 29 + length of xmp package) +// name space ("http://ns.adobe.com/xap/1.0/\0") +// XMP +// +// (Required, ISO 21496-1 metadata) APP2 (ff e2) +// 2 bytes of length +// name space (""urn:iso:std:iso:ts:21496:-1\0") +// metadata +// +// (Required) secondary image (the gain map, without the first two bytes (SOI)) +// +// Metadata versions we are using: +// ECMA TR-98 for JFIF marker +// Exif 2.2 spec for EXIF marker +// Adobe XMP spec part 3 for XMP marker +// ICC v4.3 spec for ICC +uhdr_error_info_t JpegR::appendGainMap(uhdr_compressed_image_t* sdr_intent_compressed, + uhdr_compressed_image_t* gainmap_compressed, + uhdr_mem_block_t* pExif, void* pIcc, size_t icc_size, + uhdr_gainmap_metadata_ext_t* metadata, + uhdr_compressed_image_t* dest) { + const int xmpNameSpaceLength = kXmpNameSpace.size() + 1; // need to count the null terminator + const int isoNameSpaceLength = kIsoNameSpace.size() + 1; // need to count the null terminator + + ///////////////////////////////////////////////////////////////////////////////////////////////// + // calculate secondary image length first, because the length will be written into the primary // + // image xmp // + ///////////////////////////////////////////////////////////////////////////////////////////////// + // XMP + const string xmp_secondary = generateXmpForSecondaryImage(*metadata); + // xmp_secondary_length = 2 bytes representing the length of the package + + // + xmpNameSpaceLength = 29 bytes length + // + length of xmp packet = xmp_secondary.size() + const int xmp_secondary_length = 2 + xmpNameSpaceLength + xmp_secondary.size(); + // ISO + uhdr_gainmap_metadata_frac iso_secondary_metadata; + std::vector iso_secondary_data; + UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction( + metadata, &iso_secondary_metadata)); + + UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::encodeGainmapMetadata(&iso_secondary_metadata, + iso_secondary_data)); + + // iso_secondary_length = 2 bytes representing the length of the package + + // + isoNameSpaceLength = 28 bytes length + // + length of iso metadata packet = iso_secondary_data.size() + const int iso_secondary_length = 2 + isoNameSpaceLength + iso_secondary_data.size(); + + int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + gainmap_compressed->data_sz; + if (kWriteXmpMetadata) { + secondary_image_size += xmp_secondary_length; + } + if (kWriteIso21496_1Metadata) { + secondary_image_size += iso_secondary_length; + } + + // Check if EXIF package presents in the JPEG input. + // If so, extract and remove the EXIF package. + JpegDecoderHelper decoder; + UHDR_ERR_CHECK(decoder.parseImage(sdr_intent_compressed->data, sdr_intent_compressed->data_sz)); + + uhdr_mem_block_t exif_from_jpg; + exif_from_jpg.data = nullptr; + exif_from_jpg.data_sz = 0; + + uhdr_compressed_image_t new_jpg_image; + new_jpg_image.data = nullptr; + new_jpg_image.data_sz = 0; + new_jpg_image.capacity = 0; + new_jpg_image.cg = UHDR_CG_UNSPECIFIED; + new_jpg_image.ct = UHDR_CT_UNSPECIFIED; + new_jpg_image.range = UHDR_CR_UNSPECIFIED; + + std::unique_ptr dest_data; + if (decoder.getEXIFPos() >= 0) { + if (pExif != nullptr) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received exif from uhdr_enc_set_exif_data() while the base image intent already " + "contains exif, unsure which one to use"); + return status; + } + copyJpegWithoutExif(&new_jpg_image, sdr_intent_compressed, decoder.getEXIFPos(), + decoder.getEXIFSize()); + dest_data.reset(reinterpret_cast(new_jpg_image.data)); + exif_from_jpg.data = decoder.getEXIFPtr(); + exif_from_jpg.data_sz = decoder.getEXIFSize(); + pExif = &exif_from_jpg; + } + + uhdr_compressed_image_t* final_primary_jpg_image_ptr = + new_jpg_image.data_sz == 0 ? sdr_intent_compressed : &new_jpg_image; + + int pos = 0; + // Begin primary image + // Write SOI + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + + // Write EXIF + if (pExif != nullptr) { + const int length = 2 + pExif->data_sz; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, pExif->data, pExif->data_sz, pos)); + } + + // Prepare and write XMP + if (kWriteXmpMetadata) { + const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata); + const int length = 2 + xmpNameSpaceLength + xmp_primary.size(); + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); + } + + // Write ICC + if (pIcc != nullptr && icc_size > 0) { + const int length = icc_size + 2; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, pIcc, icc_size, pos)); + } + + // Prepare and write ISO 21496-1 metadata + if (kWriteIso21496_1Metadata) { + const int length = 2 + isoNameSpaceLength + 4; + uint8_t zero = 0; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos)); + UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes minimum_version: (00 00) + UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes writer_version: (00 00) + } + + // Prepare and write MPF + { + const int length = 2 + calculateMpfSize(); + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + int primary_image_size = pos + length + final_primary_jpg_image_ptr->data_sz; + // between APP2 + package size + signature + // ff e2 00 58 4d 50 46 00 + // 2 + 2 + 4 = 8 (bytes) + // and ff d8 sign of the secondary image + int secondary_image_offset = primary_image_size - pos - 8; + std::shared_ptr mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */ + secondary_image_size, secondary_image_offset); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos)); + } + + // Write primary image + UHDR_ERR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2, + final_primary_jpg_image_ptr->data_sz - 2, pos)); + // Finish primary image + + // Begin secondary image (gain map) + // Write SOI + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + + // Prepare and write XMP + if (kWriteXmpMetadata) { + const int length = xmp_secondary_length; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos)); + } + + // Prepare and write ISO 21496-1 metadata + if (kWriteIso21496_1Metadata) { + const int length = iso_secondary_length; + const uint8_t lengthH = ((length >> 8) & 0xff); + const uint8_t lengthL = (length & 0xff); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthH, 1, pos)); + UHDR_ERR_CHECK(Write(dest, &lengthL, 1, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos)); + UHDR_ERR_CHECK(Write(dest, (void*)iso_secondary_data.data(), iso_secondary_data.size(), pos)); + } + + // Write secondary image + UHDR_ERR_CHECK( + Write(dest, (uint8_t*)gainmap_compressed->data + 2, gainmap_compressed->data_sz - 2, pos)); + + // Set back length + dest->data_sz = pos; + + // Done! + return g_no_error; +} + +uhdr_error_info_t JpegR::getJPEGRInfo(uhdr_compressed_image_t* uhdr_compressed_img, + jr_info_ptr uhdr_image_info) { + uhdr_compressed_image_t primary_image, gainmap; + + UHDR_ERR_CHECK(extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_image, &gainmap)) + + UHDR_ERR_CHECK(parseJpegInfo(&primary_image, uhdr_image_info->primaryImgInfo, + &uhdr_image_info->width, &uhdr_image_info->height)) + if (uhdr_image_info->gainmapImgInfo != nullptr) { + UHDR_ERR_CHECK(parseJpegInfo(&gainmap, uhdr_image_info->gainmapImgInfo)) + } + + return g_no_error; +} + +/* Decode API */ +uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_img, + uhdr_raw_image_t* dest, float max_display_boost, + uhdr_color_transfer_t output_ct, uhdr_img_fmt_t output_format, + uhdr_raw_image_t* gainmap_img, + uhdr_gainmap_metadata_t* gainmap_metadata) { + uhdr_compressed_image_t primary_jpeg_image, gainmap_jpeg_image; + UHDR_ERR_CHECK( + extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_jpeg_image, &gainmap_jpeg_image)) + + JpegDecoderHelper jpeg_dec_obj_sdr; + UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage( + primary_jpeg_image.data, primary_jpeg_image.data_sz, + (output_ct == UHDR_CT_SRGB) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS)); + + JpegDecoderHelper jpeg_dec_obj_gm; + uhdr_raw_image_t gainmap; + if (gainmap_img != nullptr || output_ct != UHDR_CT_SRGB) { + UHDR_ERR_CHECK(jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, + gainmap_jpeg_image.data_sz, DECODE_STREAM)); + gainmap = jpeg_dec_obj_gm.getDecompressedImage(); + if (gainmap_img != nullptr) { + UHDR_ERR_CHECK(copy_raw_image(&gainmap, gainmap_img)); } } - ultrahdr_metadata_struct uhdr_metadata; - if (metadata != nullptr || output_format != ULTRAHDR_OUTPUT_SDR) { + uhdr_gainmap_metadata_ext_t uhdr_metadata; + if (gainmap_metadata != nullptr || output_ct != UHDR_CT_SRGB) { uint8_t* iso_ptr = static_cast(jpeg_dec_obj_gm.getIsoMetadataPtr()); if (iso_ptr != nullptr) { size_t iso_size = jpeg_dec_obj_gm.getIsoMetadataSize(); if (iso_size < kIsoNameSpace.size() + 1) { - return ERROR_JPEGR_METADATA_ERROR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "iso block size needs to be atleast %d but got %d", (int)kIsoNameSpace.size() + 1, + (int)iso_size); + return status; } - gain_map_metadata decodedMetadata; + uhdr_gainmap_metadata_frac decodedMetadata; std::vector iso_vec; for (size_t i = kIsoNameSpace.size() + 1; i < iso_size; i++) { iso_vec.push_back(iso_ptr[i]); } - JPEGR_CHECK(gain_map_metadata::decodeGainmapMetadata(iso_vec, &decodedMetadata)); - JPEGR_CHECK( - gain_map_metadata::gainmapMetadataFractionToFloat(&decodedMetadata, &uhdr_metadata)); + UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::decodeGainmapMetadata(iso_vec, &decodedMetadata)); + UHDR_ERR_CHECK(uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&decodedMetadata, + &uhdr_metadata)); } else { - if (!getMetadataFromXMP(static_cast(jpeg_dec_obj_gm.getXMPPtr()), - jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) { - return ERROR_JPEGR_METADATA_ERROR; - } + UHDR_ERR_CHECK(getMetadataFromXMP(static_cast(jpeg_dec_obj_gm.getXMPPtr()), + jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)); } - if (metadata != nullptr) { - metadata->version = uhdr_metadata.version; - metadata->minContentBoost = uhdr_metadata.minContentBoost; - metadata->maxContentBoost = uhdr_metadata.maxContentBoost; - metadata->gamma = uhdr_metadata.gamma; - metadata->offsetSdr = uhdr_metadata.offsetSdr; - metadata->offsetHdr = uhdr_metadata.offsetHdr; - metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin; - metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax; + if (gainmap_metadata != nullptr) { + gainmap_metadata->min_content_boost = uhdr_metadata.min_content_boost; + gainmap_metadata->max_content_boost = uhdr_metadata.max_content_boost; + gainmap_metadata->gamma = uhdr_metadata.gamma; + gainmap_metadata->offset_sdr = uhdr_metadata.offset_sdr; + gainmap_metadata->offset_hdr = uhdr_metadata.offset_hdr; + gainmap_metadata->hdr_capacity_min = uhdr_metadata.hdr_capacity_min; + gainmap_metadata->hdr_capacity_max = uhdr_metadata.hdr_capacity_max; } } - if (output_format == ULTRAHDR_OUTPUT_SDR) { - dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth(); - dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); -#ifdef JCS_ALPHA_EXTENSIONS - memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(), - dest->width * dest->height * 4); -#else - uint32_t* pixelDst = static_cast(dest->data); - uint8_t* pixelSrc = static_cast(jpeg_dec_obj_yuv420.getDecompressedImagePtr()); - for (int i = 0; i < dest->width * dest->height; i++) { - *pixelDst = pixelSrc[0] | (pixelSrc[1] << 8) | (pixelSrc[2] << 16) | (0xff << 24); - pixelSrc += 3; - pixelDst += 1; - } -#endif - dest->colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(), - jpeg_dec_obj_yuv420.getICCSize()); - return JPEGR_NO_ERROR; + uhdr_raw_image_t sdr_intent = jpeg_dec_obj_sdr.getDecompressedImage(); + sdr_intent.cg = + IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize()); + if (output_ct == UHDR_CT_SRGB) { + UHDR_ERR_CHECK(copy_raw_image(&sdr_intent, dest)); + return g_no_error; } - jpegr_uncompressed_struct yuv420_image; - yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr(); - yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth(); - yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight(); - yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(), - jpeg_dec_obj_yuv420.getICCSize()); - yuv420_image.luma_stride = yuv420_image.width; - uint8_t* data = reinterpret_cast(yuv420_image.data); - yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; - yuv420_image.chroma_stride = yuv420_image.width >> 1; + UHDR_ERR_CHECK(applyGainMap(&sdr_intent, &gainmap, &uhdr_metadata, output_ct, output_format, + max_display_boost, dest)); - JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format, - max_display_boost, dest)); - return JPEGR_NO_ERROR; + return g_no_error; } -status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr, - JpegEncoderHelper* jpeg_enc_obj_ptr) { - if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) { - return ERROR_JPEGR_BAD_PTR; +uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_t* gainmap_img, + uhdr_gainmap_metadata_ext_t* gainmap_metadata, + uhdr_color_transfer_t output_ct, + [[maybe_unused]] uhdr_img_fmt_t output_format, + float max_display_boost, uhdr_raw_image_t* dest) { + if (gainmap_metadata->version.compare(kJpegrVersion)) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unsupported metadata version: %s", + gainmap_metadata->version.c_str()); + return status; + } + if (gainmap_metadata->offset_sdr != 0.0f || gainmap_metadata->offset_hdr != 0.0f) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unsupported metadata offset sdr, hdr: %f, %f", + gainmap_metadata->offset_sdr, gainmap_metadata->offset_hdr); + return status; + } + if (gainmap_metadata->hdr_capacity_min != gainmap_metadata->min_content_boost || + gainmap_metadata->hdr_capacity_max != gainmap_metadata->max_content_boost) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "Unsupported metadata hdr capacity min, max: %f, %f", + gainmap_metadata->hdr_capacity_min, gainmap_metadata->hdr_capacity_max); + return status; + } + if (sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "apply gainmap method expects sdr intent color format to be one of " + "{UHDR_IMG_FMT_12bppYCbCr420}. Received %d", + sdr_intent->fmt); + return status; + } + if (gainmap_img->fmt != UHDR_IMG_FMT_8bppYCbCr400 && + gainmap_img->fmt != UHDR_IMG_FMT_24bppRGB888 && + gainmap_img->fmt != UHDR_IMG_FMT_32bppRGBA8888) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "apply gainmap method expects gainmap image color format to be one of " + "{UHDR_IMG_FMT_8bppYCbCr400, UHDR_IMG_FMT_24bppRGB888, UHDR_IMG_FMT_32bppRGBA8888}. " + "Received %d", + gainmap_img->fmt); + return status; } - const uint8_t* planes[]{reinterpret_cast(gainmap_image_ptr->data)}; - if (mUseMultiChannelGainMap) { - const size_t strides[]{gainmap_image_ptr->width}; - if (!jpeg_enc_obj_ptr->compressImage(planes, strides, gainmap_image_ptr->width, - gainmap_image_ptr->height, UHDR_IMG_FMT_24bppRGB888, - mMapCompressQuality, nullptr, 0)) { - return ERROR_JPEGR_ENCODE_ERROR; - } - } else { - const size_t strides[]{gainmap_image_ptr->width}; - // Don't need to convert YUV to Bt601 since single channel - if (!jpeg_enc_obj_ptr->compressImage(planes, strides, gainmap_image_ptr->width, - gainmap_image_ptr->height, UHDR_IMG_FMT_8bppYCbCr400, - mMapCompressQuality, nullptr, 0)) { - return ERROR_JPEGR_ENCODE_ERROR; + { + float primary_aspect_ratio = (float)sdr_intent->w / sdr_intent->h; + float gainmap_aspect_ratio = (float)gainmap_img->w / gainmap_img->h; + float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio); + // Allow 1% delta + const float delta_tolerance = 0.01; + if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf( + status.detail, sizeof status.detail, + "gain map dimensions scale factor values for height and width are different, \n primary " + "image resolution is %ux%u, received gain map resolution is %ux%u", + sdr_intent->w, sdr_intent->h, gainmap_img->w, gainmap_img->h); + return status; } } - return JPEGR_NO_ERROR; -} + float map_scale_factor = (float)sdr_intent->w / gainmap_img->w; -class JobQueue { - public: - bool dequeueJob(size_t& rowStart, size_t& rowEnd); - void enqueueJob(size_t rowStart, size_t rowEnd); - void markQueueForEnd(); - void reset(); + dest->cg = sdr_intent->cg; + // Table will only be used when map scale factor is integer. + ShepardsIDW idwTable(static_cast(map_scale_factor)); + float display_boost = (std::min)(max_display_boost, gainmap_metadata->max_content_boost); + GainLUT gainLUT(gainmap_metadata, display_boost); - private: - bool mQueuedAllJobs = false; - std::deque> mJobs; - std::mutex mMutex; - std::condition_variable mCv; -}; + JobQueue jobQueue; + std::function applyRecMap = [sdr_intent, gainmap_img, dest, &jobQueue, &idwTable, + output_ct, &gainLUT, display_boost, + map_scale_factor]() -> void { + size_t width = sdr_intent->w; -bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) { - std::unique_lock lock{mMutex}; - while (true) { - if (mJobs.empty()) { - if (mQueuedAllJobs) { - return false; - } else { - mCv.wait_for(lock, std::chrono::milliseconds(100)); - } - } else { - auto it = mJobs.begin(); - rowStart = std::get<0>(*it); - rowEnd = std::get<1>(*it); - mJobs.erase(it); - return true; - } - } - return false; -} - -void JobQueue::enqueueJob(size_t rowStart, size_t rowEnd) { - std::unique_lock lock{mMutex}; - mJobs.push_back(std::make_tuple(rowStart, rowEnd)); - lock.unlock(); - mCv.notify_one(); -} - -void JobQueue::markQueueForEnd() { - std::unique_lock lock{mMutex}; - mQueuedAllJobs = true; - lock.unlock(); - mCv.notify_all(); -} - -void JobQueue::reset() { - std::unique_lock lock{mMutex}; - mJobs.clear(); - mQueuedAllJobs = false; -} - -status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr p010_image_ptr, - ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata, - jr_uncompressed_ptr dest, bool sdr_is_601) { - /*if (mUseMultiChannelGainMap) { - static_assert(kWriteIso21496_1Metadata && !kWriteXmpMetadata, - "Multi-channel gain map now is only supported for ISO 21496-1 metadata"); - }*/ - - int gainMapChannelCount = mUseMultiChannelGainMap ? 3 : 1; - - if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr || - dest == nullptr || yuv420_image_ptr->data == nullptr || - yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr || - p010_image_ptr->chroma_data == nullptr) { - return ERROR_JPEGR_BAD_PTR; - } - if (yuv420_image_ptr->width != p010_image_ptr->width || - yuv420_image_ptr->height != p010_image_ptr->height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; - } - if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED || - p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - size_t image_width = yuv420_image_ptr->width; - size_t image_height = yuv420_image_ptr->height; - size_t map_width = image_width / mMapDimensionScaleFactor; - size_t map_height = image_height / mMapDimensionScaleFactor; - - if (map_width == 0 || map_height == 0) { - int scaleFactor = (std::min)(image_width, image_height); - scaleFactor = (scaleFactor >= DCTSIZE) ? (scaleFactor / DCTSIZE) : 1; - ALOGW( - "configured gainmap scale factor is resulting in gainmap width and/or height to be zero, " - "image width %d, image height %d, scale factor %d. Modiyfing gainmap scale factor to %d ", - (int)image_width, (int)image_height, (int)mMapDimensionScaleFactor, scaleFactor); - setMapDimensionScaleFactor(scaleFactor); - map_width = image_width / mMapDimensionScaleFactor; - map_height = image_height / mMapDimensionScaleFactor; - } - - dest->data = new uint8_t[map_width * map_height * gainMapChannelCount]; - dest->width = map_width; - dest->height = map_height; - dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - dest->luma_stride = map_width; - dest->chroma_data = nullptr; - dest->chroma_stride = 0; - std::unique_ptr map_data; - map_data.reset(reinterpret_cast(dest->data)); - - ColorTransformFn hdrInvOetf = nullptr; - float hdr_white_nits; - switch (hdr_tf) { - case ULTRAHDR_TF_LINEAR: - hdrInvOetf = identityConversion; - // Note: this will produce clipping if the input exceeds kHlgMaxNits. - // TODO: TF LINEAR will be deprecated. - hdr_white_nits = kHlgMaxNits; - break; - case ULTRAHDR_TF_HLG: -#if USE_HLG_INVOETF_LUT - hdrInvOetf = hlgInvOetfLUT; -#else - hdrInvOetf = hlgInvOetf; -#endif - hdr_white_nits = kHlgMaxNits; - break; - case ULTRAHDR_TF_PQ: -#if USE_PQ_INVOETF_LUT - hdrInvOetf = pqInvOetfLUT; -#else - hdrInvOetf = pqInvOetf; -#endif - hdr_white_nits = kPqMaxNits; - break; - default: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_TRANS_FUNC; - } - - metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits; - metadata->minContentBoost = 1.0f; - metadata->gamma = kGainMapGammaDefault; - metadata->offsetSdr = 0.0f; - metadata->offsetHdr = 0.0f; - metadata->hdrCapacityMin = 1.0f; - metadata->hdrCapacityMax = metadata->maxContentBoost; - - float log2MinBoost = log2(metadata->minContentBoost); - float log2MaxBoost = log2(metadata->maxContentBoost); - - ColorTransformFn hdrGamutConversionFn = - getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut); - - ColorCalculationFn luminanceFn = nullptr; - ColorTransformFn sdrYuvToRgbFn = nullptr; - switch (yuv420_image_ptr->colorGamut) { - case ULTRAHDR_COLORGAMUT_BT709: - luminanceFn = srgbLuminance; - sdrYuvToRgbFn = srgbYuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_P3: - luminanceFn = p3Luminance; - sdrYuvToRgbFn = p3YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - luminanceFn = bt2100Luminance; - sdrYuvToRgbFn = bt2100YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - if (sdr_is_601) { - sdrYuvToRgbFn = p3YuvToRgb; - } - - ColorTransformFn hdrYuvToRgbFn = nullptr; - switch (p010_image_ptr->colorGamut) { - case ULTRAHDR_COLORGAMUT_BT709: - hdrYuvToRgbFn = srgbYuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_P3: - hdrYuvToRgbFn = p3YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - hdrYuvToRgbFn = bt2100YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - - const int threads = (std::min)(GetCPUCoreCount(), 4); - const int jobSizeInRows = 4; - size_t rowStep = threads == 1 ? image_height : jobSizeInRows; - JobQueue jobQueue; - std::function generateMap; - - if (mUseMultiChannelGainMap) { - - generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf, - hdrGamutConversionFn, sdrYuvToRgbFn, gainMapChannelCount, hdrYuvToRgbFn, - hdr_white_nits, log2MinBoost, log2MaxBoost, &jobQueue, this]() -> void { - size_t rowStart, rowEnd; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < dest->width; ++x) { - Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, mMapDimensionScaleFactor, x, y); - Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); - // We are assuming the SDR input is always sRGB transfer. -#if USE_SRGB_INVOETF_LUT - Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); -#else - Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); -#endif - Color sdr_rgb_nits = sdr_rgb * kSdrWhiteNits; - - Color hdr_yuv_gamma = sampleP010(p010_image_ptr, mMapDimensionScaleFactor, x, y); - Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); - Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); - hdr_rgb = hdrGamutConversionFn(hdr_rgb); - Color hdr_rgb_nits = hdr_rgb * hdr_white_nits; - - size_t pixel_idx = (x + y * dest->width) * gainMapChannelCount; - - // R - reinterpret_cast(dest->data)[pixel_idx] = - encodeGain(sdr_rgb_nits.r, hdr_rgb_nits.r, metadata, log2MinBoost, log2MaxBoost); - // G - reinterpret_cast(dest->data)[pixel_idx + 1] = - encodeGain(sdr_rgb_nits.g, hdr_rgb_nits.g, metadata, log2MinBoost, log2MaxBoost); - // B - reinterpret_cast(dest->data)[pixel_idx + 2] = - encodeGain(sdr_rgb_nits.b, hdr_rgb_nits.b, metadata, log2MinBoost, log2MaxBoost); - } - } - } - }; - } else { - generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf, - hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn, hdrYuvToRgbFn, hdr_white_nits, - log2MinBoost, log2MaxBoost, &jobQueue, this]() -> void { - size_t rowStart, rowEnd; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < dest->width; ++x) { - Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, mMapDimensionScaleFactor, x, y); - Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma); - // We are assuming the SDR input is always sRGB transfer. -#if USE_SRGB_INVOETF_LUT - Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma); -#else - Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma); -#endif - float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits; - - Color hdr_yuv_gamma = sampleP010(p010_image_ptr, mMapDimensionScaleFactor, x, y); - Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); - Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); - hdr_rgb = hdrGamutConversionFn(hdr_rgb); - float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits; - - size_t pixel_idx = x + y * dest->width; - reinterpret_cast(dest->data)[pixel_idx] = - encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost); - } - } - } - }; - } - - // generate map - std::vector workers; - for (int th = 0; th < threads - 1; th++) { - workers.push_back(std::thread(generateMap)); - } - - for (size_t rowStart = 0; rowStart < map_height;) { - size_t rowEnd = (std::min)(rowStart + rowStep, map_height); - jobQueue.enqueueJob(rowStart, rowEnd); - rowStart = rowEnd; - } - jobQueue.markQueueForEnd(); - generateMap(); - std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - - map_data.release(); - - return JPEGR_NO_ERROR; -} - -status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, - jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata, - ultrahdr_output_format output_format, float max_display_boost, - jr_uncompressed_ptr dest) { - if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr || - dest == nullptr || yuv420_image_ptr->data == nullptr || - yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) { - return ERROR_JPEGR_BAD_PTR; - } - if (metadata->version.compare(kJpegrVersion)) { - ALOGE("Unsupported metadata version: %s", metadata->version.c_str()); - return ERROR_JPEGR_BAD_METADATA; - } - if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) { - ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr); - return ERROR_JPEGR_BAD_METADATA; - } - if (metadata->hdrCapacityMin != metadata->minContentBoost || - metadata->hdrCapacityMax != metadata->maxContentBoost) { - ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin, - metadata->hdrCapacityMax); - return ERROR_JPEGR_BAD_METADATA; - } - - { - float primary_aspect_ratio = (float) yuv420_image_ptr->width / yuv420_image_ptr->height; - float gainmap_aspect_ratio = (float) gainmap_image_ptr->width / gainmap_image_ptr->height; - float delta_aspect_ratio = fabs(primary_aspect_ratio - gainmap_aspect_ratio); - // Allow 1% delta - const float delta_tolerance = 0.01; - if (delta_aspect_ratio / primary_aspect_ratio > delta_tolerance) { - ALOGE( - "gain map dimensions scale factor values for height and width are different, \n primary " - "image resolution is %zux%zu, received gain map resolution is %zux%zu", - yuv420_image_ptr->width, yuv420_image_ptr->height, gainmap_image_ptr->width, - gainmap_image_ptr->height); - return ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR; - } - } - - float map_scale_factor = (float) yuv420_image_ptr->width / gainmap_image_ptr->width; - - dest->width = yuv420_image_ptr->width; - dest->height = yuv420_image_ptr->height; - dest->colorGamut = yuv420_image_ptr->colorGamut; - // Table will only be used when map scale factor is integer. - ShepardsIDW idwTable(static_cast(map_scale_factor)); - float display_boost = (std::min)(max_display_boost, metadata->maxContentBoost); - GainLUT gainLUT(metadata, display_boost); - - JobQueue jobQueue; - std::function applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, dest, &jobQueue, - &idwTable, output_format, &gainLUT, display_boost, - map_scale_factor]() -> void { - size_t width = yuv420_image_ptr->width; - - size_t rowStart, rowEnd; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; ++y) { - for (size_t x = 0; x < width; ++x) { - Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y); - // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients - Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); - // We are assuming the SDR base image is always sRGB transfer. -#if USE_SRGB_INVOETF_LUT - Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr); -#else - Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr); -#endif - Color rgb_hdr; - if (gainmap_image_ptr->pixelFormat == UHDR_IMG_FMT_8bppYCbCr400) { - float gain; + size_t rowStart, rowEnd; + while (jobQueue.dequeueJob(rowStart, rowEnd)) { + for (size_t y = rowStart; y < rowEnd; ++y) { + for (size_t x = 0; x < width; ++x) { + Color yuv_gamma_sdr = getYuv420Pixel(sdr_intent, x, y); + // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients + Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); + // We are assuming the SDR base image is always sRGB transfer. +#if USE_SRGB_INVOETF_LUT + Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr); +#else + Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr); +#endif + Color rgb_hdr; + if (gainmap_img->fmt == UHDR_IMG_FMT_8bppYCbCr400) { + float gain; if (map_scale_factor != floorf(map_scale_factor)) { - gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y); + gain = sampleMap(gainmap_img, map_scale_factor, x, y); } else { - gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable); + gain = sampleMap(gainmap_img, map_scale_factor, x, y, idwTable); } #if USE_APPLY_GAIN_LUT @@ -1220,13 +1096,11 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, Color gain; if (map_scale_factor != floorf(map_scale_factor)) { - gain = - sampleMap3Channel(gainmap_image_ptr, map_scale_factor, x, y, - gainmap_image_ptr->pixelFormat == UHDR_IMG_FMT_32bppRGBA8888); + gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y, + gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888); } else { - gain = - sampleMap3Channel(gainmap_image_ptr, map_scale_factor, x, y, idwTable, - gainmap_image_ptr->pixelFormat == UHDR_IMG_FMT_32bppRGBA8888); + gain = sampleMap3Channel(gainmap_img, map_scale_factor, x, y, idwTable, + gainmap_img->fmt == UHDR_IMG_FMT_32bppRGBA8888); } #if USE_APPLY_GAIN_LUT @@ -1237,15 +1111,15 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, } rgb_hdr = rgb_hdr / display_boost; - size_t pixel_idx = x + y * width; + size_t pixel_idx = x + y * dest->stride[UHDR_PLANE_PACKED]; - switch (output_format) { - case ULTRAHDR_OUTPUT_HDR_LINEAR: { + switch (output_ct) { + case UHDR_CT_LINEAR: { uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr); - reinterpret_cast(dest->data)[pixel_idx] = rgba_f16; + reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = rgba_f16; break; } - case ULTRAHDR_OUTPUT_HDR_HLG: { + case UHDR_CT_HLG: { #if USE_HLG_OETF_LUT ColorTransformFn hdrOetf = hlgOetfLUT; #else @@ -1253,10 +1127,11 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, #endif Color rgb_gamma_hdr = hdrOetf(rgb_hdr); uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); - reinterpret_cast(dest->data)[pixel_idx] = rgba_1010102; + reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = + rgba_1010102; break; } - case ULTRAHDR_OUTPUT_HDR_PQ: { + case UHDR_CT_PQ: { #if USE_PQ_OETF_LUT ColorTransformFn hdrOetf = pqOetfLUT; #else @@ -1264,7 +1139,8 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, #endif Color rgb_gamma_hdr = hdrOetf(rgb_hdr); uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr); - reinterpret_cast(dest->data)[pixel_idx] = rgba_1010102; + reinterpret_cast(dest->planes[UHDR_PLANE_PACKED])[pixel_idx] = + rgba_1010102; break; } default: { @@ -1281,62 +1157,75 @@ status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr, for (int th = 0; th < threads - 1; th++) { workers.push_back(std::thread(applyRecMap)); } - const int rowStep = threads == 1 ? yuv420_image_ptr->height : map_scale_factor; - for (size_t rowStart = 0; rowStart < yuv420_image_ptr->height;) { - int rowEnd = (std::min)(rowStart + rowStep, yuv420_image_ptr->height); + const int rowStep = threads == 1 ? sdr_intent->h : map_scale_factor; + for (size_t rowStart = 0; rowStart < sdr_intent->h;) { + int rowEnd = (std::min)(rowStart + rowStep, (size_t)sdr_intent->h); jobQueue.enqueueJob(rowStart, rowEnd); rowStart = rowEnd; } jobQueue.markQueueForEnd(); applyRecMap(); std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - return JPEGR_NO_ERROR; + return g_no_error; } -status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, - jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr) { - if (jpegr_image_ptr == nullptr) { - return ERROR_JPEGR_BAD_PTR; - } - +uhdr_error_info_t JpegR::extractPrimaryImageAndGainMap(uhdr_compressed_image_t* jpegr_image, + uhdr_compressed_image_t* primary_image, + uhdr_compressed_image_t* gainmap_image) { MessageHandler msg_handler; msg_handler.SetMessageWriter(make_unique(AlogMessageWriter())); + std::shared_ptr seg = DataSegment::Create( - DataRange(0, jpegr_image_ptr->length), static_cast(jpegr_image_ptr->data), + DataRange(0, jpegr_image->data_sz), static_cast(jpegr_image->data), DataSegment::BufferDispositionPolicy::kDontDelete); DataSegmentDataSource data_source(seg); + JpegInfoBuilder jpeg_info_builder; jpeg_info_builder.SetImageLimit(2); + JpegScanner jpeg_scanner(&msg_handler); jpeg_scanner.Run(&data_source, &jpeg_info_builder); data_source.Reset(); if (jpeg_scanner.HasError()) { - return JPEGR_UNKNOWN_ERROR; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + auto messages = msg_handler.GetMessages(); + std::string append{}; + for (auto message : messages) append += message.GetText(); + snprintf(status.detail, sizeof status.detail, "%s", append.c_str()); + return status; } const auto& jpeg_info = jpeg_info_builder.GetInfo(); const auto& image_ranges = jpeg_info.GetImageRanges(); if (image_ranges.empty()) { - return ERROR_JPEGR_NO_IMAGES_FOUND; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "input uhdr image does not any valid images"); + return status; } - if (primary_jpg_image_ptr != nullptr) { - primary_jpg_image_ptr->data = - static_cast(jpegr_image_ptr->data) + image_ranges[0].GetBegin(); - primary_jpg_image_ptr->length = image_ranges[0].GetLength(); + if (primary_image != nullptr) { + primary_image->data = static_cast(jpegr_image->data) + image_ranges[0].GetBegin(); + primary_image->data_sz = image_ranges[0].GetLength(); } if (image_ranges.size() == 1) { - return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "input uhdr image does not contain gainmap image"); + return status; } - if (gainmap_jpg_image_ptr != nullptr) { - gainmap_jpg_image_ptr->data = - static_cast(jpegr_image_ptr->data) + image_ranges[1].GetBegin(); - gainmap_jpg_image_ptr->length = image_ranges[1].GetLength(); + if (gainmap_image != nullptr) { + gainmap_image->data = static_cast(jpegr_image->data) + image_ranges[1].GetBegin(); + gainmap_image->data_sz = image_ranges[1].GetLength(); } // TODO: choose primary image and gain map image carefully @@ -1345,38 +1234,37 @@ status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr, (int)image_ranges.size()); } - return JPEGR_NO_ERROR; + return g_no_error; } -status_t JpegR::parseJpegInfo(jr_compressed_ptr jpeg_image_ptr, j_info_ptr jpeg_image_info_ptr, - size_t* img_width, size_t* img_height) { +uhdr_error_info_t JpegR::parseJpegInfo(uhdr_compressed_image_t* jpeg_image, j_info_ptr image_info, + size_t* img_width, size_t* img_height) { JpegDecoderHelper jpeg_dec_obj; - if (!jpeg_dec_obj.parseImage(jpeg_image_ptr->data, jpeg_image_ptr->length)) { - return ERROR_JPEGR_DECODE_ERROR; - } - size_t imgWidth, imgHeight; + UHDR_ERR_CHECK(jpeg_dec_obj.parseImage(jpeg_image->data, jpeg_image->data_sz)) + size_t imgWidth, imgHeight, numComponents; imgWidth = jpeg_dec_obj.getDecompressedImageWidth(); imgHeight = jpeg_dec_obj.getDecompressedImageHeight(); - - if (jpeg_image_info_ptr != nullptr) { - jpeg_image_info_ptr->width = imgWidth; - jpeg_image_info_ptr->height = imgHeight; - jpeg_image_info_ptr->imgData.resize(jpeg_image_ptr->length, 0); - memcpy(static_cast(jpeg_image_info_ptr->imgData.data()), jpeg_image_ptr->data, - jpeg_image_ptr->length); + numComponents = jpeg_dec_obj.getNumComponentsInImage(); + + if (image_info != nullptr) { + image_info->width = imgWidth; + image_info->height = imgHeight; + image_info->numComponents = numComponents; + image_info->imgData.resize(jpeg_image->data_sz, 0); + memcpy(static_cast(image_info->imgData.data()), jpeg_image->data, jpeg_image->data_sz); if (jpeg_dec_obj.getICCSize() != 0) { - jpeg_image_info_ptr->iccData.resize(jpeg_dec_obj.getICCSize(), 0); - memcpy(static_cast(jpeg_image_info_ptr->iccData.data()), jpeg_dec_obj.getICCPtr(), + image_info->iccData.resize(jpeg_dec_obj.getICCSize(), 0); + memcpy(static_cast(image_info->iccData.data()), jpeg_dec_obj.getICCPtr(), jpeg_dec_obj.getICCSize()); } if (jpeg_dec_obj.getEXIFSize() != 0) { - jpeg_image_info_ptr->exifData.resize(jpeg_dec_obj.getEXIFSize(), 0); - memcpy(static_cast(jpeg_image_info_ptr->exifData.data()), jpeg_dec_obj.getEXIFPtr(), + image_info->exifData.resize(jpeg_dec_obj.getEXIFSize(), 0); + memcpy(static_cast(image_info->exifData.data()), jpeg_dec_obj.getEXIFPtr(), jpeg_dec_obj.getEXIFSize()); } if (jpeg_dec_obj.getXMPSize() != 0) { - jpeg_image_info_ptr->xmpData.resize(jpeg_dec_obj.getXMPSize(), 0); - memcpy(static_cast(jpeg_image_info_ptr->xmpData.data()), jpeg_dec_obj.getXMPPtr(), + image_info->xmpData.resize(jpeg_dec_obj.getXMPSize(), 0); + memcpy(static_cast(image_info->xmpData.data()), jpeg_dec_obj.getXMPPtr(), jpeg_dec_obj.getXMPSize()); } } @@ -1384,529 +1272,841 @@ status_t JpegR::parseJpegInfo(jr_compressed_ptr jpeg_image_ptr, j_info_ptr jpeg_ *img_width = imgWidth; *img_height = imgHeight; } - return JPEGR_NO_ERROR; + return g_no_error; } -// JPEG/R structure: -// SOI (ff d8) -// -// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents -// in the JPEG input (Encode API-2, API-3, API-4)) -// APP1 (ff e1) -// 2 bytes of length (2 + length of exif package) -// EXIF package (this includes the first two bytes representing the package length) -// -// (Required, XMP package) APP1 (ff e1) -// 2 bytes of length (2 + 29 + length of xmp package) -// name space ("http://ns.adobe.com/xap/1.0/\0") -// XMP -// -// (Required, ISO 21496-1 metadata, version only) APP2 (ff e2) -// 2 bytes of length -// name space (""urn:iso:std:iso:ts:21496:-1\0") -// 2 bytes minimum_version: (00 00) -// 2 bytes writer_version: (00 00) -// -// (Required, MPF package) APP2 (ff e2) -// 2 bytes of length -// MPF -// -// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages) -// -// SOI (ff d8) -// -// (Required, XMP package) APP1 (ff e1) -// 2 bytes of length (2 + 29 + length of xmp package) -// name space ("http://ns.adobe.com/xap/1.0/\0") -// XMP -// -// (Required, ISO 21496-1 metadata) APP2 (ff e2) -// 2 bytes of length -// name space (""urn:iso:std:iso:ts:21496:-1\0") -// metadata -// -// (Required) secondary image (the gain map, without the first two bytes (SOI)) -// -// Metadata versions we are using: -// ECMA TR-98 for JFIF marker -// Exif 2.2 spec for EXIF marker -// Adobe XMP spec part 3 for XMP marker -// ICC v4.3 spec for ICC -status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr, - jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, - void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata, - jr_compressed_ptr dest) { - static_assert(kWriteXmpMetadata || kWriteIso21496_1Metadata, - "Must write gain map metadata in XMP format, or iso 21496-1 format, or both."); - if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr || - dest == nullptr) { - return ERROR_JPEGR_BAD_PTR; - } - if (metadata->version.compare("1.0")) { - ALOGE("received bad value for version: %s", metadata->version.c_str()); - return ERROR_JPEGR_BAD_METADATA; - } - if (metadata->maxContentBoost < metadata->minContentBoost) { - ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost, - metadata->maxContentBoost); - return ERROR_JPEGR_BAD_METADATA; - } - if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) { - ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin, - metadata->hdrCapacityMax); - return ERROR_JPEGR_BAD_METADATA; - } - if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) { - ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr); - return ERROR_JPEGR_BAD_METADATA; - } - if (metadata->gamma <= 0.0f) { - ALOGE("received bad value for gamma %f", metadata->gamma); - return ERROR_JPEGR_BAD_METADATA; - } +static float ReinhardMap(float y_hdr, float headroom) { + float out = 1.0 + y_hdr / (headroom * headroom); + out /= 1.0 + y_hdr; + return out * y_hdr; +} - const int xmpNameSpaceLength = kXmpNameSpace.size() + 1; // need to count the null terminator - const int isoNameSpaceLength = kIsoNameSpace.size() + 1; // need to count the null terminator +GlobalTonemapOutputs hlgGlobalTonemap(const std::array& rgb_in, float headroom) { + constexpr float kRgbToYBt2020[3] = {0.2627f, 0.6780f, 0.0593f}; + constexpr float kOotfGamma = 1.2f; - ///////////////////////////////////////////////////////////////////////////////////////////////// - // calculate secondary image length first, because the length will be written into the primary // - // image xmp // - ///////////////////////////////////////////////////////////////////////////////////////////////// - // XMP - const string xmp_secondary = generateXmpForSecondaryImage(*metadata); - // xmp_secondary_length = 2 bytes representing the length of the package + - // + xmpNameSpaceLength = 29 bytes length - // + length of xmp packet = xmp_secondary.size() - const int xmp_secondary_length = 2 + xmpNameSpaceLength + xmp_secondary.size(); - // ISO - gain_map_metadata iso_secondary_metadata; - std::vector iso_secondary_data; - gain_map_metadata::gainmapMetadataFloatToFraction(metadata, &iso_secondary_metadata); + // Apply OOTF and Scale to Headroom to get HDR values that are referenced to + // SDR white. The range [0.0, 1.0] is linearly stretched to [0.0, headroom] + // after the OOTF. + const float y_in = + rgb_in[0] * kRgbToYBt2020[0] + rgb_in[1] * kRgbToYBt2020[1] + rgb_in[2] * kRgbToYBt2020[2]; + const float y_ootf_div_y_in = std::pow(y_in, kOotfGamma - 1.0f); + std::array rgb_hdr; + std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(), + [&](float x) { return x * headroom * y_ootf_div_y_in; }); - gain_map_metadata::encodeGainmapMetadata(&iso_secondary_metadata, iso_secondary_data); + // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by + // keeping the shadows the same and crushing the highlights. + float max_hdr = *std::max_element(rgb_hdr.begin(), rgb_hdr.end()); + float max_sdr = ReinhardMap(max_hdr, headroom); + std::array rgb_sdr; + std::transform(rgb_hdr.begin(), rgb_hdr.end(), rgb_sdr.begin(), [&](float x) { + if (x > 0.0f) { + return x * max_sdr / max_hdr; + } + return 0.0f; + }); - // iso_secondary_length = 2 bytes representing the length of the package + - // + isoNameSpaceLength = 28 bytes length - // + length of iso metadata packet = iso_secondary_data.size() - const int iso_secondary_length = 2 + isoNameSpaceLength + iso_secondary_data.size(); + GlobalTonemapOutputs tonemap_outputs; + tonemap_outputs.rgb_out = rgb_sdr; + tonemap_outputs.y_hdr = max_hdr; + tonemap_outputs.y_sdr = max_sdr; - int secondary_image_size = 2 /* 2 bytes length of APP1 sign */ + gainmap_jpg_image_ptr->length; - if (kWriteXmpMetadata) { - secondary_image_size += xmp_secondary_length; - } - if (kWriteIso21496_1Metadata) { - secondary_image_size += iso_secondary_length; - } + return tonemap_outputs; +} - // Check if EXIF package presents in the JPEG input. - // If so, extract and remove the EXIF package. - JpegDecoderHelper decoder; - if (!decoder.parseImage(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) { - return ERROR_JPEGR_DECODE_ERROR; +uint8_t ScaleTo8Bit(float value) { + constexpr float kMaxValFloat = 255.0f; + constexpr int kMaxValInt = 255; + return std::clamp(static_cast(std::round(value * kMaxValFloat)), 0, kMaxValInt); +} + +uhdr_error_info_t JpegR::toneMap(uhdr_raw_image_t* hdr_intent, uhdr_raw_image_t* sdr_intent) { + if (sdr_intent->fmt != UHDR_IMG_FMT_12bppYCbCr420) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "tonemap method expects sdr intent color format to be one of " + "{UHDR_IMG_FMT_12bppYCbCr420}. Received %d", + sdr_intent->fmt); + return status; + } + if (hdr_intent->fmt != UHDR_IMG_FMT_24bppYCbCrP010) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "tonemap method expects hdr intent color format to be one of " + "{UHDR_IMG_FMT_24bppYCbCrP010}. Received %d", + hdr_intent->fmt); + return status; } - jpegr_exif_struct exif_from_jpg; - exif_from_jpg.data = nullptr; - exif_from_jpg.length = 0; - jpegr_compressed_struct new_jpg_image; - new_jpg_image.data = nullptr; - new_jpg_image.length = 0; - new_jpg_image.maxLength = 0; - new_jpg_image.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED; - std::unique_ptr dest_data; - if (decoder.getEXIFPos() >= 0) { - if (pExif != nullptr) { - ALOGE("received EXIF from outside while the primary image already contains EXIF"); - return ERROR_JPEGR_MULTIPLE_EXIFS_RECEIVED; - } - copyJpegWithoutExif(&new_jpg_image, primary_jpg_image_ptr, decoder.getEXIFPos(), - decoder.getEXIFSize()); - dest_data.reset(reinterpret_cast(new_jpg_image.data)); - exif_from_jpg.data = decoder.getEXIFPtr(); - exif_from_jpg.length = decoder.getEXIFSize(); - pExif = &exif_from_jpg; + + ColorTransformFn hdrYuvToRgbFn = nullptr; + switch (hdr_intent->cg) { + case UHDR_CG_BT_709: + hdrYuvToRgbFn = srgbYuvToRgb; + break; + case UHDR_CG_DISPLAY_P3: + hdrYuvToRgbFn = p3YuvToRgb; + break; + case UHDR_CG_BT_2100: + hdrYuvToRgbFn = bt2100YuvToRgb; + break; + case UHDR_CG_UNSPECIFIED: + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "Unrecognized hdr intent color gamut %d", + hdr_intent->cg); + return status; } - jr_compressed_ptr final_primary_jpg_image_ptr = - new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image; + ColorTransformFn hdrInvOetf = nullptr; + switch (hdr_intent->ct) { + case UHDR_CT_LINEAR: + hdrInvOetf = identityConversion; + break; + case UHDR_CT_HLG: +#if USE_HLG_INVOETF_LUT + hdrInvOetf = hlgInvOetfLUT; +#else + hdrInvOetf = hlgInvOetf; +#endif + break; + case UHDR_CT_PQ: +#if USE_PQ_INVOETF_LUT + hdrInvOetf = pqInvOetfLUT; +#else + hdrInvOetf = pqInvOetf; +#endif + break; + default: + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "Unrecognized hdr intent color transfer characteristics %d", hdr_intent->ct); + return status; + } + + sdr_intent->cg = UHDR_CG_DISPLAY_P3; + sdr_intent->ct = UHDR_CT_SRGB; + sdr_intent->range = UHDR_CR_FULL_RANGE; + + ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(sdr_intent->cg, hdr_intent->cg); + uint8_t* luma_data = reinterpret_cast(sdr_intent->planes[UHDR_PLANE_Y]); + uint8_t* cb_data = reinterpret_cast(sdr_intent->planes[UHDR_PLANE_U]); + uint8_t* cr_data = reinterpret_cast(sdr_intent->planes[UHDR_PLANE_V]); + size_t luma_stride = sdr_intent->stride[UHDR_PLANE_Y]; + size_t cb_stride = sdr_intent->stride[UHDR_PLANE_U]; + size_t cr_stride = sdr_intent->stride[UHDR_PLANE_V]; + size_t height = hdr_intent->h; + const int threads = (std::min)(GetCPUCoreCount(), 4); + const int jobSizeInRows = 2; + size_t rowStep = threads == 1 ? height : jobSizeInRows; + JobQueue jobQueue; + std::function toneMapInternal; - int pos = 0; - // Begin primary image - // Write SOI - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + toneMapInternal = [hdr_intent, luma_data, cb_data, cr_data, hdrInvOetf, hdrGamutConversionFn, + hdrYuvToRgbFn, luma_stride, cb_stride, cr_stride, &jobQueue]() -> void { + size_t rowStart, rowEnd; + while (jobQueue.dequeueJob(rowStart, rowEnd)) { + for (size_t y = rowStart; y < rowEnd; y += 2) { + for (size_t x = 0; x < hdr_intent->w; x += 2) { + // We assume the input is P010, and output is YUV420 + float sdr_u_gamma = 0.0f; + float sdr_v_gamma = 0.0f; - // Write EXIF - if (pExif != nullptr) { - const int length = 2 + pExif->length; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos)); - } + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + Color hdr_yuv_gamma = getP010Pixel(hdr_intent, x + j, y + i); + Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); + Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); - // Prepare and write XMP - if (kWriteXmpMetadata) { - const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata); - const int length = 2 + xmpNameSpaceLength + xmp_primary.size(); - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos)); - } + GlobalTonemapOutputs tonemap_outputs = + hlgGlobalTonemap({hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, kHlgHeadroom); + Color sdr_rgb_linear_bt2100 = { + {{tonemap_outputs.rgb_out[0], tonemap_outputs.rgb_out[1], + tonemap_outputs.rgb_out[2]}}}; + Color sdr_rgb = hdrGamutConversionFn(sdr_rgb_linear_bt2100); - // Write ICC - if (pIcc != nullptr && icc_size > 0) { - const int length = icc_size + 2; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, pIcc, icc_size, pos)); - } + // Hard clip out-of-gamut values; + sdr_rgb = clampPixelFloat(sdr_rgb); - // Prepare and write ISO 21496-1 metadata - if (kWriteIso21496_1Metadata) { - const int length = 2 + isoNameSpaceLength + 4; - uint8_t zero = 0; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, &zero, 1, pos)); - JPEGR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes minimum_version: (00 00) - JPEGR_CHECK(Write(dest, &zero, 1, pos)); - JPEGR_CHECK(Write(dest, &zero, 1, pos)); // 2 bytes writer_version: (00 00) - } + Color sdr_rgb_gamma = srgbOetf(sdr_rgb); + Color sdr_yuv_gamma = p3RgbToYuv(sdr_rgb_gamma); - // Prepare and write MPF - { - const int length = 2 + calculateMpfSize(); - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - int primary_image_size = pos + length + final_primary_jpg_image_ptr->length; - // between APP2 + package size + signature - // ff e2 00 58 4d 50 46 00 - // 2 + 2 + 4 = 8 (bytes) - // and ff d8 sign of the secondary image - int secondary_image_offset = primary_image_size - pos - 8; - std::shared_ptr mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */ - secondary_image_size, secondary_image_offset); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos)); - } + sdr_yuv_gamma += {{{0.0f, 0.5f, 0.5f}}}; - // Write primary image - JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2, - final_primary_jpg_image_ptr->length - 2, pos)); - // Finish primary image + size_t out_y_idx = (y + i) * luma_stride + x + j; + luma_data[out_y_idx] = ScaleTo8Bit(sdr_yuv_gamma.y); - // Begin secondary image (gain map) - // Write SOI - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos)); + sdr_u_gamma += sdr_yuv_gamma.u; + sdr_v_gamma += sdr_yuv_gamma.v; + } + } + sdr_u_gamma *= 0.25f; + sdr_v_gamma *= 0.25f; + cb_data[x / 2 + (y / 2) * cb_stride] = ScaleTo8Bit(sdr_u_gamma); + cr_data[x / 2 + (y / 2) * cr_stride] = ScaleTo8Bit(sdr_v_gamma); + } + } + } + }; - // Prepare and write XMP - if (kWriteXmpMetadata) { - const int length = xmp_secondary_length; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)kXmpNameSpace.c_str(), xmpNameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos)); + // tone map + std::vector workers; + for (int th = 0; th < threads - 1; th++) { + workers.push_back(std::thread(toneMapInternal)); } - // Prepare and write ISO 21496-1 metadata - if (kWriteIso21496_1Metadata) { - const int length = iso_secondary_length; - const uint8_t lengthH = ((length >> 8) & 0xff); - const uint8_t lengthL = (length & 0xff); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos)); - JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthH, 1, pos)); - JPEGR_CHECK(Write(dest, &lengthL, 1, pos)); - JPEGR_CHECK(Write(dest, (void*)kIsoNameSpace.c_str(), isoNameSpaceLength, pos)); - JPEGR_CHECK(Write(dest, (void*)iso_secondary_data.data(), iso_secondary_data.size(), pos)); + for (size_t rowStart = 0; rowStart < height;) { + size_t rowEnd = (std::min)(rowStart + rowStep, height); + jobQueue.enqueueJob(rowStart, rowEnd); + rowStart = rowEnd; } + jobQueue.markQueueForEnd(); + toneMapInternal(); + std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - // Write secondary image - JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2, - gainmap_jpg_image_ptr->length - 2, pos)); - - // Set back length - dest->length = pos; - - // Done! - return JPEGR_NO_ERROR; + return g_no_error; } -status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding, - ultrahdr_color_gamut dest_encoding) { - if (image == nullptr) { +status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, + jr_uncompressed_ptr yuv420_image_ptr, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest_ptr) { + if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) { + ALOGE("Received nullptr for input p010 image"); return ERROR_JPEGR_BAD_PTR; } - if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED || - dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) { + if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) { + ALOGE("Image dimensions cannot be odd, image dimensions %zux%zu", p010_image_ptr->width, + p010_image_ptr->height); + return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; + } + if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) { + ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %zux%zu", kMinWidth, + kMinHeight, p010_image_ptr->width, p010_image_ptr->height); + return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; + } + if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) { + ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %zux%zu", kMaxWidth, + kMaxHeight, p010_image_ptr->width, p010_image_ptr->height); + return ERROR_JPEGR_UNSUPPORTED_WIDTH_HEIGHT; + } + if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || + p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { + ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut); return ERROR_JPEGR_INVALID_COLORGAMUT; } - - const std::array* coeffs_ptr = nullptr; - switch (src_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - switch (dest_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - return JPEGR_NO_ERROR; - case ULTRAHDR_COLORGAMUT_P3: - coeffs_ptr = &kYuvBt709ToBt601; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - coeffs_ptr = &kYuvBt709ToBt2100; - break; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - break; - case ULTRAHDR_COLORGAMUT_P3: - switch (dest_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - coeffs_ptr = &kYuvBt601ToBt709; - break; - case ULTRAHDR_COLORGAMUT_P3: - return JPEGR_NO_ERROR; - case ULTRAHDR_COLORGAMUT_BT2100: - coeffs_ptr = &kYuvBt601ToBt2100; - break; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - break; - case ULTRAHDR_COLORGAMUT_BT2100: - switch (dest_encoding) { - case ULTRAHDR_COLORGAMUT_BT709: - coeffs_ptr = &kYuvBt2100ToBt709; - break; - case ULTRAHDR_COLORGAMUT_P3: - coeffs_ptr = &kYuvBt2100ToBt601; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - return JPEGR_NO_ERROR; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; - } - break; - default: - // Should be impossible to hit after input validation - return ERROR_JPEGR_INVALID_COLORGAMUT; + if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) { + ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu", + p010_image_ptr->luma_stride, p010_image_ptr->width); + return ERROR_JPEGR_INVALID_STRIDE; } - - if (coeffs_ptr == nullptr) { - // Should be impossible to hit after input validation + if (p010_image_ptr->chroma_data != nullptr && + p010_image_ptr->chroma_stride < p010_image_ptr->width) { + ALOGE("Chroma stride must not be smaller than width, stride=%zu, width=%zu", + p010_image_ptr->chroma_stride, p010_image_ptr->width); + return ERROR_JPEGR_INVALID_STRIDE; + } + if (dest_ptr == nullptr || dest_ptr->data == nullptr) { + ALOGE("Received nullptr for destination"); + return ERROR_JPEGR_BAD_PTR; + } + if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) { + ALOGE("Invalid hdr transfer function %d", hdr_tf); + return ERROR_JPEGR_INVALID_TRANS_FUNC; + } + if (yuv420_image_ptr == nullptr) { + return JPEGR_NO_ERROR; + } + if (yuv420_image_ptr->data == nullptr) { + ALOGE("Received nullptr for uncompressed 420 image"); + return ERROR_JPEGR_BAD_PTR; + } + if (yuv420_image_ptr->luma_stride != 0 && + yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) { + ALOGE("Luma stride must not be smaller than width, stride=%zu, width=%zu", + yuv420_image_ptr->luma_stride, yuv420_image_ptr->width); + return ERROR_JPEGR_INVALID_STRIDE; + } + if (yuv420_image_ptr->chroma_data != nullptr && + yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) { + ALOGE("Chroma stride must not be smaller than (width / 2), stride=%zu, width=%zu", + yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width); + return ERROR_JPEGR_INVALID_STRIDE; + } + if (p010_image_ptr->width != yuv420_image_ptr->width || + p010_image_ptr->height != yuv420_image_ptr->height) { + ALOGE("Image resolutions mismatch: P010: %zux%zu, YUV420: %zux%zu", p010_image_ptr->width, + p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height); + return ERROR_JPEGR_RESOLUTION_MISMATCH; + } + if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED || + yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) { + ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut); return ERROR_JPEGR_INVALID_COLORGAMUT; } - - transformYuv420(image, *coeffs_ptr); return JPEGR_NO_ERROR; } -namespace { -float ReinhardMap(float y_hdr, float headroom) { - float out = 1.0 + y_hdr / (headroom * headroom); - out /= 1.0 + y_hdr; - return out * y_hdr; -} -} // namespace - -GlobalTonemapOutputs hlgGlobalTonemap(const std::array& rgb_in, float headroom) { - constexpr float kRgbToYBt2020[3] = {0.2627f, 0.6780f, 0.0593f}; - constexpr float kOotfGamma = 1.2f; - - // Apply OOTF and Scale to Headroom to get HDR values that are referenced to - // SDR white. The range [0.0, 1.0] is linearly stretched to [0.0, headroom] - // after the OOTF. - const float y_in = - rgb_in[0] * kRgbToYBt2020[0] + rgb_in[1] * kRgbToYBt2020[1] + rgb_in[2] * kRgbToYBt2020[2]; - const float y_ootf_div_y_in = std::pow(y_in, kOotfGamma - 1.0f); - std::array rgb_hdr; - std::transform(rgb_in.begin(), rgb_in.end(), rgb_hdr.begin(), - [&](float x) { return x * headroom * y_ootf_div_y_in; }); +status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr, + jr_uncompressed_ptr yuv420_image_ptr, + ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest_ptr, int quality) { + if (quality < 0 || quality > 100) { + ALOGE("quality factor is out side range [0-100], quality factor : %d", quality); + return ERROR_JPEGR_INVALID_QUALITY_FACTOR; + } + return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr); +} - // Apply a tone mapping to compress the range [0, headroom] to [0, 1] by - // keeping the shadows the same and crushing the highlights. - float max_hdr = *std::max_element(rgb_hdr.begin(), rgb_hdr.end()); - float max_sdr = ReinhardMap(max_hdr, headroom); - std::array rgb_sdr; - std::transform(rgb_hdr.begin(), rgb_hdr.end(), rgb_sdr.begin(), [&](float x) { - if (x > 0.0f) { - return x * max_sdr / max_hdr; - } - return 0.0f; - }); +uhdr_color_transfer_t map_legacy_ct_to_ct(ultrahdr::ultrahdr_transfer_function ct) { + switch (ct) { + case ultrahdr::ULTRAHDR_TF_HLG: + return UHDR_CT_HLG; + case ultrahdr::ULTRAHDR_TF_PQ: + return UHDR_CT_PQ; + case ultrahdr::ULTRAHDR_TF_LINEAR: + return UHDR_CT_LINEAR; + case ultrahdr::ULTRAHDR_TF_SRGB: + return UHDR_CT_SRGB; + default: + return UHDR_CT_UNSPECIFIED; + } +} - GlobalTonemapOutputs tonemap_outputs; - tonemap_outputs.rgb_out = rgb_sdr; - tonemap_outputs.y_hdr = max_hdr; - tonemap_outputs.y_sdr = max_sdr; - return tonemap_outputs; +uhdr_color_gamut_t map_legacy_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) { + switch (cg) { + case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100: + return UHDR_CG_BT_2100; + case ultrahdr::ULTRAHDR_COLORGAMUT_BT709: + return UHDR_CG_BT_709; + case ultrahdr::ULTRAHDR_COLORGAMUT_P3: + return UHDR_CG_DISPLAY_P3; + default: + return UHDR_CG_UNSPECIFIED; + } } -uint8_t ScaleTo8Bit(float value) { - constexpr float kMaxValFloat = 255.0f; - constexpr int kMaxValInt = 255; - return std::clamp(static_cast(std::round(value * kMaxValFloat)), 0, kMaxValInt); +ultrahdr::ultrahdr_color_gamut map_cg_to_legacy_cg(uhdr_color_gamut_t cg) { + switch (cg) { + case UHDR_CG_BT_2100: + return ultrahdr::ULTRAHDR_COLORGAMUT_BT2100; + case UHDR_CG_BT_709: + return ultrahdr::ULTRAHDR_COLORGAMUT_BT709; + case UHDR_CG_DISPLAY_P3: + return ultrahdr::ULTRAHDR_COLORGAMUT_P3; + default: + return ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + } } -status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest, - ultrahdr_transfer_function hdr_tf) { - if (src == nullptr || dest == nullptr) { +/* Encode API-0 */ +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { + // validate input arguments + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality)); + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr for exif metadata"); return ERROR_JPEGR_BAD_PTR; } - if (src->width != dest->width || src->height != dest->height) { - return ERROR_JPEGR_RESOLUTION_MISMATCH; + + // clean up input structure for later usage + jpegr_uncompressed_struct p010_image = *p010_image_ptr; + if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; + if (!p010_image.chroma_data) { + uint16_t* data = reinterpret_cast(p010_image.data); + p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; + p010_image.chroma_stride = p010_image.luma_stride; } - dest->colorGamut = ULTRAHDR_COLORGAMUT_P3; + uhdr_raw_image_t hdr_intent; + hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); + hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); + hdr_intent.range = p010_image.colorRange; + hdr_intent.w = p010_image.width; + hdr_intent.h = p010_image.height; + hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; + hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; + hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; + hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; + hdr_intent.planes[UHDR_PLANE_V] = nullptr; + hdr_intent.stride[UHDR_PLANE_V] = 0; + + uhdr_compressed_image_t output; + output.data = dest->data; + output.data_sz = 0; + output.capacity = dest->maxLength; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + + uhdr_mem_block_t exifBlock; + if (exif) { + exifBlock.data = exif->data; + exifBlock.data_sz = exifBlock.capacity = exif->length; + } + + auto result = encodeJPEGR(&hdr_intent, &output, quality, exif ? &exifBlock : nullptr); + if (result.error_code == UHDR_CODEC_OK) { + dest->colorGamut = map_cg_to_legacy_cg(output.cg); + dest->length = output.data_sz; + } + + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; +} - size_t height = src->height; +/* Encode API-1 */ +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, + jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf, + jr_compressed_ptr dest, int quality, jr_exif_ptr exif) { + // validate input arguments + if (yuv420_image_ptr == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); + return ERROR_JPEGR_BAD_PTR; + } + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr for exif metadata"); + return ERROR_JPEGR_BAD_PTR; + } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality)) - ColorTransformFn hdrYuvToRgbFn = nullptr; - switch (src->colorGamut) { - case ULTRAHDR_COLORGAMUT_BT709: - hdrYuvToRgbFn = srgbYuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_P3: - hdrYuvToRgbFn = p3YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_BT2100: - hdrYuvToRgbFn = bt2100YuvToRgb; - break; - case ULTRAHDR_COLORGAMUT_UNSPECIFIED: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_COLORGAMUT; + // clean up input structure for later usage + jpegr_uncompressed_struct p010_image = *p010_image_ptr; + if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; + if (!p010_image.chroma_data) { + uint16_t* data = reinterpret_cast(p010_image.data); + p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; + p010_image.chroma_stride = p010_image.luma_stride; } + uhdr_raw_image_t hdr_intent; + hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); + hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); + hdr_intent.range = p010_image.colorRange; + hdr_intent.w = p010_image.width; + hdr_intent.h = p010_image.height; + hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; + hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; + hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; + hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; + hdr_intent.planes[UHDR_PLANE_V] = nullptr; + hdr_intent.stride[UHDR_PLANE_V] = 0; - ColorTransformFn hdrInvOetf = nullptr; - switch (hdr_tf) { - case ULTRAHDR_TF_HLG: -#if USE_HLG_INVOETF_LUT - hdrInvOetf = hlgInvOetfLUT; -#else - hdrInvOetf = hlgInvOetf; -#endif - break; - case ULTRAHDR_TF_PQ: -#if USE_PQ_INVOETF_LUT - hdrInvOetf = pqInvOetfLUT; -#else - hdrInvOetf = pqInvOetf; -#endif - break; - default: - // Should be impossible to hit after input validation. - return ERROR_JPEGR_INVALID_TRANS_FUNC; + jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; + if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; + if (!yuv420_image.chroma_data) { + uint8_t* data = reinterpret_cast(yuv420_image.data); + yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height; + yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; } + uhdr_raw_image_t sdrRawImg; + sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420; + sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut); + sdrRawImg.ct = UHDR_CT_SRGB; + sdrRawImg.range = yuv420_image.colorRange; + sdrRawImg.w = yuv420_image.width; + sdrRawImg.h = yuv420_image.height; + sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data; + sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride; + sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data; + sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride; + uint8_t* data = reinterpret_cast(yuv420_image.chroma_data); + data += (yuv420_image.height * yuv420_image.chroma_stride) / 2; + sdrRawImg.planes[UHDR_PLANE_V] = data; + sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride; + auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg); + + uhdr_compressed_image_t output; + output.data = dest->data; + output.data_sz = 0; + output.capacity = dest->maxLength; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + + uhdr_mem_block_t exifBlock; + if (exif) { + exifBlock.data = exif->data; + exifBlock.data_sz = exifBlock.capacity = exif->length; + } + + auto result = + encodeJPEGR(&hdr_intent, sdr_intent.get(), &output, quality, exif ? &exifBlock : nullptr); + if (result.error_code == UHDR_CODEC_OK) { + dest->colorGamut = map_cg_to_legacy_cg(output.cg); + dest->length = output.data_sz; + } + + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; +} - ColorTransformFn hdrGamutConversionFn = getHdrConversionFn(dest->colorGamut, src->colorGamut); +/* Encode API-2 */ +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, + jr_uncompressed_ptr yuv420_image_ptr, + jr_compressed_ptr yuv420jpg_image_ptr, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { + // validate input arguments + if (yuv420_image_ptr == nullptr) { + ALOGE("received nullptr for uncompressed 420 image"); + return ERROR_JPEGR_BAD_PTR; + } + if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_BAD_PTR; + } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest)) - size_t luma_stride = dest->luma_stride == 0 ? dest->width : dest->luma_stride; - size_t chroma_stride = dest->chroma_stride == 0 ? luma_stride / 2 : dest->chroma_stride; - if (dest->chroma_data == nullptr) { - uint8_t* data = reinterpret_cast(dest->data); - dest->chroma_data = data + luma_stride * dest->height; + // clean up input structure for later usage + jpegr_uncompressed_struct p010_image = *p010_image_ptr; + if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; + if (!p010_image.chroma_data) { + uint16_t* data = reinterpret_cast(p010_image.data); + p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; + p010_image.chroma_stride = p010_image.luma_stride; } - uint8_t* luma_data = reinterpret_cast(dest->data); - uint8_t* chroma_data = reinterpret_cast(dest->chroma_data); + uhdr_raw_image_t hdr_intent; + hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); + hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); + hdr_intent.range = p010_image.colorRange; + hdr_intent.w = p010_image.width; + hdr_intent.h = p010_image.height; + hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; + hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; + hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; + hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; + hdr_intent.planes[UHDR_PLANE_V] = nullptr; + hdr_intent.stride[UHDR_PLANE_V] = 0; - const int threads = (std::min)(GetCPUCoreCount(), 4); - const int jobSizeInRows = mMapDimensionScaleFactor * 4; - size_t rowStep = threads == 1 ? src->height : jobSizeInRows; - JobQueue jobQueue; - std::function toneMapInternal; + jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr; + if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width; + if (!yuv420_image.chroma_data) { + uint8_t* data = reinterpret_cast(yuv420_image.data); + yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height; + yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1; + } + uhdr_raw_image_t sdrRawImg; + sdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420; + sdrRawImg.cg = map_legacy_cg_to_cg(yuv420_image.colorGamut); + sdrRawImg.ct = UHDR_CT_SRGB; + sdrRawImg.range = yuv420_image.colorRange; + sdrRawImg.w = yuv420_image.width; + sdrRawImg.h = yuv420_image.height; + sdrRawImg.planes[UHDR_PLANE_Y] = yuv420_image.data; + sdrRawImg.stride[UHDR_PLANE_Y] = yuv420_image.luma_stride; + sdrRawImg.planes[UHDR_PLANE_U] = yuv420_image.chroma_data; + sdrRawImg.stride[UHDR_PLANE_U] = yuv420_image.chroma_stride; + uint8_t* data = reinterpret_cast(yuv420_image.chroma_data); + data += (yuv420_image.height * yuv420_image.chroma_stride) / 2; + sdrRawImg.planes[UHDR_PLANE_V] = data; + sdrRawImg.stride[UHDR_PLANE_V] = yuv420_image.chroma_stride; + auto sdr_intent = convert_raw_input_to_ycbcr(&sdrRawImg); + + uhdr_compressed_image_t input; + input.data = yuv420jpg_image_ptr->data; + input.data_sz = yuv420jpg_image_ptr->length; + input.capacity = yuv420jpg_image_ptr->maxLength; + input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut); + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_UNSPECIFIED; + + uhdr_compressed_image_t output; + output.data = dest->data; + output.data_sz = 0; + output.capacity = dest->maxLength; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + + auto result = encodeJPEGR(&hdr_intent, sdr_intent.get(), &input, &output); + if (result.error_code == UHDR_CODEC_OK) { + dest->colorGamut = map_cg_to_legacy_cg(output.cg); + dest->length = output.data_sz; + } + + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; +} - toneMapInternal = [src, dest, luma_data, chroma_data, hdrInvOetf, hdrGamutConversionFn, - hdrYuvToRgbFn, luma_stride, chroma_stride, &jobQueue]() -> void { - size_t rowStart, rowEnd; - while (jobQueue.dequeueJob(rowStart, rowEnd)) { - for (size_t y = rowStart; y < rowEnd; y += 2) { - for (size_t x = 0; x < dest->width; x += 2) { - // We assume the input is P010, and output is YUV420 - float sdr_u_gamma = 0.0f; - float sdr_v_gamma = 0.0f; - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - Color hdr_yuv_gamma = getP010Pixel(src, x + j, y + i); - Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma); +/* Encode API-3 */ +status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, + jr_compressed_ptr yuv420jpg_image_ptr, + ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) { + // validate input arguments + if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_BAD_PTR; + } + JPEGR_CHECK(areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest)) - Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma); + // clean up input structure for later usage + jpegr_uncompressed_struct p010_image = *p010_image_ptr; + if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width; + if (!p010_image.chroma_data) { + uint16_t* data = reinterpret_cast(p010_image.data); + p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height; + p010_image.chroma_stride = p010_image.luma_stride; + } + uhdr_raw_image_t hdr_intent; + hdr_intent.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + hdr_intent.cg = map_legacy_cg_to_cg(p010_image.colorGamut); + hdr_intent.ct = map_legacy_ct_to_ct(hdr_tf); + hdr_intent.range = p010_image.colorRange; + hdr_intent.w = p010_image.width; + hdr_intent.h = p010_image.height; + hdr_intent.planes[UHDR_PLANE_Y] = p010_image.data; + hdr_intent.stride[UHDR_PLANE_Y] = p010_image.luma_stride; + hdr_intent.planes[UHDR_PLANE_UV] = p010_image.chroma_data; + hdr_intent.stride[UHDR_PLANE_UV] = p010_image.chroma_stride; + hdr_intent.planes[UHDR_PLANE_V] = nullptr; + hdr_intent.stride[UHDR_PLANE_V] = 0; + + uhdr_compressed_image_t input; + input.data = yuv420jpg_image_ptr->data; + input.data_sz = yuv420jpg_image_ptr->length; + input.capacity = yuv420jpg_image_ptr->maxLength; + input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut); + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_UNSPECIFIED; + + uhdr_compressed_image_t output; + output.data = dest->data; + output.data_sz = 0; + output.capacity = dest->maxLength; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + + auto result = encodeJPEGR(&hdr_intent, &input, &output); + if (result.error_code == UHDR_CODEC_OK) { + dest->colorGamut = map_cg_to_legacy_cg(output.cg); + dest->length = output.data_sz; + } + + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; +} - GlobalTonemapOutputs tonemap_outputs = - hlgGlobalTonemap({hdr_rgb.r, hdr_rgb.g, hdr_rgb.b}, kHlgHeadroom); - Color sdr_rgb_linear_bt2100 = {{{tonemap_outputs.rgb_out[0], - tonemap_outputs.rgb_out[1], - tonemap_outputs.rgb_out[2]}}}; - Color sdr_rgb = hdrGamutConversionFn(sdr_rgb_linear_bt2100); +/* Encode API-4 */ +status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr, + jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata, + jr_compressed_ptr dest) { + if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) { + ALOGE("received nullptr for compressed jpeg image"); + return ERROR_JPEGR_BAD_PTR; + } + if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) { + ALOGE("received nullptr for compressed gain map"); + return ERROR_JPEGR_BAD_PTR; + } + if (dest == nullptr || dest->data == nullptr) { + ALOGE("received nullptr for destination"); + return ERROR_JPEGR_BAD_PTR; + } - // Hard clip out-of-gamut values; - sdr_rgb = clampPixelFloat(sdr_rgb); + uhdr_compressed_image_t input; + input.data = yuv420jpg_image_ptr->data; + input.data_sz = yuv420jpg_image_ptr->length; + input.capacity = yuv420jpg_image_ptr->maxLength; + input.cg = map_legacy_cg_to_cg(yuv420jpg_image_ptr->colorGamut); + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_UNSPECIFIED; + + uhdr_compressed_image_t gainmap; + gainmap.data = yuv420jpg_image_ptr->data; + gainmap.data_sz = yuv420jpg_image_ptr->length; + gainmap.capacity = yuv420jpg_image_ptr->maxLength; + gainmap.cg = UHDR_CG_UNSPECIFIED; + gainmap.ct = UHDR_CT_UNSPECIFIED; + gainmap.range = UHDR_CR_UNSPECIFIED; + + uhdr_compressed_image_t output; + output.data = dest->data; + output.data_sz = 0; + output.capacity = dest->maxLength; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + + uhdr_gainmap_metadata_ext_t meta; + meta.version = metadata->version; + meta.hdr_capacity_max = metadata->hdrCapacityMax; + meta.hdr_capacity_min = metadata->hdrCapacityMin; + meta.gamma = metadata->gamma; + meta.offset_sdr = metadata->offsetSdr; + meta.offset_hdr = metadata->offsetHdr; + meta.max_content_boost = metadata->maxContentBoost; + meta.min_content_boost = metadata->minContentBoost; + + auto result = encodeJPEGR(&input, &gainmap, &meta, &output); + if (result.error_code == UHDR_CODEC_OK) { + dest->colorGamut = map_cg_to_legacy_cg(output.cg); + dest->length = output.data_sz; + } + + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; +} - Color sdr_rgb_gamma = srgbOetf(sdr_rgb); - Color sdr_yuv_gamma = p3RgbToYuv(sdr_rgb_gamma); +/* Decode API */ +status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpegr_image_info_ptr) { + if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { + ALOGE("received nullptr for compressed jpegr image"); + return ERROR_JPEGR_BAD_PTR; + } + if (jpegr_image_info_ptr == nullptr) { + ALOGE("received nullptr for compressed jpegr info struct"); + return ERROR_JPEGR_BAD_PTR; + } - sdr_yuv_gamma += {{{0.0f, 0.5f, 0.5f}}}; + uhdr_compressed_image_t input; + input.data = jpegr_image_ptr->data; + input.data_sz = jpegr_image_ptr->length; + input.capacity = jpegr_image_ptr->maxLength; + input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut); + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_UNSPECIFIED; - size_t out_y_idx = (y + i) * luma_stride + x + j; - luma_data[out_y_idx] = ScaleTo8Bit(sdr_yuv_gamma.y); + auto result = getJPEGRInfo(&input, jpegr_image_info_ptr); - sdr_u_gamma += sdr_yuv_gamma.u; - sdr_v_gamma += sdr_yuv_gamma.v; - } - } - sdr_u_gamma *= 0.25f; - sdr_v_gamma *= 0.25f; - size_t out_chroma_idx = x / 2 + (y / 2) * chroma_stride; - size_t offset_cr = chroma_stride * (dest->height / 2); - chroma_data[out_chroma_idx] = ScaleTo8Bit(sdr_u_gamma); - chroma_data[out_chroma_idx + offset_cr] = ScaleTo8Bit(sdr_v_gamma); - } - } - } - }; + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; +} - // tone map - std::vector workers; - for (int th = 0; th < threads - 1; th++) { - workers.push_back(std::thread(toneMapInternal)); +status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest, + float max_display_boost, jr_exif_ptr exif, + ultrahdr_output_format output_format, + jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) { + if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) { + ALOGE("received nullptr for compressed jpegr image"); + return ERROR_JPEGR_BAD_PTR; + } + if (dest == nullptr || dest->data == nullptr) { + ALOGE("received nullptr for dest image"); + return ERROR_JPEGR_BAD_PTR; + } + if (max_display_boost < 1.0f) { + ALOGE("received bad value for max_display_boost %f", max_display_boost); + return ERROR_JPEGR_INVALID_DISPLAY_BOOST; + } + if (exif != nullptr && exif->data == nullptr) { + ALOGE("received nullptr address for exif data"); + return ERROR_JPEGR_BAD_PTR; + } + if (gainmap_image_ptr != nullptr && gainmap_image_ptr->data == nullptr) { + ALOGE("received nullptr address for gainmap data"); + return ERROR_JPEGR_BAD_PTR; + } + if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) { + ALOGE("received bad value for output format %d", output_format); + return ERROR_JPEGR_INVALID_OUTPUT_FORMAT; } - for (size_t rowStart = 0; rowStart < height;) { - size_t rowEnd = (std::min)(rowStart + rowStep, height); - jobQueue.enqueueJob(rowStart, rowEnd); - rowStart = rowEnd; + uhdr_color_transfer_t ct; + uhdr_img_fmt fmt; + if (output_format == ULTRAHDR_OUTPUT_HDR_HLG) { + fmt = UHDR_IMG_FMT_32bppRGBA1010102; + ct = UHDR_CT_HLG; + } else if (output_format == ULTRAHDR_OUTPUT_HDR_PQ) { + fmt = UHDR_IMG_FMT_32bppRGBA1010102; + ct = UHDR_CT_PQ; + } else if (output_format == ULTRAHDR_OUTPUT_HDR_LINEAR) { + fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat; + ct = UHDR_CT_LINEAR; + } else if (output_format == ULTRAHDR_OUTPUT_SDR) { + fmt = UHDR_IMG_FMT_32bppRGBA8888; + ct = UHDR_CT_SRGB; + } + + uhdr_compressed_image_t input; + input.data = jpegr_image_ptr->data; + input.data_sz = jpegr_image_ptr->length; + input.capacity = jpegr_image_ptr->maxLength; + input.cg = map_legacy_cg_to_cg(jpegr_image_ptr->colorGamut); + input.ct = UHDR_CT_UNSPECIFIED; + input.range = UHDR_CR_UNSPECIFIED; + + jpeg_info_struct primary_image; + jpeg_info_struct gainmap_image; + jpegr_info_struct jpegr_info; + jpegr_info.primaryImgInfo = &primary_image; + jpegr_info.gainmapImgInfo = &gainmap_image; + if (getJPEGRInfo(&input, &jpegr_info).error_code != UHDR_CODEC_OK) return JPEGR_UNKNOWN_ERROR; + + if (exif != nullptr) { + if (exif->length < primary_image.exifData.size()) { + return ERROR_JPEGR_BUFFER_TOO_SMALL; + } + memcpy(exif->data, primary_image.exifData.data(), primary_image.exifData.size()); + exif->length = primary_image.exifData.size(); + } + + uhdr_raw_image_t output; + output.fmt = fmt; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + output.w = jpegr_info.width; + output.h = jpegr_info.height; + output.planes[UHDR_PLANE_PACKED] = dest->data; + output.stride[UHDR_PLANE_PACKED] = jpegr_info.width; + output.planes[UHDR_PLANE_U] = nullptr; + output.stride[UHDR_PLANE_U] = 0; + output.planes[UHDR_PLANE_V] = nullptr; + output.stride[UHDR_PLANE_V] = 0; + + uhdr_raw_image_t output_gm; + if (gainmap_image_ptr) { + output.fmt = + gainmap_image.numComponents == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_24bppRGB888; + output.cg = UHDR_CG_UNSPECIFIED; + output.ct = UHDR_CT_UNSPECIFIED; + output.range = UHDR_CR_UNSPECIFIED; + output.w = gainmap_image.width; + output.h = gainmap_image.height; + output.planes[UHDR_PLANE_PACKED] = gainmap_image_ptr->data; + output.stride[UHDR_PLANE_PACKED] = gainmap_image.width; + output.planes[UHDR_PLANE_U] = nullptr; + output.stride[UHDR_PLANE_U] = 0; + output.planes[UHDR_PLANE_V] = nullptr; + output.stride[UHDR_PLANE_V] = 0; + } + + uhdr_gainmap_metadata_ext_t meta; + auto result = decodeJPEGR(&input, &output, max_display_boost, ct, fmt, + gainmap_image_ptr ? &output_gm : nullptr, metadata ? &meta : nullptr); + + if (result.error_code == UHDR_CODEC_OK) { + dest->width = output.w; + dest->height = output.h; + dest->colorGamut = map_cg_to_legacy_cg(output.cg); + dest->colorRange = output.range; + dest->pixelFormat = output.fmt; + dest->chroma_data = nullptr; + if (gainmap_image_ptr) { + gainmap_image_ptr->width = output_gm.w; + gainmap_image_ptr->height = output_gm.h; + gainmap_image_ptr->colorGamut = map_cg_to_legacy_cg(output_gm.cg); + gainmap_image_ptr->colorRange = output_gm.range; + gainmap_image_ptr->pixelFormat = output_gm.fmt; + gainmap_image_ptr->chroma_data = nullptr; + } + if (metadata) { + metadata->version = meta.version; + metadata->hdrCapacityMax = meta.hdr_capacity_max; + metadata->hdrCapacityMin = meta.hdr_capacity_min; + metadata->gamma = meta.gamma; + metadata->offsetSdr = meta.offset_sdr; + metadata->offsetHdr = meta.offset_hdr; + metadata->maxContentBoost = meta.max_content_boost; + metadata->minContentBoost = meta.min_content_boost; + } } - jobQueue.markQueueForEnd(); - toneMapInternal(); - std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); }); - return JPEGR_NO_ERROR; + return result.error_code == UHDR_CODEC_OK ? JPEGR_NO_ERROR : JPEGR_UNKNOWN_ERROR; } } // namespace ultrahdr diff --git a/lib/src/jpegrutils.cpp b/lib/src/jpegrutils.cpp index d9647c5..4233847 100644 --- a/lib/src/jpegrutils.cpp +++ b/lib/src/jpegrutils.cpp @@ -73,6 +73,7 @@ bool DataStruct::write16(uint16_t value) { uint16_t v = value; return write(&v, 2); } + bool DataStruct::write32(uint32_t value) { uint32_t v = value; return write(&v, 4); @@ -92,14 +93,20 @@ bool DataStruct::write(const void* src, int size) { /* * Helper function used for writing data to destination. */ -status_t Write(jr_compressed_ptr destination, const void* source, int length, int& position) { - if (position + length > destination->maxLength) { - return ERROR_JPEGR_BUFFER_TOO_SMALL; +uhdr_error_info_t Write(uhdr_compressed_image_t* destination, const void* source, int length, + int& position) { + if (position + length > (int)destination->capacity) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "output buffer to store compressed data is too small"); + return status; } memcpy((uint8_t*)destination->data + sizeof(uint8_t) * position, source, length); position += length; - return JPEGR_NO_ERROR; + return g_no_error; } // Extremely simple XML Handler - just searches for interesting elements @@ -433,17 +440,28 @@ const string XMPXmlHandler::hdrCapacityMinAttrName = kMapHDRCapacityMin; const string XMPXmlHandler::hdrCapacityMaxAttrName = kMapHDRCapacityMax; const string XMPXmlHandler::baseRenditionIsHdrAttrName = kMapBaseRenditionIsHDR; -bool getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, ultrahdr_metadata_struct* metadata) { +uhdr_error_info_t getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, + uhdr_gainmap_metadata_ext_t* metadata) { string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; if (xmp_size < (int)nameSpace.size() + 2) { - // Data too short - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "size of xmp block is expected to be atleast %d bytes, received only %d bytes", + (int)nameSpace.size() + 2, xmp_size); + return status; } if (strncmp(reinterpret_cast(xmp_data), nameSpace.c_str(), nameSpace.size())) { - // Not correct namespace - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "mismatch in namespace of xmp block. Expected %s, Got %.*s", nameSpace.c_str(), + (int)nameSpace.size(), reinterpret_cast(xmp_data)); + return status; } // Position the pointers to the start of XMP XML portion @@ -492,8 +510,11 @@ bool getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, ultrahdr_metadata_struc reader.Parse(str); reader.FinishParse(); if (reader.HasErrors()) { - // Parse error - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNKNOWN_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parser returned with error"); + return status; } // Apply default values to any not-present fields, except for Version, @@ -502,49 +523,110 @@ bool getMetadataFromXMP(uint8_t* xmp_data, int xmp_size, ultrahdr_metadata_struc // indicates it is invalid (eg. string where there should be a float). bool present = false; if (!handler.getVersion(&metadata->version, &present) || !present) { - return false; - } - if (!handler.getMaxContentBoost(&metadata->maxContentBoost, &present) || !present) { - return false; - } - if (!handler.getHdrCapacityMax(&metadata->hdrCapacityMax, &present) || !present) { - return false; - } - if (!handler.getMinContentBoost(&metadata->minContentBoost, &present)) { - if (present) return false; - metadata->minContentBoost = 1.0f; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, could not find attribute %s", + kMapVersion.c_str()); + return status; + } + if (!handler.getMaxContentBoost(&metadata->max_content_boost, &present) || !present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, could not find attribute %s", + kMapGainMapMax.c_str()); + return status; + } + if (!handler.getHdrCapacityMax(&metadata->hdr_capacity_max, &present) || !present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, could not find attribute %s", + kMapHDRCapacityMax.c_str()); + return status; + } + if (!handler.getMinContentBoost(&metadata->min_content_boost, &present)) { + if (present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s", + kMapGainMapMin.c_str()); + return status; + } + metadata->min_content_boost = 1.0f; } if (!handler.getGamma(&metadata->gamma, &present)) { - if (present) return false; + if (present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s", + kMapGamma.c_str()); + return status; + } metadata->gamma = 1.0f; } - if (!handler.getOffsetSdr(&metadata->offsetSdr, &present)) { - if (present) return false; - metadata->offsetSdr = 1.0f / 64.0f; - } - if (!handler.getOffsetHdr(&metadata->offsetHdr, &present)) { - if (present) return false; - metadata->offsetHdr = 1.0f / 64.0f; - } - if (!handler.getHdrCapacityMin(&metadata->hdrCapacityMin, &present)) { - if (present) return false; - metadata->hdrCapacityMin = 1.0f; + if (!handler.getOffsetSdr(&metadata->offset_sdr, &present)) { + if (present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s", + kMapOffsetSdr.c_str()); + return status; + } + metadata->offset_sdr = 1.0f / 64.0f; + } + if (!handler.getOffsetHdr(&metadata->offset_hdr, &present)) { + if (present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s", + kMapOffsetHdr.c_str()); + return status; + } + metadata->offset_hdr = 1.0f / 64.0f; + } + if (!handler.getHdrCapacityMin(&metadata->hdr_capacity_min, &present)) { + if (present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s", + kMapHDRCapacityMin.c_str()); + return status; + } + metadata->hdr_capacity_min = 1.0f; } bool base_rendition_is_hdr; if (!handler.getBaseRenditionIsHdr(&base_rendition_is_hdr, &present)) { - if (present) return false; + if (present) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "xml parse error, unable to parse attribute %s", + kMapBaseRenditionIsHDR.c_str()); + return status; + } base_rendition_is_hdr = false; } if (base_rendition_is_hdr) { - ALOGE("Base rendition of HDR is not supported!"); - return false; + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "hdr intent as base rendition is not supported"); + return status; } - return true; + return g_no_error; } -string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_struct& metadata) { +string generateXmpForPrimaryImage(int secondary_image_length, + uhdr_gainmap_metadata_ext_t& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); const vector kLiItem({string("rdf:li"), kConItem}); @@ -582,7 +664,7 @@ string generateXmpForPrimaryImage(int secondary_image_length, ultrahdr_metadata_ return ss.str(); } -string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { +string generateXmpForSecondaryImage(uhdr_gainmap_metadata_ext_t& metadata) { const vector kConDirSeq({kConDirectory, string("rdf:Seq")}); std::stringstream ss; @@ -595,13 +677,13 @@ string generateXmpForSecondaryImage(ultrahdr_metadata_struct& metadata) { writer.StartWritingElement("rdf:Description"); writer.WriteXmlns(kGainMapPrefix, kGainMapUri); writer.WriteAttributeNameAndValue(kMapVersion, metadata.version); - writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.minContentBoost)); - writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.maxContentBoost)); + writer.WriteAttributeNameAndValue(kMapGainMapMin, log2(metadata.min_content_boost)); + writer.WriteAttributeNameAndValue(kMapGainMapMax, log2(metadata.max_content_boost)); writer.WriteAttributeNameAndValue(kMapGamma, metadata.gamma); - writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offsetSdr); - writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offsetHdr); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdrCapacityMin)); - writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdrCapacityMax)); + writer.WriteAttributeNameAndValue(kMapOffsetSdr, metadata.offset_sdr); + writer.WriteAttributeNameAndValue(kMapOffsetHdr, metadata.offset_hdr); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMin, log2(metadata.hdr_capacity_min)); + writer.WriteAttributeNameAndValue(kMapHDRCapacityMax, log2(metadata.hdr_capacity_max)); writer.WriteAttributeNameAndValue(kMapBaseRenditionIsHDR, "False"); writer.FinishWriting(); diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp index c7ad668..fdfe557 100644 --- a/lib/src/ultrahdr_api.cpp +++ b/lib/src/ultrahdr_api.cpp @@ -24,8 +24,6 @@ #include "ultrahdr/jpegr.h" #include "ultrahdr/jpegrutils.h" -static const uhdr_error_info_t g_no_error = {UHDR_CODEC_OK, 0, ""}; - namespace ultrahdr { uhdr_memory_block::uhdr_memory_block(size_t capacity) { @@ -49,6 +47,8 @@ uhdr_raw_image_ext::uhdr_raw_image_ext(uhdr_img_fmt_t fmt_, uhdr_color_gamut_t c int bpp = 1; if (fmt_ == UHDR_IMG_FMT_24bppYCbCrP010) { bpp = 2; + } else if (fmt_ == UHDR_IMG_FMT_24bppRGB888) { + bpp = 3; } else if (fmt_ == UHDR_IMG_FMT_32bppRGBA8888 || fmt_ == UHDR_IMG_FMT_32bppRGBA1010102) { bpp = 4; } else if (fmt_ == UHDR_IMG_FMT_64bppRGBAHalfFloat) { @@ -335,105 +335,6 @@ uhdr_codec_private::~uhdr_codec_private() { m_effects.clear(); } -ultrahdr::ultrahdr_color_gamut map_cg_to_internal_cg(uhdr_color_gamut_t cg) { - switch (cg) { - case UHDR_CG_BT_2100: - return ultrahdr::ULTRAHDR_COLORGAMUT_BT2100; - case UHDR_CG_BT_709: - return ultrahdr::ULTRAHDR_COLORGAMUT_BT709; - case UHDR_CG_DISPLAY_P3: - return ultrahdr::ULTRAHDR_COLORGAMUT_P3; - default: - return ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - } -} - -uhdr_color_gamut_t map_internal_cg_to_cg(ultrahdr::ultrahdr_color_gamut cg) { - switch (cg) { - case ultrahdr::ULTRAHDR_COLORGAMUT_BT2100: - return UHDR_CG_BT_2100; - case ultrahdr::ULTRAHDR_COLORGAMUT_BT709: - return UHDR_CG_BT_709; - case ultrahdr::ULTRAHDR_COLORGAMUT_P3: - return UHDR_CG_DISPLAY_P3; - default: - return UHDR_CG_UNSPECIFIED; - } -} - -ultrahdr::ultrahdr_transfer_function map_ct_to_internal_ct(uhdr_color_transfer_t ct) { - switch (ct) { - case UHDR_CT_HLG: - return ultrahdr::ULTRAHDR_TF_HLG; - case UHDR_CT_PQ: - return ultrahdr::ULTRAHDR_TF_PQ; - case UHDR_CT_LINEAR: - return ultrahdr::ULTRAHDR_TF_LINEAR; - case UHDR_CT_SRGB: - return ultrahdr::ULTRAHDR_TF_SRGB; - default: - return ultrahdr::ULTRAHDR_TF_UNSPECIFIED; - } -} - -ultrahdr::ultrahdr_output_format map_ct_fmt_to_internal_output_fmt(uhdr_color_transfer_t ct, - uhdr_img_fmt fmt) { - if (ct == UHDR_CT_HLG && fmt == UHDR_IMG_FMT_32bppRGBA1010102) { - return ultrahdr::ULTRAHDR_OUTPUT_HDR_HLG; - } else if (ct == UHDR_CT_PQ && fmt == UHDR_IMG_FMT_32bppRGBA1010102) { - return ultrahdr::ULTRAHDR_OUTPUT_HDR_PQ; - } else if (ct == UHDR_CT_LINEAR && fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { - return ultrahdr::ULTRAHDR_OUTPUT_HDR_LINEAR; - } else if (ct == UHDR_CT_SRGB && fmt == UHDR_IMG_FMT_32bppRGBA8888) { - return ultrahdr::ULTRAHDR_OUTPUT_SDR; - } - return ultrahdr::ULTRAHDR_OUTPUT_UNSPECIFIED; -} - -void map_internal_error_status_to_error_info(ultrahdr::status_t internal_status, - uhdr_error_info_t& status) { - if (internal_status == ultrahdr::JPEGR_NO_ERROR) { - status = g_no_error; - } else { - status.has_detail = 1; - if (internal_status == ultrahdr::ERROR_JPEGR_RESOLUTION_MISMATCH) { - status.error_code = UHDR_CODEC_INVALID_PARAM; - snprintf(status.detail, sizeof status.detail, - "dimensions of sdr intent and hdr intent do not match"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_ENCODE_ERROR) { - status.error_code = UHDR_CODEC_UNKNOWN_ERROR; - snprintf(status.detail, sizeof status.detail, "encountered unknown error during encoding"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_DECODE_ERROR) { - status.error_code = UHDR_CODEC_UNKNOWN_ERROR; - snprintf(status.detail, sizeof status.detail, "encountered unknown error during decoding"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_NO_IMAGES_FOUND) { - status.error_code = UHDR_CODEC_UNKNOWN_ERROR; - snprintf(status.detail, sizeof status.detail, "input uhdr image does not any valid images"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) { - status.error_code = UHDR_CODEC_UNKNOWN_ERROR; - snprintf(status.detail, sizeof status.detail, - "input uhdr image does not contain gainmap image"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_BUFFER_TOO_SMALL) { - status.error_code = UHDR_CODEC_MEM_ERROR; - snprintf(status.detail, sizeof status.detail, - "output buffer to store compressed data is too small"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_MULTIPLE_EXIFS_RECEIVED) { - status.error_code = UHDR_CODEC_INVALID_OPERATION; - snprintf(status.detail, sizeof status.detail, - "received exif from uhdr_enc_set_exif_data() while the base image intent already " - "contains exif, unsure which one to use"); - } else if (internal_status == ultrahdr::ERROR_JPEGR_UNSUPPORTED_MAP_SCALE_FACTOR) { - status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; - snprintf(status.detail, sizeof status.detail, - "say base image wd to gain map image wd ratio is 'k1' and base image ht to gain map " - "image ht ratio is 'k2', we found k1 != k2."); - } else { - status.error_code = UHDR_CODEC_UNKNOWN_ERROR; - status.has_detail = 0; - } - } -} - uhdr_error_info_t uhdr_enc_validate_and_set_compressed_img(uhdr_codec_private_t* enc, uhdr_compressed_image_t* img, uhdr_img_label_t intent) { @@ -986,54 +887,30 @@ uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) { } } - ultrahdr::status_t internal_status = ultrahdr::JPEGR_NO_ERROR; if (handle->m_output_format == UHDR_CODEC_JPG) { - ultrahdr::jpegr_exif_struct exif{}; + uhdr_mem_block_t exif{}; if (handle->m_exif.size() > 0) { exif.data = handle->m_exif.data(); - exif.length = handle->m_exif.size(); + exif.capacity = exif.data_sz = handle->m_exif.size(); } ultrahdr::JpegR jpegr(handle->m_gainmap_scale_factor, handle->m_quality.find(UHDR_GAIN_MAP_IMG)->second, handle->m_use_multi_channel_gainmap); - ultrahdr::jpegr_compressed_struct dest{}; if (handle->m_compressed_images.find(UHDR_BASE_IMG) != handle->m_compressed_images.end() && handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG) != handle->m_compressed_images.end()) { auto& base_entry = handle->m_compressed_images.find(UHDR_BASE_IMG)->second; - ultrahdr::jpegr_compressed_struct primary_image; - primary_image.data = base_entry->data; - primary_image.length = primary_image.maxLength = base_entry->data_sz; - primary_image.colorGamut = map_cg_to_internal_cg(base_entry->cg); - auto& gainmap_entry = handle->m_compressed_images.find(UHDR_GAIN_MAP_IMG)->second; - ultrahdr::jpegr_compressed_struct gainmap_image; - gainmap_image.data = gainmap_entry->data; - gainmap_image.length = gainmap_image.maxLength = gainmap_entry->data_sz; - gainmap_image.colorGamut = map_cg_to_internal_cg(gainmap_entry->cg); - - ultrahdr::ultrahdr_metadata_struct metadata; - metadata.version = ultrahdr::kJpegrVersion; - metadata.maxContentBoost = handle->m_metadata.max_content_boost; - metadata.minContentBoost = handle->m_metadata.min_content_boost; - metadata.gamma = handle->m_metadata.gamma; - metadata.offsetSdr = handle->m_metadata.offset_sdr; - metadata.offsetHdr = handle->m_metadata.offset_hdr; - metadata.hdrCapacityMin = handle->m_metadata.hdr_capacity_min; - metadata.hdrCapacityMax = handle->m_metadata.hdr_capacity_max; - - size_t size = (std::max)((8 * 1024), 2 * (primary_image.length + gainmap_image.length)); + + size_t size = (std::max)((8u * 1024), 2 * (base_entry->data_sz + gainmap_entry->data_sz)); handle->m_compressed_output_buffer = std::make_unique( UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size); - dest.data = handle->m_compressed_output_buffer->data; - dest.length = 0; - dest.maxLength = handle->m_compressed_output_buffer->capacity; - dest.colorGamut = ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED; + ultrahdr::uhdr_gainmap_metadata_ext_t metadata(handle->m_metadata, ultrahdr::kJpegrVersion); // api - 4 - internal_status = jpegr.encodeJPEGR(&primary_image, &gainmap_image, &metadata, &dest); - map_internal_error_status_to_error_info(internal_status, status); + status = jpegr.encodeJPEGR(base_entry.get(), gainmap_entry.get(), &metadata, + handle->m_compressed_output_buffer.get()); } else if (handle->m_raw_images.find(UHDR_HDR_IMG) != handle->m_raw_images.end()) { auto& hdr_raw_entry = handle->m_raw_images.find(UHDR_HDR_IMG)->second; @@ -1041,83 +918,42 @@ uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) { handle->m_compressed_output_buffer = std::make_unique( UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, size); - dest.data = handle->m_compressed_output_buffer->data; - dest.length = 0; - dest.maxLength = handle->m_compressed_output_buffer->capacity; - dest.colorGamut = ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED; - - ultrahdr::jpegr_uncompressed_struct p010_image; - p010_image.data = hdr_raw_entry->planes[UHDR_PLANE_Y]; - p010_image.width = hdr_raw_entry->w; - p010_image.height = hdr_raw_entry->h; - p010_image.colorGamut = map_cg_to_internal_cg(hdr_raw_entry->cg); - p010_image.luma_stride = hdr_raw_entry->stride[UHDR_PLANE_Y]; - p010_image.chroma_data = hdr_raw_entry->planes[UHDR_PLANE_UV]; - p010_image.chroma_stride = hdr_raw_entry->stride[UHDR_PLANE_UV]; - p010_image.colorRange = hdr_raw_entry->range; - p010_image.pixelFormat = hdr_raw_entry->fmt; - if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end() && handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { // api - 0 - internal_status = jpegr.encodeJPEGR(&p010_image, map_ct_to_internal_ct(hdr_raw_entry->ct), - &dest, handle->m_quality.find(UHDR_BASE_IMG)->second, - handle->m_exif.size() > 0 ? &exif : nullptr); + status = jpegr.encodeJPEGR(hdr_raw_entry.get(), handle->m_compressed_output_buffer.get(), + handle->m_quality.find(UHDR_BASE_IMG)->second, + handle->m_exif.size() > 0 ? &exif : nullptr); } else if (handle->m_compressed_images.find(UHDR_SDR_IMG) != handle->m_compressed_images.end() && handle->m_raw_images.find(UHDR_SDR_IMG) == handle->m_raw_images.end()) { auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second; - ultrahdr::jpegr_compressed_struct sdr_compressed_image; - sdr_compressed_image.data = sdr_compressed_entry->data; - sdr_compressed_image.length = sdr_compressed_image.maxLength = - sdr_compressed_entry->data_sz; - sdr_compressed_image.colorGamut = map_cg_to_internal_cg(sdr_compressed_entry->cg); // api - 3 - internal_status = jpegr.encodeJPEGR(&p010_image, &sdr_compressed_image, - map_ct_to_internal_ct(hdr_raw_entry->ct), &dest); + status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_compressed_entry.get(), + handle->m_compressed_output_buffer.get()); } else if (handle->m_raw_images.find(UHDR_SDR_IMG) != handle->m_raw_images.end()) { auto& sdr_raw_entry = handle->m_raw_images.find(UHDR_SDR_IMG)->second; - ultrahdr::jpegr_uncompressed_struct yuv420_image; - yuv420_image.data = sdr_raw_entry->planes[UHDR_PLANE_Y]; - yuv420_image.width = sdr_raw_entry->w; - yuv420_image.height = sdr_raw_entry->h; - yuv420_image.colorGamut = map_cg_to_internal_cg(sdr_raw_entry->cg); - yuv420_image.luma_stride = sdr_raw_entry->stride[UHDR_PLANE_Y]; - yuv420_image.chroma_data = nullptr; - yuv420_image.chroma_stride = 0; - yuv420_image.pixelFormat = sdr_raw_entry->fmt; - if (handle->m_compressed_images.find(UHDR_SDR_IMG) == handle->m_compressed_images.end()) { // api - 1 - internal_status = jpegr.encodeJPEGR(&p010_image, &yuv420_image, - map_ct_to_internal_ct(hdr_raw_entry->ct), &dest, - handle->m_quality.find(UHDR_BASE_IMG)->second, - handle->m_exif.size() > 0 ? &exif : nullptr); + status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_raw_entry.get(), + handle->m_compressed_output_buffer.get(), + handle->m_quality.find(UHDR_BASE_IMG)->second, + handle->m_exif.size() > 0 ? &exif : nullptr); } else { auto& sdr_compressed_entry = handle->m_compressed_images.find(UHDR_SDR_IMG)->second; - ultrahdr::jpegr_compressed_struct sdr_compressed_image; - sdr_compressed_image.data = sdr_compressed_entry->data; - sdr_compressed_image.length = sdr_compressed_image.maxLength = - sdr_compressed_entry->data_sz; - sdr_compressed_image.colorGamut = map_cg_to_internal_cg(sdr_compressed_entry->cg); - // api - 2 - internal_status = jpegr.encodeJPEGR(&p010_image, &yuv420_image, &sdr_compressed_image, - map_ct_to_internal_ct(hdr_raw_entry->ct), &dest); + status = jpegr.encodeJPEGR(hdr_raw_entry.get(), sdr_raw_entry.get(), + sdr_compressed_entry.get(), + handle->m_compressed_output_buffer.get()); } } - map_internal_error_status_to_error_info(internal_status, status); } else { status.error_code = UHDR_CODEC_INVALID_OPERATION; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, "resources required for uhdr_encode() operation are not present"); } - if (status.error_code == UHDR_CODEC_OK) { - handle->m_compressed_output_buffer->data_sz = dest.length; - handle->m_compressed_output_buffer->cg = map_internal_cg_to_cg(dest.colorGamut); - } } return status; @@ -1372,37 +1208,27 @@ uhdr_error_info_t uhdr_dec_probe(uhdr_codec_private_t* dec) { jpegr_info.primaryImgInfo = &primary_image; jpegr_info.gainmapImgInfo = &gainmap_image; - ultrahdr::jpegr_compressed_struct uhdr_image; - uhdr_image.data = handle->m_uhdr_compressed_img->data; - uhdr_image.length = uhdr_image.maxLength = handle->m_uhdr_compressed_img->data_sz; - uhdr_image.colorGamut = map_cg_to_internal_cg(handle->m_uhdr_compressed_img->cg); - ultrahdr::JpegR jpegr; - ultrahdr::status_t internal_status = jpegr.getJPEGRInfo(&uhdr_image, &jpegr_info); - map_internal_error_status_to_error_info(internal_status, status); + status = jpegr.getJPEGRInfo(handle->m_uhdr_compressed_img.get(), &jpegr_info); if (status.error_code != UHDR_CODEC_OK) return status; - ultrahdr::ultrahdr_metadata_struct metadata; - if (ultrahdr::getMetadataFromXMP(gainmap_image.xmpData.data(), gainmap_image.xmpData.size(), - &metadata)) { - handle->m_metadata.max_content_boost = metadata.maxContentBoost; - handle->m_metadata.min_content_boost = metadata.minContentBoost; - handle->m_metadata.gamma = metadata.gamma; - handle->m_metadata.offset_sdr = metadata.offsetSdr; - handle->m_metadata.offset_hdr = metadata.offsetHdr; - handle->m_metadata.hdr_capacity_min = metadata.hdrCapacityMin; - handle->m_metadata.hdr_capacity_max = metadata.hdrCapacityMax; - } else { - status.error_code = UHDR_CODEC_UNKNOWN_ERROR; - status.has_detail = 1; - snprintf(status.detail, sizeof status.detail, "encountered error while parsing metadata"); - return status; - } + ultrahdr::uhdr_gainmap_metadata_ext_t metadata; + status = ultrahdr::getMetadataFromXMP(gainmap_image.xmpData.data(), + gainmap_image.xmpData.size(), &metadata); + if (status.error_code != UHDR_CODEC_OK) return status; + handle->m_metadata.max_content_boost = metadata.max_content_boost; + handle->m_metadata.min_content_boost = metadata.min_content_boost; + handle->m_metadata.gamma = metadata.gamma; + handle->m_metadata.offset_sdr = metadata.offset_sdr; + handle->m_metadata.offset_hdr = metadata.offset_hdr; + handle->m_metadata.hdr_capacity_min = metadata.hdr_capacity_min; + handle->m_metadata.hdr_capacity_max = metadata.hdr_capacity_max; handle->m_img_wd = primary_image.width; handle->m_img_ht = primary_image.height; handle->m_gainmap_wd = gainmap_image.width; handle->m_gainmap_ht = gainmap_image.height; + handle->m_gainmap_num_comp = gainmap_image.numComponents; handle->m_exif = std::move(primary_image.exifData); handle->m_exif_block.data = handle->m_exif.data(); handle->m_exif_block.data_sz = handle->m_exif_block.capacity = handle->m_exif.size(); @@ -1528,9 +1354,11 @@ uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec) { handle->m_sailed = true; - ultrahdr::ultrahdr_output_format outputFormat = - map_ct_fmt_to_internal_output_fmt(handle->m_output_ct, handle->m_output_fmt); - if (outputFormat == ultrahdr::ultrahdr_output_format::ULTRAHDR_OUTPUT_UNSPECIFIED) { + if ((handle->m_output_fmt == UHDR_IMG_FMT_32bppRGBA1010102 && + (handle->m_output_ct != UHDR_CT_HLG && handle->m_output_ct != UHDR_CT_PQ)) || + (handle->m_output_fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat && + handle->m_output_ct != UHDR_CT_LINEAR) || + (handle->m_output_fmt == UHDR_IMG_FMT_32bppRGBA8888 && handle->m_output_ct != UHDR_CT_SRGB)) { status.error_code = UHDR_CODEC_INVALID_PARAM; status.has_detail = 1; snprintf(status.detail, sizeof status.detail, @@ -1538,34 +1366,20 @@ uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec) { return status; } - ultrahdr::jpegr_compressed_struct uhdr_image; - uhdr_image.data = handle->m_uhdr_compressed_img->data; - uhdr_image.length = uhdr_image.maxLength = handle->m_uhdr_compressed_img->data_sz; - uhdr_image.colorGamut = map_cg_to_internal_cg(handle->m_uhdr_compressed_img->cg); - handle->m_decoded_img_buffer = std::make_unique( handle->m_output_fmt, UHDR_CG_UNSPECIFIED, handle->m_output_ct, UHDR_CR_UNSPECIFIED, handle->m_img_wd, handle->m_img_ht, 1); - // alias - ultrahdr::jpegr_uncompressed_struct dest; - dest.data = handle->m_decoded_img_buffer->planes[UHDR_PLANE_PACKED]; - dest.colorGamut = ultrahdr::ULTRAHDR_COLORGAMUT_UNSPECIFIED; handle->m_gainmap_img_buffer = std::make_unique( - UHDR_IMG_FMT_8bppYCbCr400, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, - handle->m_gainmap_wd, handle->m_gainmap_ht, 1); - // alias - ultrahdr::jpegr_uncompressed_struct dest_gainmap; - dest_gainmap.data = handle->m_gainmap_img_buffer->planes[UHDR_PLANE_Y]; + handle->m_gainmap_num_comp == 1 ? UHDR_IMG_FMT_8bppYCbCr400 : UHDR_IMG_FMT_32bppRGBA8888, + UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, handle->m_gainmap_wd, + handle->m_gainmap_ht, 1); ultrahdr::JpegR jpegr; - ultrahdr::status_t internal_status = - jpegr.decodeJPEGR(&uhdr_image, &dest, handle->m_output_max_disp_boost, nullptr, outputFormat, - &dest_gainmap, nullptr); - map_internal_error_status_to_error_info(internal_status, status); - if (status.error_code == UHDR_CODEC_OK) { - handle->m_decoded_img_buffer->cg = map_internal_cg_to_cg(dest.colorGamut); - } + status = + jpegr.decodeJPEGR(handle->m_uhdr_compressed_img.get(), handle->m_decoded_img_buffer.get(), + handle->m_output_max_disp_boost, handle->m_output_ct, handle->m_output_fmt, + handle->m_gainmap_img_buffer.get(), nullptr); if (status.error_code == UHDR_CODEC_OK && dec->m_effects.size() != 0) { status = ultrahdr::apply_effects(handle); @@ -1621,6 +1435,7 @@ void uhdr_reset_decoder(uhdr_codec_private_t* dec) { handle->m_img_ht = 0; handle->m_gainmap_wd = 0; handle->m_gainmap_ht = 0; + handle->m_gainmap_num_comp = 0; handle->m_exif.clear(); memset(&handle->m_exif_block, 0, sizeof handle->m_exif_block); handle->m_icc.clear(); diff --git a/tests/gainmapmath_test.cpp b/tests/gainmapmath_test.cpp index b2b09d3..c3358dd 100644 --- a/tests/gainmapmath_test.cpp +++ b/tests/gainmapmath_test.cpp @@ -48,19 +48,21 @@ class GainMapMathTest : public testing::Test { int16_t v; }; - Pixel getYuv420Pixel_uint(jr_uncompressed_ptr image, size_t x, size_t y) { - uint8_t* luma_data = reinterpret_cast(image->data); - size_t luma_stride = image->luma_stride; - uint8_t* chroma_data = reinterpret_cast(image->chroma_data); - size_t chroma_stride = image->chroma_stride; + Pixel getYuv420Pixel_uint(uhdr_raw_image_t* image, size_t x, size_t y) { + uint8_t* luma_data = reinterpret_cast(image->planes[UHDR_PLANE_Y]); + size_t luma_stride = image->stride[UHDR_PLANE_Y]; + uint8_t* cb_data = reinterpret_cast(image->planes[UHDR_PLANE_U]); + size_t cb_stride = image->stride[UHDR_PLANE_U]; + uint8_t* cr_data = reinterpret_cast(image->planes[UHDR_PLANE_V]); + size_t cr_stride = image->stride[UHDR_PLANE_V]; - size_t offset_cr = chroma_stride * (image->height / 2); size_t pixel_y_idx = x + y * luma_stride; - size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride; + size_t pixel_cb_idx = x / 2 + (y / 2) * cb_stride; + size_t pixel_cr_idx = x / 2 + (y / 2) * cr_stride; uint8_t y_uint = luma_data[pixel_y_idx]; - uint8_t u_uint = chroma_data[pixel_chroma_idx]; - uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx]; + uint8_t u_uint = cb_data[pixel_cb_idx]; + uint8_t v_uint = cr_data[pixel_cr_idx]; return {y_uint, u_uint, v_uint}; } @@ -146,13 +148,13 @@ class GainMapMathTest : public testing::Test { return luminance_scaled * scale_factor; } - Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) { + Color Recover(Color yuv_gamma, float gain, uhdr_gainmap_metadata_ext_t* metadata) { Color rgb_gamma = srgbYuvToRgb(yuv_gamma); Color rgb = srgbInvOetf(rgb_gamma); return applyGain(rgb, gain, metadata); } - jpegr_uncompressed_struct Yuv420Image() { + uhdr_raw_image_t Yuv420Image() { static uint8_t pixels[] = { // Y 0x00, @@ -182,10 +184,23 @@ class GainMapMathTest : public testing::Test { 0xB2, 0xB3, }; - return {pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2}; + uhdr_raw_image_t img; + img.cg = UHDR_CG_BT_709; + img.ct = UHDR_CT_SRGB; + img.range = UHDR_CR_FULL_RANGE; + img.fmt = UHDR_IMG_FMT_12bppYCbCr420; + img.w = 4; + img.h = 4; + img.planes[UHDR_PLANE_Y] = pixels; + img.planes[UHDR_PLANE_U] = pixels + 16; + img.planes[UHDR_PLANE_V] = pixels + 16 + 4; + img.stride[UHDR_PLANE_Y] = 4; + img.stride[UHDR_PLANE_U] = 2; + img.stride[UHDR_PLANE_V] = 2; + return img; } - jpegr_uncompressed_struct Yuv420Image32x4() { + uhdr_raw_image_t Yuv420Image32x4() { // clang-format off static uint8_t pixels[] = { // Y @@ -205,7 +220,20 @@ class GainMapMathTest : public testing::Test { 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDD, 0xDD, 0xDC, 0xDD, 0xDE, 0xDF, }; // clang-format on - return {pixels, 32, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 128, 32, 16}; + uhdr_raw_image_t img; + img.cg = UHDR_CG_BT_709; + img.ct = UHDR_CT_SRGB; + img.range = UHDR_CR_FULL_RANGE; + img.fmt = UHDR_IMG_FMT_12bppYCbCr420; + img.w = 32; + img.h = 4; + img.planes[UHDR_PLANE_Y] = pixels; + img.planes[UHDR_PLANE_U] = pixels + 128; + img.planes[UHDR_PLANE_V] = pixels + 128 + 32; + img.stride[UHDR_PLANE_Y] = 32; + img.stride[UHDR_PLANE_U] = 16; + img.stride[UHDR_PLANE_V] = 16; + return img; } Color (*Yuv420Colors())[4] { @@ -238,7 +266,7 @@ class GainMapMathTest : public testing::Test { return colors; } - jpegr_uncompressed_struct P010Image() { + uhdr_raw_image_t P010Image() { static uint16_t pixels[] = { // Y 0x00 << 6, @@ -267,7 +295,20 @@ class GainMapMathTest : public testing::Test { 0xA3 << 6, 0xB3 << 6, }; - return {pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4}; + uhdr_raw_image_t img; + img.cg = UHDR_CG_BT_709; + img.ct = UHDR_CT_HLG; + img.range = UHDR_CR_LIMITED_RANGE; + img.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + img.w = 4; + img.h = 4; + img.planes[UHDR_PLANE_Y] = pixels; + img.planes[UHDR_PLANE_UV] = pixels + 16; + img.planes[UHDR_PLANE_V] = nullptr; + img.stride[UHDR_PLANE_Y] = 4; + img.stride[UHDR_PLANE_UV] = 4; + img.stride[UHDR_PLANE_V] = 0; + return img; } Color (*P010Colors())[4] { @@ -300,12 +341,26 @@ class GainMapMathTest : public testing::Test { return colors; } - jpegr_uncompressed_struct MapImage() { + uhdr_raw_image_t MapImage() { static uint8_t pixels[] = { 0x00, 0x10, 0x20, 0x30, 0x01, 0x11, 0x21, 0x31, 0x02, 0x12, 0x22, 0x32, 0x03, 0x13, 0x23, 0x33, }; - return {pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED}; + + uhdr_raw_image_t img; + img.cg = UHDR_CG_UNSPECIFIED; + img.ct = UHDR_CT_UNSPECIFIED; + img.range = UHDR_CR_UNSPECIFIED; + img.fmt = UHDR_IMG_FMT_8bppYCbCr400; + img.w = 4; + img.h = 4; + img.planes[UHDR_PLANE_Y] = pixels; + img.planes[UHDR_PLANE_U] = nullptr; + img.planes[UHDR_PLANE_V] = nullptr; + img.stride[UHDR_PLANE_Y] = 4; + img.stride[UHDR_PLANE_U] = 0; + img.stride[UHDR_PLANE_V] = 0; + return img; } float (*MapValues())[4] { @@ -826,27 +881,31 @@ TEST_F(GainMapMathTest, YuvConversionNeon) { #endif TEST_F(GainMapMathTest, TransformYuv420) { - jpegr_uncompressed_struct input = Yuv420Image(); - const size_t buf_size = input.width * input.height * 3 / 2; + auto input = Yuv420Image(); + const size_t buf_size = input.w * input.h * 3 / 2; std::unique_ptr out_buf = std::make_unique(buf_size); + uint8_t* luma = out_buf.get(); + uint8_t* cb = luma + input.w * input.h; + uint8_t* cr = cb + input.w * input.h / 4; const std::array, 6> conversion_coeffs = { kYuvBt709ToBt601, kYuvBt709ToBt2100, kYuvBt601ToBt709, kYuvBt601ToBt2100, kYuvBt2100ToBt709, kYuvBt2100ToBt601}; for (size_t coeffs_idx = 0; coeffs_idx < conversion_coeffs.size(); ++coeffs_idx) { - jpegr_uncompressed_struct output = Yuv420Image(); - memcpy(out_buf.get(), input.data, buf_size); - output.data = out_buf.get(); - output.chroma_data = out_buf.get() + input.width * input.height; - output.luma_stride = input.width; - output.chroma_stride = input.width / 2; + auto output = Yuv420Image(); + memcpy(luma, input.planes[UHDR_PLANE_Y], input.w * input.h); + memcpy(cb, input.planes[UHDR_PLANE_U], input.w * input.h / 4); + memcpy(cr, input.planes[UHDR_PLANE_V], input.w * input.h / 4); + output.planes[UHDR_PLANE_Y] = luma; + output.planes[UHDR_PLANE_U] = cb; + output.planes[UHDR_PLANE_V] = cr; // Perform a color gamut conversion to the entire 4:2:0 image. transformYuv420(&output, conversion_coeffs.at(coeffs_idx)); - for (size_t y = 0; y < input.height; y += 2) { - for (size_t x = 0; x < input.width; x += 2) { + for (size_t y = 0; y < input.h; y += 2) { + for (size_t x = 0; x < input.w; x += 2) { Pixel out1 = getYuv420Pixel_uint(&output, x, y); Pixel out2 = getYuv420Pixel_uint(&output, x + 1, y); Pixel out3 = getYuv420Pixel_uint(&output, x, y + 1); @@ -907,21 +966,25 @@ TEST_F(GainMapMathTest, TransformYuv420Neon) { {kYuv2100To601_coeffs_neon, kYuvBt2100ToBt601}}}; for (const auto& [neon_coeffs_ptr, floating_point_coeffs] : fixed_floating_coeffs) { - jpegr_uncompressed_struct input = Yuv420Image32x4(); - const size_t buf_size = input.width * input.height * 3 / 2; - + uhdr_raw_image_t input = Yuv420Image32x4(); + const size_t buf_size = input.w * input.h * 3 / 2; std::unique_ptr out_buf = std::make_unique(buf_size); - memcpy(out_buf.get(), input.data, buf_size); - jpegr_uncompressed_struct output = Yuv420Image32x4(); - output.data = out_buf.get(); - output.chroma_data = out_buf.get() + input.width * input.height; - output.luma_stride = input.width; - output.chroma_stride = input.width / 2; + uint8_t* luma = out_buf.get(); + uint8_t* cb = luma + input.w * input.h; + uint8_t* cr = cb + input.w * input.h / 4; + + uhdr_raw_image_t output = Yuv420Image32x4(); + memcpy(luma, input.planes[UHDR_PLANE_Y], input.w * input.h); + memcpy(cb, input.planes[UHDR_PLANE_U], input.w * input.h / 4); + memcpy(cr, input.planes[UHDR_PLANE_V], input.w * input.h / 4); + output.planes[UHDR_PLANE_Y] = luma; + output.planes[UHDR_PLANE_U] = cb; + output.planes[UHDR_PLANE_V] = cr; transformYuv420_neon(&output, neon_coeffs_ptr); - for (size_t y = 0; y < input.height / 2; ++y) { - for (size_t x = 0; x < input.width / 2; ++x) { + for (size_t y = 0; y < input.h / 2; ++y) { + for (size_t x = 0; x < input.w / 2; ++x) { const Pixel out1 = getYuv420Pixel_uint(&output, x * 2, y * 2); const Pixel out2 = getYuv420Pixel_uint(&output, x * 2 + 1, y * 2); const Pixel out3 = getYuv420Pixel_uint(&output, x * 2, y * 2 + 1); @@ -1064,13 +1127,13 @@ TEST_F(GainMapMathTest, srgbInvOetfLUT) { TEST_F(GainMapMathTest, applyGainLUT) { for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata; + uhdr_gainmap_metadata_ext_t metadata; - metadata.minContentBoost = 1.0f / static_cast(boost); - metadata.maxContentBoost = static_cast(boost); + metadata.min_content_boost = 1.0f / static_cast(boost); + metadata.max_content_boost = static_cast(boost); metadata.gamma = 1.0f; GainLUT gainLUT(&metadata); - GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); + GainLUT gainLUTWithBoost(&metadata, metadata.max_content_boost); for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), @@ -1097,13 +1160,13 @@ TEST_F(GainMapMathTest, applyGainLUT) { } for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata; + uhdr_gainmap_metadata_ext_t metadata; - metadata.minContentBoost = 1.0f; - metadata.maxContentBoost = static_cast(boost); + metadata.min_content_boost = 1.0f; + metadata.max_content_boost = static_cast(boost); metadata.gamma = 1.0f; GainLUT gainLUT(&metadata); - GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); + GainLUT gainLUTWithBoost(&metadata, metadata.max_content_boost); for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), @@ -1130,13 +1193,13 @@ TEST_F(GainMapMathTest, applyGainLUT) { } for (int boost = 1; boost <= 10; boost++) { - ultrahdr_metadata_struct metadata; + uhdr_gainmap_metadata_ext_t metadata; - metadata.minContentBoost = 1.0f / powf(static_cast(boost), 1.0f / 3.0f); - metadata.maxContentBoost = static_cast(boost); + metadata.min_content_boost = 1.0f / powf(static_cast(boost), 1.0f / 3.0f); + metadata.max_content_boost = static_cast(boost); metadata.gamma = 1.0f; GainLUT gainLUT(&metadata); - GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost); + GainLUT gainLUTWithBoost(&metadata, metadata.max_content_boost); for (size_t idx = 0; idx < kGainFactorNumEntries; idx++) { float value = static_cast(idx) / static_cast(kGainFactorNumEntries - 1); EXPECT_RGB_NEAR(applyGain(RgbBlack(), value, &metadata), @@ -1172,41 +1235,32 @@ TEST_F(GainMapMathTest, PqTransferFunctionRoundtrip) { } TEST_F(GainMapMathTest, ColorConversionLookup) { - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT709), - identityConversion); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3), p3ToBt709); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_BT2100), - bt2100ToBt709); - - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_UNSPECIFIED), nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT709), bt709ToP3); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_P3), identityConversion); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100), bt2100ToP3); - - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT709), - bt709ToBt2100); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_P3), p3ToBt2100); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_BT2100, ULTRAHDR_COLORGAMUT_BT2100), - identityConversion); - - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_UNSPECIFIED), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT709), - nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_P3), nullptr); - EXPECT_EQ(getHdrConversionFn(ULTRAHDR_COLORGAMUT_UNSPECIFIED, ULTRAHDR_COLORGAMUT_BT2100), - nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_709, UHDR_CG_UNSPECIFIED), nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_709, UHDR_CG_BT_709), identityConversion); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_709, UHDR_CG_DISPLAY_P3), p3ToBt709); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_709, UHDR_CG_BT_2100), bt2100ToBt709); + + EXPECT_EQ(getHdrConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_UNSPECIFIED), nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_BT_709), bt709ToP3); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_DISPLAY_P3), identityConversion); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_DISPLAY_P3, UHDR_CG_BT_2100), bt2100ToP3); + + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_2100, UHDR_CG_UNSPECIFIED), nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_2100, UHDR_CG_BT_709), bt709ToBt2100); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_2100, UHDR_CG_DISPLAY_P3), p3ToBt2100); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_BT_2100, UHDR_CG_BT_2100), identityConversion); + + EXPECT_EQ(getHdrConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_UNSPECIFIED), nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_BT_709), nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_DISPLAY_P3), nullptr); + EXPECT_EQ(getHdrConversionFn(UHDR_CG_UNSPECIFIED, UHDR_CG_BT_2100), nullptr); } TEST_F(GainMapMathTest, EncodeGain) { - ultrahdr_metadata_struct metadata; + uhdr_gainmap_metadata_ext_t metadata; - metadata.minContentBoost = 1.0f / 4.0f; - metadata.maxContentBoost = 4.0f; + metadata.min_content_boost = 1.0f / 4.0f; + metadata.max_content_boost = 4.0f; metadata.gamma = 1.0f; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 127); @@ -1222,24 +1276,24 @@ TEST_F(GainMapMathTest, EncodeGain) { EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 191); EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 63); - metadata.maxContentBoost = 2.0f; - metadata.minContentBoost = 1.0f / 2.0f; + metadata.max_content_boost = 2.0f; + metadata.min_content_boost = 1.0f / 2.0f; EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 255); EXPECT_EQ(encodeGain(2.0f, 1.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 1.41421f, &metadata), 191); EXPECT_EQ(encodeGain(1.41421f, 1.0f, &metadata), 63); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f / 8.0f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 1.0f / 8.0f; EXPECT_EQ(encodeGain(1.0f, 8.0f, &metadata), 255); EXPECT_EQ(encodeGain(8.0f, 1.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 2.82843f, &metadata), 191); EXPECT_EQ(encodeGain(2.82843f, 1.0f, &metadata), 63); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 1.0f; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 0); EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); @@ -1249,8 +1303,8 @@ TEST_F(GainMapMathTest, EncodeGain) { EXPECT_EQ(encodeGain(1.0f, 4.0f, &metadata), 170); EXPECT_EQ(encodeGain(1.0f, 2.0f, &metadata), 85); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 0.5f; EXPECT_EQ(encodeGain(0.0f, 0.0f, &metadata), 63); EXPECT_EQ(encodeGain(1.0f, 0.0f, &metadata), 0); @@ -1264,12 +1318,12 @@ TEST_F(GainMapMathTest, EncodeGain) { } TEST_F(GainMapMathTest, ApplyGain) { - ultrahdr_metadata_struct metadata; + uhdr_gainmap_metadata_ext_t metadata; - metadata.minContentBoost = 1.0f / 4.0f; - metadata.maxContentBoost = 4.0f; + metadata.min_content_boost = 1.0f / 4.0f; + metadata.max_content_boost = 4.0f; metadata.gamma = 1.0f; - float displayBoost = metadata.maxContentBoost; + float displayBoost = metadata.max_content_boost; EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.0f, &metadata), RgbBlack()); EXPECT_RGB_NEAR(applyGain(RgbBlack(), 0.5f, &metadata), RgbBlack()); @@ -1281,8 +1335,8 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 4.0f); - metadata.maxContentBoost = 2.0f; - metadata.minContentBoost = 1.0f / 2.0f; + metadata.max_content_boost = 2.0f; + metadata.min_content_boost = 1.0f / 2.0f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 1.41421f); @@ -1290,8 +1344,8 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 1.41421f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 2.0f); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f / 8.0f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 1.0f / 8.0f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 8.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite() / 2.82843f); @@ -1299,16 +1353,16 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.75f, &metadata), RgbWhite() * 2.82843f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 1.0f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite()); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 0.5f; EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.0f, &metadata), RgbWhite() / 2.0f); EXPECT_RGB_NEAR(applyGain(RgbWhite(), 0.25f, &metadata), RgbWhite()); @@ -1317,8 +1371,8 @@ TEST_F(GainMapMathTest, ApplyGain) { EXPECT_RGB_NEAR(applyGain(RgbWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); Color e = {{{0.0f, 0.5f, 1.0f}}}; - metadata.maxContentBoost = 4.0f; - metadata.minContentBoost = 1.0f / 4.0f; + metadata.max_content_boost = 4.0f; + metadata.min_content_boost = 1.0f / 4.0f; EXPECT_RGB_NEAR(applyGain(e, 0.0f, &metadata), e / 4.0f); EXPECT_RGB_NEAR(applyGain(e, 0.25f, &metadata), e / 2.0f); @@ -1340,7 +1394,7 @@ TEST_F(GainMapMathTest, ApplyGain) { } TEST_F(GainMapMathTest, GetYuv420Pixel) { - jpegr_uncompressed_struct image = Yuv420Image(); + auto image = Yuv420Image(); Color(*colors)[4] = Yuv420Colors(); for (size_t y = 0; y < 4; ++y) { @@ -1351,7 +1405,7 @@ TEST_F(GainMapMathTest, GetYuv420Pixel) { } TEST_F(GainMapMathTest, GetP010Pixel) { - jpegr_uncompressed_struct image = P010Image(); + auto image = P010Image(); Color(*colors)[4] = P010Colors(); for (size_t y = 0; y < 4; ++y) { @@ -1362,7 +1416,7 @@ TEST_F(GainMapMathTest, GetP010Pixel) { } TEST_F(GainMapMathTest, SampleYuv420) { - jpegr_uncompressed_struct image = Yuv420Image(); + auto image = Yuv420Image(); Color(*colors)[4] = Yuv420Colors(); static const size_t kMapScaleFactor = 2; @@ -1388,7 +1442,7 @@ TEST_F(GainMapMathTest, SampleYuv420) { } TEST_F(GainMapMathTest, SampleP010) { - jpegr_uncompressed_struct image = P010Image(); + auto image = P010Image(); Color(*colors)[4] = P010Colors(); static const size_t kMapScaleFactor = 2; @@ -1414,7 +1468,7 @@ TEST_F(GainMapMathTest, SampleP010) { } TEST_F(GainMapMathTest, SampleMap) { - jpegr_uncompressed_struct image = MapImage(); + auto image = MapImage(); float(*values)[4] = MapValues(); static const size_t kMapScaleFactor = 2; @@ -1558,10 +1612,10 @@ TEST_F(GainMapMathTest, GenerateMapLuminancePq) { } TEST_F(GainMapMathTest, ApplyMap) { - ultrahdr_metadata_struct metadata; + uhdr_gainmap_metadata_ext_t metadata; - metadata.minContentBoost = 1.0f / 8.0f; - metadata.maxContentBoost = 8.0f; + metadata.min_content_boost = 1.0f / 8.0f; + metadata.max_content_boost = 8.0f; metadata.gamma = 1.0f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); @@ -1594,16 +1648,16 @@ TEST_F(GainMapMathTest, ApplyMap) { EXPECT_RGB_CLOSE(Recover(SrgbYuvGreen(), 0.0f, &metadata), RgbGreen() / 8.0f); EXPECT_RGB_CLOSE(Recover(SrgbYuvBlue(), 0.0f, &metadata), RgbBlue() / 8.0f); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 1.0f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 1.0f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 2.0f / 3.0f, &metadata), RgbWhite() * 4.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f / 3.0f, &metadata), RgbWhite() * 2.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.0f, &metadata), RgbWhite()); - metadata.maxContentBoost = 8.0f; - metadata.minContentBoost = 0.5f; + metadata.max_content_boost = 8.0f; + metadata.min_content_boost = 0.5f; EXPECT_RGB_EQ(Recover(YuvWhite(), 1.0f, &metadata), RgbWhite() * 8.0f); EXPECT_RGB_EQ(Recover(YuvWhite(), 0.75, &metadata), RgbWhite() * 4.0f); diff --git a/tests/gainmapmetadata_test.cpp b/tests/gainmapmetadata_test.cpp index 5131bed..88e9a7c 100644 --- a/tests/gainmapmetadata_test.cpp +++ b/tests/gainmapmetadata_test.cpp @@ -42,35 +42,35 @@ void GainMapMetadataTest::TearDown() {} const std::string kIso = "urn:iso:std:iso:ts:21496:-1"; TEST_F(GainMapMetadataTest, encodeMetadataThenDecode) { - ultrahdr_metadata_struct expected; - expected.version = "1.0"; - expected.maxContentBoost = 100.5f; - expected.minContentBoost = 1.5f; + uhdr_gainmap_metadata_ext_t expected("1.0"); + expected.max_content_boost = 100.5f; + expected.min_content_boost = 1.5f; expected.gamma = 1.0f; - expected.offsetSdr = 0.0f; - expected.offsetHdr = 0.0f; - expected.hdrCapacityMin = 1.0f; - expected.hdrCapacityMax = expected.maxContentBoost; + expected.offset_sdr = 0.0f; + expected.offset_hdr = 0.0f; + expected.hdr_capacity_min = 1.0f; + expected.hdr_capacity_max = expected.max_content_boost; - gain_map_metadata metadata; - gain_map_metadata::gainmapMetadataFloatToFraction(&expected, &metadata); + uhdr_gainmap_metadata_frac metadata; + uhdr_gainmap_metadata_frac::gainmapMetadataFloatToFraction(&expected, &metadata); // metadata.dump(); std::vector data; - gain_map_metadata::encodeGainmapMetadata(&metadata, data); + uhdr_gainmap_metadata_frac::encodeGainmapMetadata(&metadata, data); - gain_map_metadata decodedMetadata; - gain_map_metadata::decodeGainmapMetadata(data, &decodedMetadata); + uhdr_gainmap_metadata_frac decodedMetadata; + uhdr_gainmap_metadata_frac::decodeGainmapMetadata(data, &decodedMetadata); - ultrahdr_metadata_struct decodedUHdrMetadata; - gain_map_metadata::gainmapMetadataFractionToFloat(&decodedMetadata, &decodedUHdrMetadata); + uhdr_gainmap_metadata_ext_t decodedUHdrMetadata; + uhdr_gainmap_metadata_frac::gainmapMetadataFractionToFloat(&decodedMetadata, + &decodedUHdrMetadata); - EXPECT_EQ(expected.maxContentBoost, decodedUHdrMetadata.maxContentBoost); - EXPECT_EQ(expected.minContentBoost, decodedUHdrMetadata.minContentBoost); + EXPECT_EQ(expected.max_content_boost, decodedUHdrMetadata.max_content_boost); + EXPECT_EQ(expected.min_content_boost, decodedUHdrMetadata.min_content_boost); EXPECT_EQ(expected.gamma, decodedUHdrMetadata.gamma); - EXPECT_EQ(expected.offsetSdr, decodedUHdrMetadata.offsetSdr); - EXPECT_EQ(expected.offsetHdr, decodedUHdrMetadata.offsetHdr); - EXPECT_EQ(expected.hdrCapacityMin, decodedUHdrMetadata.hdrCapacityMin); - EXPECT_EQ(expected.hdrCapacityMax, decodedUHdrMetadata.hdrCapacityMax); + EXPECT_EQ(expected.offset_sdr, decodedUHdrMetadata.offset_sdr); + EXPECT_EQ(expected.offset_hdr, decodedUHdrMetadata.offset_hdr); + EXPECT_EQ(expected.hdr_capacity_min, decodedUHdrMetadata.hdr_capacity_min); + EXPECT_EQ(expected.hdr_capacity_max, decodedUHdrMetadata.hdr_capacity_max); } } // namespace ultrahdr diff --git a/tests/icchelper_test.cpp b/tests/icchelper_test.cpp index 26f78a6..2f71afe 100644 --- a/tests/icchelper_test.cpp +++ b/tests/icchelper_test.cpp @@ -39,31 +39,26 @@ void IccHelperTest::SetUp() {} void IccHelperTest::TearDown() {} TEST_F(IccHelperTest, iccWriteThenRead) { - std::shared_ptr iccBt709 = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); + std::shared_ptr iccBt709 = IccHelper::writeIccProfile(UHDR_CT_SRGB, UHDR_CG_BT_709); ASSERT_NE(iccBt709->getLength(), 0); ASSERT_NE(iccBt709->getData(), nullptr); EXPECT_EQ(IccHelper::readIccColorGamut(iccBt709->getData(), iccBt709->getLength()), - ULTRAHDR_COLORGAMUT_BT709); + UHDR_CG_BT_709); - std::shared_ptr iccP3 = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_P3); + std::shared_ptr iccP3 = IccHelper::writeIccProfile(UHDR_CT_SRGB, UHDR_CG_DISPLAY_P3); ASSERT_NE(iccP3->getLength(), 0); ASSERT_NE(iccP3->getData(), nullptr); - EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()), - ULTRAHDR_COLORGAMUT_P3); + EXPECT_EQ(IccHelper::readIccColorGamut(iccP3->getData(), iccP3->getLength()), UHDR_CG_DISPLAY_P3); - std::shared_ptr iccBt2100 = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT2100); + std::shared_ptr iccBt2100 = IccHelper::writeIccProfile(UHDR_CT_SRGB, UHDR_CG_BT_2100); ASSERT_NE(iccBt2100->getLength(), 0); ASSERT_NE(iccBt2100->getData(), nullptr); EXPECT_EQ(IccHelper::readIccColorGamut(iccBt2100->getData(), iccBt2100->getLength()), - ULTRAHDR_COLORGAMUT_BT2100); + UHDR_CG_BT_2100); } TEST_F(IccHelperTest, iccEndianness) { - std::shared_ptr icc = - IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709); + std::shared_ptr icc = IccHelper::writeIccProfile(UHDR_CT_SRGB, UHDR_CG_BT_709); size_t profile_size = icc->getLength() - kICCIdentifierSize; uint8_t* icc_bytes = reinterpret_cast(icc->getData()) + kICCIdentifierSize; diff --git a/tests/jpegdecoderhelper_test.cpp b/tests/jpegdecoderhelper_test.cpp index cde8ff8..b34ce2e 100644 --- a/tests/jpegdecoderhelper_test.cpp +++ b/tests/jpegdecoderhelper_test.cpp @@ -101,47 +101,56 @@ void JpegDecoderHelperTest::TearDown() {} TEST_F(JpegDecoderHelperTest, decodeYuvImage) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size)); + EXPECT_EQ(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size).error_code, + UHDR_CODEC_OK); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_UNSPECIFIED); + UHDR_CG_UNSPECIFIED); } TEST_F(JpegDecoderHelperTest, decodeYuvImageToRgba) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size, DECODE_TO_RGB_CS)); + EXPECT_EQ( + decoder.decompressImage(mYuvImage.buffer.get(), mYuvImage.size, DECODE_TO_RGB_CS).error_code, + UHDR_CODEC_OK); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_UNSPECIFIED); + UHDR_CG_UNSPECIFIED); } TEST_F(JpegDecoderHelperTest, decodeYuvIccImage) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size)); + EXPECT_EQ(decoder.decompressImage(mYuvIccImage.buffer.get(), mYuvIccImage.size).error_code, + UHDR_CODEC_OK); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_BT709); + UHDR_CG_BT_709); } TEST_F(JpegDecoderHelperTest, decodeGreyImage) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size)); + EXPECT_EQ(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size).error_code, + UHDR_CODEC_OK); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); - EXPECT_TRUE(decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size, DECODE_STREAM)); + EXPECT_EQ( + decoder.decompressImage(mGreyImage.buffer.get(), mGreyImage.size, DECODE_STREAM).error_code, + UHDR_CODEC_OK); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); } TEST_F(JpegDecoderHelperTest, decodeRgbImageToRgba) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.decompressImage(mRgbImage.buffer.get(), mRgbImage.size, DECODE_STREAM)); + EXPECT_EQ( + decoder.decompressImage(mRgbImage.buffer.get(), mRgbImage.size, DECODE_STREAM).error_code, + UHDR_CODEC_OK); ASSERT_GT(decoder.getDecompressedImageSize(), static_cast(0)); EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_UNSPECIFIED); + UHDR_CG_UNSPECIFIED); } TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.parseImage(mYuvImage.buffer.get(), mYuvImage.size)); + EXPECT_EQ(decoder.parseImage(mYuvImage.buffer.get(), mYuvImage.size).error_code, UHDR_CODEC_OK); EXPECT_EQ(IMAGE_WIDTH, decoder.getDecompressedImageWidth()); EXPECT_EQ(IMAGE_HEIGHT, decoder.getDecompressedImageHeight()); EXPECT_EQ(decoder.getICCSize(), 0); @@ -150,13 +159,14 @@ TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) { TEST_F(JpegDecoderHelperTest, getCompressedImageParametersIcc) { JpegDecoderHelper decoder; - EXPECT_TRUE(decoder.parseImage(mYuvIccImage.buffer.get(), mYuvIccImage.size)); + EXPECT_EQ(decoder.parseImage(mYuvIccImage.buffer.get(), mYuvIccImage.size).error_code, + UHDR_CODEC_OK); EXPECT_EQ(IMAGE_WIDTH, decoder.getDecompressedImageWidth()); EXPECT_EQ(IMAGE_HEIGHT, decoder.getDecompressedImageHeight()); EXPECT_GT(decoder.getICCSize(), 0); EXPECT_GT(decoder.getEXIFSize(), 0); EXPECT_EQ(IccHelper::readIccColorGamut(decoder.getICCPtr(), decoder.getICCSize()), - ULTRAHDR_COLORGAMUT_BT709); + UHDR_CG_BT_709); } } // namespace ultrahdr diff --git a/tests/jpegencoderhelper_test.cpp b/tests/jpegencoderhelper_test.cpp index c97e216..703d085 100644 --- a/tests/jpegencoderhelper_test.cpp +++ b/tests/jpegencoderhelper_test.cpp @@ -20,7 +20,6 @@ #include #include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/ultrahdr.h" #include "ultrahdr/jpegencoderhelper.h" namespace ultrahdr { @@ -110,8 +109,11 @@ TEST_F(JpegEncoderHelperTest, encodeAlignedImage) { const uint8_t* vPlane = uPlane + mAlignedImage.width * mAlignedImage.height / 4; const uint8_t* planes[3]{yPlane, uPlane, vPlane}; const size_t strides[3]{mAlignedImage.width, mAlignedImage.width / 2, mAlignedImage.width / 2}; - EXPECT_TRUE(encoder.compressImage(planes, strides, mAlignedImage.width, mAlignedImage.height, - UHDR_IMG_FMT_12bppYCbCr420, JPEG_QUALITY, NULL, 0)); + EXPECT_EQ(encoder + .compressImage(planes, strides, mAlignedImage.width, mAlignedImage.height, + UHDR_IMG_FMT_12bppYCbCr420, JPEG_QUALITY, NULL, 0) + .error_code, + UHDR_CODEC_OK); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } @@ -123,8 +125,11 @@ TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) { const uint8_t* planes[3]{yPlane, uPlane, vPlane}; const size_t strides[3]{mUnalignedImage.width, mUnalignedImage.width / 2, mUnalignedImage.width / 2}; - EXPECT_TRUE(encoder.compressImage(planes, strides, mUnalignedImage.width, mUnalignedImage.height, - UHDR_IMG_FMT_12bppYCbCr420, JPEG_QUALITY, NULL, 0)); + EXPECT_EQ(encoder + .compressImage(planes, strides, mUnalignedImage.width, mUnalignedImage.height, + UHDR_IMG_FMT_12bppYCbCr420, JPEG_QUALITY, NULL, 0) + .error_code, + UHDR_CODEC_OK); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } @@ -133,9 +138,12 @@ TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) { const uint8_t* yPlane = mSingleChannelImage.buffer.get(); const uint8_t* planes[1]{yPlane}; const size_t strides[1]{mSingleChannelImage.width}; - EXPECT_TRUE(encoder.compressImage(planes, strides, mSingleChannelImage.width, - mSingleChannelImage.height, UHDR_IMG_FMT_8bppYCbCr400, - JPEG_QUALITY, NULL, 0)); + EXPECT_EQ( + encoder + .compressImage(planes, strides, mSingleChannelImage.width, mSingleChannelImage.height, + UHDR_IMG_FMT_8bppYCbCr400, JPEG_QUALITY, NULL, 0) + .error_code, + UHDR_CODEC_OK); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } @@ -144,8 +152,11 @@ TEST_F(JpegEncoderHelperTest, encodeRGBImage) { const uint8_t* rgbPlane = mRgbImage.buffer.get(); const uint8_t* planes[1]{rgbPlane}; const size_t strides[1]{mRgbImage.width}; - EXPECT_TRUE(encoder.compressImage(planes, strides, mRgbImage.width, mRgbImage.height, - UHDR_IMG_FMT_24bppRGB888, JPEG_QUALITY, NULL, 0)); + EXPECT_EQ(encoder + .compressImage(planes, strides, mRgbImage.width, mRgbImage.height, + UHDR_IMG_FMT_24bppRGB888, JPEG_QUALITY, NULL, 0) + .error_code, + UHDR_CODEC_OK); ASSERT_GT(encoder.getCompressedImageSize(), static_cast(0)); } diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp index 394c548..18e407b 100644 --- a/tests/jpegr_test.cpp +++ b/tests/jpegr_test.cpp @@ -1399,15 +1399,15 @@ TEST(JpegRTest, DecodeAPIWithInvalidArgs) { } TEST(JpegRTest, writeXmpThenRead) { - ultrahdr_metadata_struct metadata_expected; + uhdr_gainmap_metadata_ext_t metadata_expected; metadata_expected.version = "1.0"; - metadata_expected.maxContentBoost = 1.25f; - metadata_expected.minContentBoost = 0.75f; + metadata_expected.max_content_boost = 1.25f; + metadata_expected.min_content_boost = 0.75f; metadata_expected.gamma = 1.0f; - metadata_expected.offsetSdr = 0.0f; - metadata_expected.offsetHdr = 0.0f; - metadata_expected.hdrCapacityMin = 1.0f; - metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost; + metadata_expected.offset_sdr = 0.0f; + metadata_expected.offset_hdr = 0.0f; + metadata_expected.hdr_capacity_min = 1.0f; + metadata_expected.hdr_capacity_max = metadata_expected.max_content_boost; const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0"; const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator @@ -1420,15 +1420,16 @@ TEST(JpegRTest, writeXmpThenRead) { xmpData.insert(xmpData.end(), reinterpret_cast(xmp.c_str()), reinterpret_cast(xmp.c_str()) + xmp.size()); - ultrahdr_metadata_struct metadata_read; - EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read)); - EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost); - EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost); + uhdr_gainmap_metadata_ext_t metadata_read; + EXPECT_EQ(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read).error_code, + UHDR_CODEC_OK); + EXPECT_FLOAT_EQ(metadata_expected.max_content_boost, metadata_read.max_content_boost); + EXPECT_FLOAT_EQ(metadata_expected.min_content_boost, metadata_read.min_content_boost); EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma); - EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr); - EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr); - EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin); - EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax); + EXPECT_FLOAT_EQ(metadata_expected.offset_sdr, metadata_read.offset_sdr); + EXPECT_FLOAT_EQ(metadata_expected.offset_hdr, metadata_read.offset_hdr); + EXPECT_FLOAT_EQ(metadata_expected.hdr_capacity_min, metadata_read.hdr_capacity_min); + EXPECT_FLOAT_EQ(metadata_expected.hdr_capacity_max, metadata_read.hdr_capacity_max); } class JpegRAPIEncodeAndDecodeTest @@ -2157,7 +2158,7 @@ INSTANTIATE_TEST_SUITE_P( ULTRAHDR_COLORGAMUT_BT2100), ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3, ULTRAHDR_COLORGAMUT_BT2100))); - +#if 0 // ============================================================================ // Profiling // ============================================================================ @@ -2304,5 +2305,6 @@ TEST(JpegRTest, ProfileGainMapFuncs) { map.data = nullptr; } } +#endif } // namespace ultrahdr diff --git a/ultrahdr_api.h b/ultrahdr_api.h index f66ce51..5e2f8a8 100644 --- a/ultrahdr_api.h +++ b/ultrahdr_api.h @@ -119,6 +119,9 @@ typedef enum uhdr_codec_err { /*!\brief Operation completed without error */ UHDR_CODEC_OK, + /*!\brief Generic codec error, refer detail field for description */ + UHDR_CODEC_ERROR, + /*!\brief Unspecified error */ UHDR_CODEC_UNKNOWN_ERROR,