diff options
author | Ram Mohan M <ram.mohan@ittiam.com> | 2024-03-19 04:53:42 +0530 |
---|---|---|
committer | Ram Mohan M <ram.mohan@ittiam.com> | 2024-03-19 04:54:03 +0530 |
commit | 63359d26be78fc80f422f0aad722047e02ada7dd (patch) | |
tree | 538e437cf0f6d9dd2c111e924c3bb1b1afacfdb9 | |
parent | a463f1210389cd724889229b86fa61edbb19b016 (diff) | |
download | libultrahdr-63359d26be78fc80f422f0aad722047e02ada7dd.tar.gz |
Add C compatible interface for libultrahdr library
Updated sample application and unit tests for new interface
Test: ./ultrahdr_unit_test
Co-authored-by: Aayush Soni <aayush.soni@ittiam.com>
Change-Id: I99d8ada4b0850630bc14a1c33a764a00e6f09e9f
-rw-r--r-- | Android.bp | 6 | ||||
-rw-r--r-- | CMakeLists.txt | 3 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | examples/ultrahdr_app.cpp | 770 | ||||
-rw-r--r-- | lib/include/ultrahdr/jpegdecoderhelper.h | 4 | ||||
-rw-r--r-- | lib/include/ultrahdr/ultrahdrcommon.h | 85 | ||||
-rw-r--r-- | lib/src/ultrahdr_api.cpp | 1146 | ||||
-rw-r--r-- | tests/jpegr_test.cpp | 235 | ||||
-rw-r--r-- | ultrahdr_api.h | 515 |
9 files changed, 2439 insertions, 327 deletions
@@ -35,7 +35,10 @@ cc_library { name: "libultrahdr", host_supported: true, vendor_available: true, - export_include_dirs: ["lib/include"], + export_include_dirs: [ + ".", + "lib/include", + ], local_include_dirs: ["lib/include"], srcs: [ @@ -44,6 +47,7 @@ cc_library { "lib/src/gainmapmath.cpp", "lib/src/jpegrutils.cpp", "lib/src/multipictureformat.cpp", + "lib/src/ultrahdr_api.cpp", ], shared_libs: [ diff --git a/CMakeLists.txt b/CMakeLists.txt index d576f12..4992b6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,7 @@ if(MSVC) add_compile_options(/wd4267) # conversion from 'size_t' to 'type' possible loss of data add_compile_options(/wd4305) # truncation from 'double' to 'float' add_compile_options(/wd4838) # conversion from 'type1' to 'type2' requires a narrowing conversion + add_compile_options(/wd26812) # Prefer enum class over enum else() add_compile_options(-ffunction-sections) add_compile_options(-fdata-sections) @@ -299,7 +300,7 @@ file(GLOB UHDR_TEST_LIST "${TESTS_DIR}/*.cpp") file(GLOB UHDR_BM_LIST "${BENCHMARK_DIR}/*.cpp") file(GLOB IMAGE_IO_LIST "${THIRD_PARTY_DIR}/image_io/src/**/*.cc") -set(COMMON_INCLUDE_LIST ${SOURCE_DIR}/include/ ${JPEG_INCLUDE_DIRS}) +set(COMMON_INCLUDE_LIST ${CMAKE_SOURCE_DIR} ${SOURCE_DIR}/include/ ${JPEG_INCLUDE_DIRS}) set(COMMON_LIBS_LIST ${JPEG_LIBRARIES} Threads::Threads) ########################################################### @@ -162,5 +162,5 @@ List of decode API: | metadata | (optional, default to NULL) Destination of metadata (gain map version, min/max content boost). | For more info: -- Refer to [jpegr.h](lib/include/ultrahdr/jpegr.h) for detailed description of various encode and decode api. +- Refer to [ultrahdr_api.h](ultrahdr_api.h) for detailed description of various encode and decode api. - Refer to [ultrahdr_app.cpp](examples/ultrahdr_app.cpp) for examples of its usage. diff --git a/examples/ultrahdr_app.cpp b/examples/ultrahdr_app.cpp index 20b9329..c4e9c5b 100644 --- a/examples/ultrahdr_app.cpp +++ b/examples/ultrahdr_app.cpp @@ -24,14 +24,11 @@ #include <algorithm> #include <cmath> +#include <cstdint> #include <fstream> #include <iostream> -#include "ultrahdr/ultrahdrcommon.h" -#include "ultrahdr/gainmapmath.h" -#include "ultrahdr/jpegr.h" - -using namespace ultrahdr; +#include "ultrahdr_api.h" const float BT601YUVtoRGBMatrix[9] = { 1, 0, 1.402, 1, (-0.202008 / 0.587), (-0.419198 / 0.587), 1.0, 1.772, 0.0}; @@ -62,6 +59,10 @@ const float BT2020RGBtoYUVMatrix[9] = {0.2627, (-0.6780 / 1.4746), (-0.0593 / 1.4746)}; +// remove these once introduced in ultrahdr_api.h +const int UHDR_IMG_FMT_24bppYCbCr444 = 100; +const int UHDR_IMG_FMT_48bppYCbCr444 = 101; + int optind_s = 1; int optopt_s = 0; char* optarg_s = nullptr; @@ -158,6 +159,26 @@ static bool loadFile(const char* filename, void*& result, int length) { return false; } +static bool loadFile(const char* filename, uhdr_raw_image_t* handle) { + std::ifstream ifd(filename, std::ios::binary); + if (ifd.good()) { + if (handle->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + const int bpp = 2; + ifd.read(static_cast<char*>(handle->planes[0]), handle->w * handle->h * bpp); + ifd.read(static_cast<char*>(handle->planes[1]), (handle->w / 2) * (handle->h / 2) * bpp * 2); + return true; + } else if (handle->fmt == UHDR_IMG_FMT_12bppYCbCr420) { + ifd.read(static_cast<char*>(handle->planes[0]), handle->w * handle->h); + ifd.read(static_cast<char*>(handle->planes[1]), (handle->w / 2) * (handle->h / 2)); + ifd.read(static_cast<char*>(handle->planes[2]), (handle->w / 2) * (handle->h / 2)); + return true; + } + return false; + } + std::cerr << "unable to open file : " << filename << std::endl; + return false; +} + static bool writeFile(const char* filename, void*& result, int length) { std::ofstream ofd(filename, std::ios::binary); if (ofd.is_open()) { @@ -168,14 +189,54 @@ static bool writeFile(const char* filename, void*& result, int length) { return false; } +static bool writeFile(const char* filename, uhdr_raw_image_t* img) { + std::ofstream ofd(filename, std::ios::binary); + if (ofd.is_open()) { + if (img->fmt == UHDR_IMG_FMT_32bppRGBA8888 || img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat || + img->fmt == UHDR_IMG_FMT_32bppRGBA1010102) { + char* data = static_cast<char*>(img->planes[0]); + int bpp = img->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat ? 8 : 4; + const size_t stride = img->stride[0] * bpp; + const size_t length = img->w * bpp; + for (unsigned i = 0; i < img->h; i++, data += stride) { + ofd.write(data, length); + } + return true; + } else if ((int)img->fmt == UHDR_IMG_FMT_24bppYCbCr444 || + (int)img->fmt == UHDR_IMG_FMT_48bppYCbCr444) { + char* data = static_cast<char*>(img->planes[0]); + int bpp = (int)img->fmt == UHDR_IMG_FMT_48bppYCbCr444 ? 2 : 1; + size_t stride = img->stride[0] * bpp; + size_t length = img->w * bpp; + for (unsigned i = 0; i < img->h; i++, data += stride) { + ofd.write(data, length); + } + data = static_cast<char*>(img->planes[1]); + stride = img->stride[1] * bpp; + for (unsigned i = 0; i < img->h; i++, data += stride) { + ofd.write(data, length); + } + data = static_cast<char*>(img->planes[2]); + stride = img->stride[2] * bpp; + for (unsigned i = 0; i < img->h; i++, data += stride) { + ofd.write(data, length); + } + return true; + } + return false; + } + std::cerr << "unable to write to file : " << filename << std::endl; + return false; +} + class UltraHdrAppInput { public: UltraHdrAppInput(const char* p010File, const char* yuv420File, const char* yuv420JpegFile, - size_t width, size_t height, - ultrahdr_color_gamut p010Cg = ULTRAHDR_COLORGAMUT_BT709, - ultrahdr_color_gamut yuv420Cg = ULTRAHDR_COLORGAMUT_BT709, - ultrahdr_transfer_function tf = ULTRAHDR_TF_HLG, int quality = 100, - ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG) + size_t width, size_t height, uhdr_color_gamut_t p010Cg = UHDR_CG_BT_709, + uhdr_color_gamut_t yuv420Cg = UHDR_CG_BT_709, + uhdr_color_transfer_t p010Tf = UHDR_CT_HLG, int quality = 100, + uhdr_color_transfer_t oTf = UHDR_CT_HLG, + uhdr_img_fmt_t oFmt = UHDR_IMG_FMT_32bppRGBA1010102) : mP010File(p010File), mYuv420File(yuv420File), mYuv420JpegFile(yuv420JpegFile), @@ -184,38 +245,56 @@ class UltraHdrAppInput { mHeight(height), mP010Cg(p010Cg), mYuv420Cg(yuv420Cg), - mTf(tf), + mP010Tf(p010Tf), mQuality(quality), - mOf(of), + mOTf(oTf), + mOfmt(oFmt), mMode(0){}; - UltraHdrAppInput(const char* jpegRFile, ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG) + UltraHdrAppInput(const char* jpegRFile, uhdr_color_transfer_t oTf = UHDR_CT_HLG, + uhdr_img_fmt_t oFmt = UHDR_IMG_FMT_32bppRGBA1010102) : mP010File(nullptr), mYuv420File(nullptr), mJpegRFile(jpegRFile), mWidth(0), mHeight(0), - mP010Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED), - mYuv420Cg(ULTRAHDR_COLORGAMUT_UNSPECIFIED), - mTf(ULTRAHDR_TF_UNSPECIFIED), + mP010Cg(UHDR_CG_UNSPECIFIED), + mYuv420Cg(UHDR_CG_UNSPECIFIED), + mP010Tf(UHDR_CT_UNSPECIFIED), mQuality(100), - mOf(of), + mOTf(oTf), + mOfmt(oFmt), mMode(1){}; ~UltraHdrAppInput() { - if (mRawP010Image.data) free(mRawP010Image.data); - if (mRawP010Image.chroma_data) free(mRawP010Image.chroma_data); - if (mRawRgba1010102Image.data) free(mRawRgba1010102Image.data); - if (mRawRgba1010102Image.chroma_data) free(mRawRgba1010102Image.chroma_data); - if (mRawYuv420Image.data) free(mRawYuv420Image.data); - if (mRawYuv420Image.chroma_data) free(mRawYuv420Image.chroma_data); - if (mRawRgba8888Image.data) free(mRawRgba8888Image.data); - if (mRawRgba8888Image.chroma_data) free(mRawRgba8888Image.chroma_data); + int count = sizeof mRawP010Image.planes / sizeof mRawP010Image.planes[0]; + for (int i = 0; i < count; i++) { + if (mRawP010Image.planes[i]) { + free(mRawP010Image.planes[i]); + mRawP010Image.planes[i] = nullptr; + } + if (mRawRgba1010102Image.planes[i]) { + free(mRawRgba1010102Image.planes[i]); + mRawRgba1010102Image.planes[i] = nullptr; + } + if (mRawYuv420Image.planes[i]) { + free(mRawYuv420Image.planes[i]); + mRawYuv420Image.planes[i] = nullptr; + } + if (mRawRgba8888Image.planes[i]) { + free(mRawRgba8888Image.planes[i]); + mRawRgba8888Image.planes[i] = nullptr; + } + if (mDestImage.planes[i]) { + free(mDestImage.planes[i]); + mDestImage.planes[i] = nullptr; + } + if (mDestYUV444Image.planes[i]) { + free(mDestYUV444Image.planes[i]); + mDestYUV444Image.planes[i] = nullptr; + } + } if (mJpegImgR.data) free(mJpegImgR.data); - if (mDestImage.data) free(mDestImage.data); - if (mDestImage.chroma_data) free(mDestImage.chroma_data); - if (mDestYUV444Image.data) free(mDestYUV444Image.data); - if (mDestYUV444Image.chroma_data) free(mDestYUV444Image.chroma_data); } bool fillJpegRImageHandle(); @@ -239,48 +318,70 @@ class UltraHdrAppInput { const char* mJpegRFile; const int mWidth; const int mHeight; - const ultrahdr_color_gamut mP010Cg; - const ultrahdr_color_gamut mYuv420Cg; - const ultrahdr_transfer_function mTf; + const uhdr_color_gamut_t mP010Cg; + const uhdr_color_gamut_t mYuv420Cg; + const uhdr_color_transfer_t mP010Tf; const int mQuality; - const ultrahdr_output_format mOf; + const uhdr_color_transfer_t mOTf; + const uhdr_img_fmt mOfmt; const int mMode; - jpegr_uncompressed_struct mRawP010Image{}; - jpegr_uncompressed_struct mRawRgba1010102Image{}; - jpegr_uncompressed_struct mRawYuv420Image{}; - jpegr_compressed_struct mYuv420JpegImage{}; - jpegr_uncompressed_struct mRawRgba8888Image{}; - jpegr_compressed_struct mJpegImgR{}; - jpegr_uncompressed_struct mDestImage{}; - jpegr_uncompressed_struct mDestYUV444Image{}; + + uhdr_raw_image_t mRawP010Image{}; + uhdr_raw_image_t mRawRgba1010102Image{}; + uhdr_raw_image_t mRawYuv420Image{}; + uhdr_compressed_image_t mYuv420JpegImage{}; + uhdr_raw_image_t mRawRgba8888Image{}; + uhdr_compressed_image_t mJpegImgR{}; + uhdr_raw_image_t mDestImage{}; + uhdr_raw_image_t mDestYUV444Image{}; double mPsnr[3]{}; }; bool UltraHdrAppInput::fillP010ImageHandle() { const int bpp = 2; int p010Size = mWidth * mHeight * bpp * 1.5; - mRawP010Image.width = mWidth; - mRawP010Image.height = mHeight; - mRawP010Image.colorGamut = mP010Cg; - return loadFile(mP010File, mRawP010Image.data, p010Size); + mRawP010Image.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + mRawP010Image.cg = mP010Cg; + mRawP010Image.ct = mP010Tf; + mRawP010Image.range = UHDR_CR_LIMITED_RANGE; + mRawP010Image.w = mWidth; + mRawP010Image.h = mHeight; + mRawP010Image.planes[0] = malloc(mWidth * mHeight * bpp); + mRawP010Image.planes[1] = malloc((mWidth / 2) * (mHeight / 2) * bpp * 2); + mRawP010Image.planes[2] = nullptr; + mRawP010Image.stride[0] = mWidth; + mRawP010Image.stride[1] = mWidth; + mRawP010Image.stride[2] = 0; + return loadFile(mP010File, &mRawP010Image); } bool UltraHdrAppInput::fillYuv420ImageHandle() { int yuv420Size = mWidth * mHeight * 1.5; - mRawYuv420Image.width = mWidth; - mRawYuv420Image.height = mHeight; - mRawYuv420Image.colorGamut = mYuv420Cg; - return loadFile(mYuv420File, mRawYuv420Image.data, yuv420Size); + mRawYuv420Image.fmt = UHDR_IMG_FMT_12bppYCbCr420; + mRawYuv420Image.cg = mYuv420Cg; + mRawYuv420Image.ct = UHDR_CT_SRGB; + mRawYuv420Image.range = UHDR_CR_FULL_RANGE; + mRawYuv420Image.w = mWidth; + mRawYuv420Image.h = mHeight; + mRawYuv420Image.planes[0] = malloc(mWidth * mHeight); + mRawYuv420Image.planes[1] = malloc((mWidth / 2) * (mHeight / 2)); + mRawYuv420Image.planes[2] = malloc((mWidth / 2) * (mHeight / 2)); + mRawYuv420Image.stride[0] = mWidth; + mRawYuv420Image.stride[1] = mWidth / 2; + mRawYuv420Image.stride[2] = mWidth / 2; + return loadFile(mYuv420File, &mRawYuv420Image); } bool UltraHdrAppInput::fillYuv420JpegImageHandle() { std::ifstream ifd(mYuv420JpegFile, std::ios::binary | std::ios::ate); if (ifd.good()) { int size = ifd.tellg(); - mYuv420JpegImage.length = size; - mYuv420JpegImage.maxLength = size; + mYuv420JpegImage.capacity = size; + mYuv420JpegImage.data_sz = size; mYuv420JpegImage.data = nullptr; - mYuv420JpegImage.colorGamut = mYuv420Cg; + mYuv420JpegImage.cg = mYuv420Cg; + mYuv420JpegImage.ct = UHDR_CT_UNSPECIFIED; + mYuv420JpegImage.range = UHDR_CR_UNSPECIFIED; ifd.close(); return loadFile(mYuv420JpegFile, mYuv420JpegImage.data, size); } @@ -291,10 +392,12 @@ bool UltraHdrAppInput::fillJpegRImageHandle() { std::ifstream ifd(mJpegRFile, std::ios::binary | std::ios::ate); if (ifd.good()) { int size = ifd.tellg(); - mJpegImgR.length = size; - mJpegImgR.maxLength = size; + mJpegImgR.capacity = size; + mJpegImgR.data_sz = size; mJpegImgR.data = nullptr; - mJpegImgR.colorGamut = mYuv420Cg; + mYuv420JpegImage.cg = UHDR_CG_UNSPECIFIED; + mYuv420JpegImage.ct = UHDR_CT_UNSPECIFIED; + mYuv420JpegImage.range = UHDR_CR_UNSPECIFIED; ifd.close(); return loadFile(mJpegRFile, mJpegImgR.data, size); } @@ -302,140 +405,152 @@ bool UltraHdrAppInput::fillJpegRImageHandle() { } bool UltraHdrAppInput::encode() { - if (!fillP010ImageHandle()) return false; - if (mYuv420File != nullptr && !fillYuv420ImageHandle()) return false; - if (mYuv420JpegFile != nullptr && !fillYuv420JpegImageHandle()) return false; - - mJpegImgR.maxLength = (std::max)(static_cast<size_t>(8 * 1024) /* min size 8kb */, - mRawP010Image.width * mRawP010Image.height * 3 * 2); - mJpegImgR.data = malloc(mJpegImgR.maxLength); - if (mJpegImgR.data == nullptr) { - std::cerr << "unable to allocate memory to store compressed image" << std::endl; - return false; +#define CHECK_ERR(x) \ + { \ + uhdr_error_info_t status = (x); \ + if (status.error_code != UHDR_CODEC_OK) { \ + if (status.has_detail) { \ + std::cerr << status.detail << std::endl; \ + } \ + return false; \ + } \ } - JpegR jpegHdr; - status_t status = JPEGR_UNKNOWN_ERROR; + uhdr_codec_private_t* handle = uhdr_create_encoder(); + if (mP010File != nullptr) { + if (!fillP010ImageHandle()) { + std::cerr << " failed to load file " << mP010File << std::endl; + return false; + } + CHECK_ERR(uhdr_enc_set_raw_image(handle, &mRawP010Image, UHDR_HDR_IMG)) + } + if (mYuv420File != nullptr) { + if (!fillYuv420ImageHandle()) { + std::cerr << " failed to load file " << mYuv420File << std::endl; + return false; + } + CHECK_ERR(uhdr_enc_set_raw_image(handle, &mRawYuv420Image, UHDR_SDR_IMG)) + } + if (mYuv420JpegFile != nullptr) { + if (!fillYuv420JpegImageHandle()) { + std::cerr << " failed to load file " << mYuv420JpegFile << std::endl; + return false; + } + CHECK_ERR(uhdr_enc_set_compressed_image(handle, &mYuv420JpegImage, UHDR_SDR_IMG)) + } + CHECK_ERR(uhdr_enc_set_quality(handle, mQuality, UHDR_BASE_IMG)) #ifdef PROFILE_ENABLE const int profileCount = 10; Profiler profileEncode; profileEncode.timerStart(); for (auto i = 0; i < profileCount; i++) { #endif - if (mYuv420File == nullptr && mYuv420JpegFile == nullptr) { // api-0 - status = jpegHdr.encodeJPEGR(&mRawP010Image, mTf, &mJpegImgR, mQuality, nullptr); - if (JPEGR_NO_ERROR != status) { - std::cerr << "Encountered error during encodeJPEGR call, error code " << status - << std::endl; - return false; - } - } else if (mYuv420File != nullptr && mYuv420JpegFile == nullptr) { // api-1 - status = - jpegHdr.encodeJPEGR(&mRawP010Image, &mRawYuv420Image, mTf, &mJpegImgR, mQuality, nullptr); - if (JPEGR_NO_ERROR != status) { - std::cerr << "Encountered error during encodeJPEGR call, error code " << status - << std::endl; - return false; - } - } else if (mYuv420File != nullptr && mYuv420JpegFile != nullptr) { // api-2 - status = - jpegHdr.encodeJPEGR(&mRawP010Image, &mRawYuv420Image, &mYuv420JpegImage, mTf, &mJpegImgR); - if (JPEGR_NO_ERROR != status) { - std::cerr << "Encountered error during encodeJPEGR call, error code " << status - << std::endl; - return false; - } - } else if (mYuv420File == nullptr && mYuv420JpegFile != nullptr) { // api-3 - status = jpegHdr.encodeJPEGR(&mRawP010Image, &mYuv420JpegImage, mTf, &mJpegImgR); - if (JPEGR_NO_ERROR != status) { - std::cerr << "Encountered error during encodeJPEGR call, error code " << status - << std::endl; - return false; - } - } + CHECK_ERR(uhdr_encode(handle)) #ifdef PROFILE_ENABLE } profileEncode.timerStop(); auto avgEncTime = profileEncode.elapsedTime() / (profileCount * 1000.f); printf("Average encode time for res %d x %d is %f ms \n", mWidth, mHeight, avgEncTime); #endif - writeFile("out.jpeg", mJpegImgR.data, mJpegImgR.length); + auto output = uhdr_get_encoded_stream(handle); + + // for decoding + mJpegImgR.data = malloc(output->data_sz); + memcpy(mJpegImgR.data, output->data, output->data_sz); + mJpegImgR.capacity = mJpegImgR.data_sz = output->data_sz; + mJpegImgR.cg = output->cg; + mJpegImgR.ct = output->ct; + mJpegImgR.range = output->range; + writeFile("out.jpeg", output->data, output->data_sz); + + uhdr_release_encoder(handle); return true; } bool UltraHdrAppInput::decode() { - if (mMode == 1 && !fillJpegRImageHandle()) return false; - std::vector<uint8_t> iccData(0); - std::vector<uint8_t> exifData(0); - jpegr_info_struct info{}; - JpegR jpegHdr; - status_t status = jpegHdr.getJPEGRInfo(&mJpegImgR, &info); - if (JPEGR_NO_ERROR == status) { - size_t outSize = info.width * info.height * ((mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4); - mDestImage.data = malloc(outSize); - if (mDestImage.data == nullptr) { - std::cerr << "failed to allocate memory to store decoded output" << std::endl; - return false; - } + if (mMode == 1 && !fillJpegRImageHandle()) { + std::cerr << " failed to load file " << mJpegRFile << std::endl; + return false; + } + uhdr_codec_private_t* handle = uhdr_create_decoder(); + CHECK_ERR(uhdr_dec_set_image(handle, &mJpegImgR)) + CHECK_ERR(uhdr_dec_set_out_color_transfer(handle, mOTf)) + CHECK_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++) { + const int profileCount = 10; + Profiler profileDecode; + profileDecode.timerStart(); + for (auto i = 0; i < profileCount; i++) { #endif - status = - jpegHdr.decodeJPEGR(&mJpegImgR, &mDestImage, FLT_MAX, nullptr, mOf, nullptr, nullptr); - if (JPEGR_NO_ERROR != status) { - std::cerr << "Encountered error during decodeJPEGR call, error code " << status - << std::endl; - return false; - } + CHECK_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); + } + 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); #endif - writeFile("outrgb.raw", mDestImage.data, outSize); - } else { - std::cerr << "Encountered error during getJPEGRInfo call, error code " << status << std::endl; - return false; + uhdr_raw_image_t* output = uhdr_get_decoded_image(handle); + + mDestImage.fmt = output->fmt; + mDestImage.cg = output->cg; + mDestImage.ct = output->ct; + mDestImage.range = output->range; + mDestImage.w = output->w; + mDestImage.h = output->h; + int bpp = (output->fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) ? 8 : 4; + mDestImage.planes[0] = malloc(output->w * output->h * bpp); + char* inData = static_cast<char*>(output->planes[0]); + char* outData = static_cast<char*>(mDestImage.planes[0]); + const size_t inStride = output->stride[0] * bpp; + const size_t outStride = output->w * bpp; + mDestImage.stride[0] = output->w; + const size_t length = output->w * bpp; + for (unsigned i = 0; i < output->h; i++, inData += inStride, outData += outStride) { + memcpy(outData, inData, length); } + writeFile("outrgb.raw", output); + uhdr_release_decoder(handle); return true; } +#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x) bool UltraHdrAppInput::convertP010ToRGBImage() { const float* coeffs = BT2020YUVtoRGBMatrix; - if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) { + if (mP010Cg == UHDR_CG_BT_709) { coeffs = BT709YUVtoRGBMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) { + } else if (mP010Cg == UHDR_CG_BT_2100) { coeffs = BT2020YUVtoRGBMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_P3) { + } else if (mP010Cg == UHDR_CG_DISPLAY_P3) { coeffs = BT601YUVtoRGBMatrix; } else { std::cerr << "color matrix not present for gamut " << mP010Cg << " using BT2020Matrix" << std::endl; } - mRawRgba1010102Image.data = malloc(mRawP010Image.width * mRawP010Image.height * 4); - if (mRawRgba1010102Image.data == nullptr) { - std::cerr << "failed to allocate memory to store Rgba1010102" << std::endl; - return false; - } - mRawRgba1010102Image.width = mRawP010Image.width; - mRawRgba1010102Image.height = mRawP010Image.height; - mRawRgba1010102Image.colorGamut = mRawP010Image.colorGamut; - uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba1010102Image.data); - uint16_t* y = static_cast<uint16_t*>(mRawP010Image.data); - uint16_t* u = y + mRawP010Image.width * mRawP010Image.height; + mRawRgba1010102Image.fmt = UHDR_IMG_FMT_32bppRGBA1010102; + mRawRgba1010102Image.cg = mRawP010Image.cg; + mRawRgba1010102Image.ct = mRawP010Image.ct; + mRawRgba1010102Image.range = UHDR_CR_FULL_RANGE; + mRawRgba1010102Image.w = mRawP010Image.w; + mRawRgba1010102Image.h = mRawP010Image.h; + mRawRgba1010102Image.planes[0] = malloc(mRawP010Image.w * mRawP010Image.h * 4); + mRawRgba1010102Image.planes[1] = nullptr; + mRawRgba1010102Image.planes[2] = nullptr; + mRawRgba1010102Image.stride[0] = mWidth; + mRawRgba1010102Image.stride[1] = 0; + mRawRgba1010102Image.stride[2] = 0; + + uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba1010102Image.planes[0]); + uint16_t* y = static_cast<uint16_t*>(mRawP010Image.planes[0]); + uint16_t* u = static_cast<uint16_t*>(mRawP010Image.planes[1]); uint16_t* v = u + 1; - for (size_t i = 0; i < mRawP010Image.height; i++) { - for (size_t j = 0; j < mRawP010Image.width; j++) { - float y0 = float(y[mRawP010Image.width * i + j] >> 6); - float u0 = float(u[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6); - float v0 = float(v[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6); + for (size_t i = 0; i < mRawP010Image.h; i++) { + for (size_t j = 0; j < mRawP010Image.w; j++) { + float y0 = float(y[mRawP010Image.stride[0] * i + j] >> 6); + float u0 = float(u[mRawP010Image.stride[1] * (i / 2) + (j / 2) * 2] >> 6); + float v0 = float(v[mRawP010Image.stride[1] * (i / 2) + (j / 2) * 2] >> 6); y0 = CLIP3(y0, 64.0f, 940.0f); u0 = CLIP3(u0, 64.0f, 960.0f); @@ -462,31 +577,35 @@ bool UltraHdrAppInput::convertP010ToRGBImage() { rgbData++; } } - writeFile("inRgba1010102.raw", mRawRgba1010102Image.data, - mRawP010Image.width * mRawP010Image.height * 4); + writeFile("inRgba1010102.raw", &mRawRgba1010102Image); return true; } bool UltraHdrAppInput::convertYuv420ToRGBImage() { - mRawRgba8888Image.data = malloc(mRawYuv420Image.width * mRawYuv420Image.height * 4); - if (mRawRgba8888Image.data == nullptr) { - std::cerr << "failed to allocate memory to store rgba888" << std::endl; - return false; - } - mRawRgba8888Image.width = mRawYuv420Image.width; - mRawRgba8888Image.height = mRawYuv420Image.height; - mRawRgba8888Image.colorGamut = mRawYuv420Image.colorGamut; - uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba8888Image.data); - uint8_t* y = static_cast<uint8_t*>(mRawYuv420Image.data); - uint8_t* u = y + (mRawYuv420Image.width * mRawYuv420Image.height); - uint8_t* v = u + (mRawYuv420Image.width * mRawYuv420Image.height / 4); + mRawRgba8888Image.fmt = UHDR_IMG_FMT_32bppRGBA8888; + mRawRgba8888Image.cg = mRawYuv420Image.cg; + mRawRgba8888Image.ct = mRawYuv420Image.ct; + mRawRgba8888Image.range = UHDR_CR_FULL_RANGE; + mRawRgba8888Image.w = mRawYuv420Image.w; + mRawRgba8888Image.h = mRawYuv420Image.h; + mRawRgba8888Image.planes[0] = malloc(mRawYuv420Image.w * mRawYuv420Image.h * 4); + mRawRgba8888Image.planes[1] = nullptr; + mRawRgba8888Image.planes[2] = nullptr; + mRawRgba8888Image.stride[0] = mWidth; + mRawRgba8888Image.stride[1] = 0; + mRawRgba8888Image.stride[2] = 0; + + uint32_t* rgbData = static_cast<uint32_t*>(mRawRgba8888Image.planes[0]); + uint8_t* y = static_cast<uint8_t*>(mRawYuv420Image.planes[0]); + uint8_t* u = static_cast<uint8_t*>(mRawYuv420Image.planes[1]); + uint8_t* v = static_cast<uint8_t*>(mRawYuv420Image.planes[2]); const float* coeffs = BT601YUVtoRGBMatrix; - for (size_t i = 0; i < mRawYuv420Image.height; i++) { - for (size_t j = 0; j < mRawYuv420Image.width; j++) { - float y0 = float(y[mRawYuv420Image.width * i + j]); - float u0 = float(u[mRawYuv420Image.width / 2 * (i / 2) + (j / 2)] - 128); - float v0 = float(v[mRawYuv420Image.width / 2 * (i / 2) + (j / 2)] - 128); + for (size_t i = 0; i < mRawYuv420Image.h; i++) { + for (size_t j = 0; j < mRawYuv420Image.w; j++) { + float y0 = float(y[mRawYuv420Image.stride[0] * i + j]); + float u0 = float(u[mRawYuv420Image.stride[1] * (i / 2) + (j / 2)] - 128); + float v0 = float(v[mRawYuv420Image.stride[2] * (i / 2) + (j / 2)] - 128); y0 /= 255.0f; u0 /= 255.0f; @@ -512,33 +631,36 @@ bool UltraHdrAppInput::convertYuv420ToRGBImage() { rgbData++; } } - writeFile("inRgba8888.raw", mRawRgba8888Image.data, - mRawYuv420Image.width * mRawYuv420Image.height * 4); + writeFile("inRgba8888.raw", &mRawRgba8888Image); return true; } bool UltraHdrAppInput::convertRgba8888ToYUV444Image() { - mDestYUV444Image.data = malloc(mDestImage.width * mDestImage.height * 3); - if (mDestYUV444Image.data == nullptr) { - std::cerr << "failed to allocate memory to store yuv444" << std::endl; - return false; - } - mDestYUV444Image.width = mDestImage.width; - mDestYUV444Image.height = mDestImage.height; - mDestYUV444Image.colorGamut = mDestImage.colorGamut; - - uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.data); - - uint8_t* yData = static_cast<uint8_t*>(mDestYUV444Image.data); - uint8_t* uData = yData + (mDestYUV444Image.width * mDestYUV444Image.height); - uint8_t* vData = uData + (mDestYUV444Image.width * mDestYUV444Image.height); + mDestYUV444Image.fmt = static_cast<uhdr_img_fmt_t>(UHDR_IMG_FMT_24bppYCbCr444); + mDestYUV444Image.cg = mDestImage.cg; + mDestYUV444Image.ct = mDestImage.ct; + mDestYUV444Image.range = UHDR_CR_FULL_RANGE; + mDestYUV444Image.w = mDestImage.w; + mDestYUV444Image.h = mDestImage.h; + mDestYUV444Image.planes[0] = malloc(mDestImage.w * mDestImage.h); + mDestYUV444Image.planes[1] = malloc(mDestImage.w * mDestImage.h); + mDestYUV444Image.planes[2] = malloc(mDestImage.w * mDestImage.h); + mDestYUV444Image.stride[0] = mWidth; + mDestYUV444Image.stride[1] = mWidth; + mDestYUV444Image.stride[2] = mWidth; + + uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.planes[0]); + + uint8_t* yData = static_cast<uint8_t*>(mDestYUV444Image.planes[0]); + uint8_t* uData = static_cast<uint8_t*>(mDestYUV444Image.planes[1]); + uint8_t* vData = static_cast<uint8_t*>(mDestYUV444Image.planes[2]); const float* coeffs = BT601RGBtoYUVMatrix; - for (size_t i = 0; i < mDestImage.height; i++) { - for (size_t j = 0; j < mDestImage.width; j++) { - float r0 = float(rgbData[mDestImage.width * i + j] & 0xff); - float g0 = float((rgbData[mDestImage.width * i + j] >> 8) & 0xff); - float b0 = float((rgbData[mDestImage.width * i + j] >> 16) & 0xff); + for (size_t i = 0; i < mDestImage.h; i++) { + for (size_t j = 0; j < mDestImage.w; j++) { + float r0 = float(rgbData[mDestImage.stride[0] * i + j] & 0xff); + float g0 = float((rgbData[mDestImage.stride[0] * i + j] >> 8) & 0xff); + float b0 = float((rgbData[mDestImage.stride[0] * i + j] >> 16) & 0xff); r0 /= 255.0f; g0 /= 255.0f; @@ -556,49 +678,52 @@ bool UltraHdrAppInput::convertRgba8888ToYUV444Image() { u = CLIP3(u, 0.0f, 255.0f); v = CLIP3(v, 0.0f, 255.0f); - yData[mDestYUV444Image.width * i + j] = uint8_t(y); - uData[mDestYUV444Image.width * i + j] = uint8_t(u); - vData[mDestYUV444Image.width * i + j] = uint8_t(v); + yData[mDestYUV444Image.stride[0] * i + j] = uint8_t(y); + uData[mDestYUV444Image.stride[1] * i + j] = uint8_t(u); + vData[mDestYUV444Image.stride[2] * i + j] = uint8_t(v); } } - writeFile("outyuv444.yuv", mDestYUV444Image.data, - mDestYUV444Image.width * mDestYUV444Image.height * 3); + writeFile("outyuv444.yuv", &mDestYUV444Image); return true; } bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() { const float* coeffs = BT2020RGBtoYUVMatrix; - if (mP010Cg == ULTRAHDR_COLORGAMUT_BT709) { + if (mP010Cg == UHDR_CG_BT_709) { coeffs = BT709RGBtoYUVMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_BT2100) { + } else if (mP010Cg == UHDR_CG_BT_2100) { coeffs = BT2020RGBtoYUVMatrix; - } else if (mP010Cg == ULTRAHDR_COLORGAMUT_P3) { + } else if (mP010Cg == UHDR_CG_DISPLAY_P3) { coeffs = BT601RGBtoYUVMatrix; } else { std::cerr << "color matrix not present for gamut " << mP010Cg << " using BT2020Matrix" << std::endl; } - mDestYUV444Image.data = malloc(mDestImage.width * mDestImage.height * 3 * 2); - if (mDestYUV444Image.data == nullptr) { - std::cerr << "failed to allocate memory to store yuv444" << std::endl; - return false; - } - mDestYUV444Image.width = mDestImage.width; - mDestYUV444Image.height = mDestImage.height; - mDestYUV444Image.colorGamut = mDestImage.colorGamut; - - uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.data); - - uint16_t* yData = static_cast<uint16_t*>(mDestYUV444Image.data); - uint16_t* uData = yData + (mDestYUV444Image.width * mDestYUV444Image.height); - uint16_t* vData = uData + (mDestYUV444Image.width * mDestYUV444Image.height); - - for (size_t i = 0; i < mDestImage.height; i++) { - for (size_t j = 0; j < mDestImage.width; j++) { - float r0 = float(rgbData[mDestImage.width * i + j] & 0x3ff); - float g0 = float((rgbData[mDestImage.width * i + j] >> 10) & 0x3ff); - float b0 = float((rgbData[mDestImage.width * i + j] >> 20) & 0x3ff); + mDestYUV444Image.fmt = static_cast<uhdr_img_fmt_t>(UHDR_IMG_FMT_48bppYCbCr444); + mDestYUV444Image.cg = mDestImage.cg; + mDestYUV444Image.ct = mDestImage.ct; + mDestYUV444Image.range = UHDR_CR_FULL_RANGE; + mDestYUV444Image.w = mDestImage.w; + mDestYUV444Image.h = mDestImage.h; + mDestYUV444Image.planes[0] = malloc(mDestImage.w * mDestImage.h * 2); + mDestYUV444Image.planes[1] = malloc(mDestImage.w * mDestImage.h * 2); + mDestYUV444Image.planes[2] = malloc(mDestImage.w * mDestImage.h * 2); + mDestYUV444Image.stride[0] = mWidth; + mDestYUV444Image.stride[1] = mWidth; + mDestYUV444Image.stride[2] = mWidth; + + uint32_t* rgbData = static_cast<uint32_t*>(mDestImage.planes[0]); + + uint16_t* yData = static_cast<uint16_t*>(mDestYUV444Image.planes[0]); + uint16_t* uData = static_cast<uint16_t*>(mDestYUV444Image.planes[1]); + uint16_t* vData = static_cast<uint16_t*>(mDestYUV444Image.planes[2]); + + for (size_t i = 0; i < mDestImage.h; i++) { + for (size_t j = 0; j < mDestImage.w; j++) { + float r0 = float(rgbData[mDestImage.stride[0] * i + j] & 0x3ff); + float g0 = float((rgbData[mDestImage.stride[0] * i + j] >> 10) & 0x3ff); + float b0 = float((rgbData[mDestImage.stride[0] * i + j] >> 20) & 0x3ff); r0 /= 1023.0f; g0 /= 1023.0f; @@ -616,35 +741,33 @@ bool UltraHdrAppInput::convertRgba1010102ToYUV444Image() { u = CLIP3(u, 64.0f, 960.0f); v = CLIP3(v, 64.0f, 960.0f); - yData[mDestYUV444Image.width * i + j] = uint16_t(y); - uData[mDestYUV444Image.width * i + j] = uint16_t(u); - vData[mDestYUV444Image.width * i + j] = uint16_t(v); + yData[mDestYUV444Image.stride[0] * i + j] = uint16_t(y); + uData[mDestYUV444Image.stride[1] * i + j] = uint16_t(u); + vData[mDestYUV444Image.stride[2] * i + j] = uint16_t(v); } } - writeFile("outyuv444.yuv", mDestYUV444Image.data, - mDestYUV444Image.width * mDestYUV444Image.height * 3 * 2); + writeFile("outyuv444.yuv", &mDestYUV444Image); return true; } void UltraHdrAppInput::computeRGBHdrPSNR() { - if (mOf == ULTRAHDR_OUTPUT_SDR || mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) { - std::cout << "psnr not supported for output format " << mOf << std::endl; + if (mOfmt != UHDR_IMG_FMT_32bppRGBA1010102) { + std::cout << "psnr not supported for output format " << mOfmt << std::endl; return; } - uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba1010102Image.data); - uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data); + uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba1010102Image.planes[0]); + uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.planes[0]); if (rgbDataSrc == nullptr || rgbDataDst == nullptr) { std::cerr << "invalid src or dst pointer for psnr computation " << std::endl; return; } - if ((mOf == ULTRAHDR_OUTPUT_HDR_PQ && mTf != ULTRAHDR_TF_PQ) || - (mOf == ULTRAHDR_OUTPUT_HDR_HLG && mTf != ULTRAHDR_TF_HLG)) { + if (mOTf != mP010Tf) { std::cout << "input transfer function and output format are not compatible, psnr results " "may be unreliable" << std::endl; } uint64_t rSqError = 0, gSqError = 0, bSqError = 0; - for (size_t i = 0; i < mRawP010Image.width * mRawP010Image.height; i++) { + for (size_t i = 0; i < mRawP010Image.w * mRawP010Image.h; i++) { int rSrc = *rgbDataSrc & 0x3ff; int rDst = *rgbDataDst & 0x3ff; rSqError += (rSrc - rDst) * (rSrc - rDst); @@ -660,13 +783,13 @@ void UltraHdrAppInput::computeRGBHdrPSNR() { rgbDataSrc++; rgbDataDst++; } - double meanSquareError = (double)rSqError / (mRawP010Image.width * mRawP010Image.height); + double meanSquareError = (double)rSqError / (mRawP010Image.w * mRawP010Image.h); mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - meanSquareError = (double)gSqError / (mRawP010Image.width * mRawP010Image.height); + meanSquareError = (double)gSqError / (mRawP010Image.w * mRawP010Image.h); mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - meanSquareError = (double)bSqError / (mRawP010Image.width * mRawP010Image.height); + meanSquareError = (double)bSqError / (mRawP010Image.w * mRawP010Image.h); mPsnr[2] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2] @@ -674,19 +797,19 @@ void UltraHdrAppInput::computeRGBHdrPSNR() { } void UltraHdrAppInput::computeRGBSdrPSNR() { - if (mOf != ULTRAHDR_OUTPUT_SDR) { - std::cout << "psnr not supported for output format " << mOf << std::endl; + if (mOfmt != UHDR_IMG_FMT_32bppRGBA8888) { + std::cout << "psnr not supported for output format " << mOfmt << std::endl; return; } - uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba8888Image.data); - uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.data); + uint32_t* rgbDataSrc = static_cast<uint32_t*>(mRawRgba8888Image.planes[0]); + uint32_t* rgbDataDst = static_cast<uint32_t*>(mDestImage.planes[0]); if (rgbDataSrc == nullptr || rgbDataDst == nullptr) { std::cerr << "invalid src or dst pointer for psnr computation " << std::endl; return; } uint64_t rSqError = 0, gSqError = 0, bSqError = 0; - for (size_t i = 0; i < mRawYuv420Image.width * mRawYuv420Image.height; i++) { + for (size_t i = 0; i < mRawYuv420Image.w * mRawYuv420Image.h; i++) { int rSrc = *rgbDataSrc & 0xff; int rDst = *rgbDataDst & 0xff; rSqError += (rSrc - rDst) * (rSrc - rDst); @@ -702,13 +825,13 @@ void UltraHdrAppInput::computeRGBSdrPSNR() { rgbDataSrc++; rgbDataDst++; } - double meanSquareError = (double)rSqError / (mRawYuv420Image.width * mRawYuv420Image.height); + double meanSquareError = (double)rSqError / (mRawYuv420Image.w * mRawYuv420Image.h); mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - meanSquareError = (double)gSqError / (mRawYuv420Image.width * mRawYuv420Image.height); + meanSquareError = (double)gSqError / (mRawYuv420Image.w * mRawYuv420Image.h); mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - meanSquareError = (double)bSqError / (mRawYuv420Image.width * mRawYuv420Image.height); + meanSquareError = (double)bSqError / (mRawYuv420Image.w * mRawYuv420Image.h); mPsnr[2] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; std::cout << "psnr r :: " << mPsnr[0] << " psnr g :: " << mPsnr[1] << " psnr b :: " << mPsnr[2] @@ -716,68 +839,65 @@ void UltraHdrAppInput::computeRGBSdrPSNR() { } void UltraHdrAppInput::computeYUVHdrPSNR() { - if (mOf == ULTRAHDR_OUTPUT_SDR || mOf == ULTRAHDR_OUTPUT_HDR_LINEAR) { - std::cout << "psnr not supported for output format " << mOf << std::endl; + if (mOfmt != UHDR_IMG_FMT_32bppRGBA1010102) { + std::cout << "psnr not supported for output format " << mOfmt << std::endl; return; } - uint16_t* yuvDataSrc = static_cast<uint16_t*>(mRawP010Image.data); - uint16_t* yuvDataDst = static_cast<uint16_t*>(mDestYUV444Image.data); - if (yuvDataSrc == nullptr || yuvDataDst == nullptr) { + uint16_t* yDataSrc = static_cast<uint16_t*>(mRawP010Image.planes[0]); + uint16_t* uDataSrc = static_cast<uint16_t*>(mRawP010Image.planes[1]); + uint16_t* vDataSrc = uDataSrc + 1; + + uint16_t* yDataDst = static_cast<uint16_t*>(mDestYUV444Image.planes[0]); + uint16_t* uDataDst = static_cast<uint16_t*>(mDestYUV444Image.planes[1]); + uint16_t* vDataDst = static_cast<uint16_t*>(mDestYUV444Image.planes[2]); + if (yDataSrc == nullptr || uDataSrc == nullptr || yDataDst == nullptr || uDataDst == nullptr || + vDataDst == nullptr) { std::cerr << "invalid src or dst pointer for psnr computation " << std::endl; return; } - if ((mOf == ULTRAHDR_OUTPUT_HDR_PQ && mTf != ULTRAHDR_TF_PQ) || - (mOf == ULTRAHDR_OUTPUT_HDR_HLG && mTf != ULTRAHDR_TF_HLG)) { + if (mOTf != mP010Tf) { std::cout << "input transfer function and output format are not compatible, psnr results " "may be unreliable" << std::endl; } - uint16_t* yDataSrc = static_cast<uint16_t*>(mRawP010Image.data); - uint16_t* uDataSrc = yDataSrc + (mRawP010Image.width * mRawP010Image.height); - uint16_t* vDataSrc = uDataSrc + 1; - - uint16_t* yDataDst = static_cast<uint16_t*>(mDestYUV444Image.data); - uint16_t* uDataDst = yDataDst + (mDestYUV444Image.width * mDestYUV444Image.height); - uint16_t* vDataDst = uDataDst + (mDestYUV444Image.width * mDestYUV444Image.height); - uint64_t ySqError = 0, uSqError = 0, vSqError = 0; - for (size_t i = 0; i < mDestYUV444Image.height; i++) { - for (size_t j = 0; j < mDestYUV444Image.width; j++) { - int ySrc = (yDataSrc[mRawP010Image.width * i + j] >> 6) & 0x3ff; + for (size_t i = 0; i < mDestYUV444Image.h; i++) { + for (size_t j = 0; j < mDestYUV444Image.w; j++) { + int ySrc = (yDataSrc[mRawP010Image.stride[0] * i + j] >> 6) & 0x3ff; ySrc = CLIP3(ySrc, 64, 940); - int yDst = yDataDst[mDestYUV444Image.width * i + j] & 0x3ff; + int yDst = yDataDst[mDestYUV444Image.stride[0] * i + j] & 0x3ff; ySqError += (ySrc - yDst) * (ySrc - yDst); if (i % 2 == 0 && j % 2 == 0) { - int uSrc = (uDataSrc[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff; + int uSrc = (uDataSrc[mRawP010Image.stride[1] * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff; uSrc = CLIP3(uSrc, 64, 960); - int uDst = uDataDst[mDestYUV444Image.width * i + j] & 0x3ff; - uDst += uDataDst[mDestYUV444Image.width * i + j + 1] & 0x3ff; - uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff; - uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff; + int uDst = uDataDst[mDestYUV444Image.stride[1] * i + j] & 0x3ff; + uDst += uDataDst[mDestYUV444Image.stride[1] * i + j + 1] & 0x3ff; + uDst += uDataDst[mDestYUV444Image.stride[1] * (i + 1) + j + 1] & 0x3ff; + uDst += uDataDst[mDestYUV444Image.stride[1] * (i + 1) + j + 1] & 0x3ff; uDst = (uDst + 2) >> 2; uSqError += (uSrc - uDst) * (uSrc - uDst); - int vSrc = (vDataSrc[mRawP010Image.width * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff; + int vSrc = (vDataSrc[mRawP010Image.stride[1] * (i / 2) + (j / 2) * 2] >> 6) & 0x3ff; vSrc = CLIP3(vSrc, 64, 960); - int vDst = vDataDst[mDestYUV444Image.width * i + j] & 0x3ff; - vDst += vDataDst[mDestYUV444Image.width * i + j + 1] & 0x3ff; - vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff; - vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j + 1] & 0x3ff; + int vDst = vDataDst[mDestYUV444Image.stride[2] * i + j] & 0x3ff; + vDst += vDataDst[mDestYUV444Image.stride[2] * i + j + 1] & 0x3ff; + vDst += vDataDst[mDestYUV444Image.stride[2] * (i + 1) + j + 1] & 0x3ff; + vDst += vDataDst[mDestYUV444Image.stride[2] * (i + 1) + j + 1] & 0x3ff; vDst = (vDst + 2) >> 2; vSqError += (vSrc - vDst) * (vSrc - vDst); } } } - double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height); + double meanSquareError = (double)ySqError / (mDestYUV444Image.w * mDestYUV444Image.h); mPsnr[0] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); + meanSquareError = (double)uSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4); mPsnr[1] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; - meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); + meanSquareError = (double)vSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4); mPsnr[2] = meanSquareError ? 10 * log10((double)1023 * 1023 / meanSquareError) : 100; std::cout << "psnr y :: " << mPsnr[0] << " psnr u :: " << mPsnr[1] << " psnr v :: " << mPsnr[2] @@ -785,52 +905,52 @@ void UltraHdrAppInput::computeYUVHdrPSNR() { } void UltraHdrAppInput::computeYUVSdrPSNR() { - if (mOf != ULTRAHDR_OUTPUT_SDR) { - std::cout << "psnr not supported for output format " << mOf << std::endl; + if (mOfmt != UHDR_IMG_FMT_32bppRGBA8888) { + std::cout << "psnr not supported for output format " << mOfmt << std::endl; return; } - uint8_t* yDataSrc = static_cast<uint8_t*>(mRawYuv420Image.data); - uint8_t* uDataSrc = yDataSrc + (mRawYuv420Image.width * mRawYuv420Image.height); - uint8_t* vDataSrc = uDataSrc + (mRawYuv420Image.width * mRawYuv420Image.height / 4); + uint8_t* yDataSrc = static_cast<uint8_t*>(mRawYuv420Image.planes[0]); + uint8_t* uDataSrc = static_cast<uint8_t*>(mRawYuv420Image.planes[1]); + uint8_t* vDataSrc = static_cast<uint8_t*>(mRawYuv420Image.planes[2]); - uint8_t* yDataDst = static_cast<uint8_t*>(mDestYUV444Image.data); - uint8_t* uDataDst = yDataDst + (mDestYUV444Image.width * mDestYUV444Image.height); - uint8_t* vDataDst = uDataDst + (mDestYUV444Image.width * mDestYUV444Image.height); + uint8_t* yDataDst = static_cast<uint8_t*>(mDestYUV444Image.planes[0]); + uint8_t* uDataDst = static_cast<uint8_t*>(mDestYUV444Image.planes[1]); + uint8_t* vDataDst = static_cast<uint8_t*>(mDestYUV444Image.planes[2]); uint64_t ySqError = 0, uSqError = 0, vSqError = 0; - for (size_t i = 0; i < mDestYUV444Image.height; i++) { - for (size_t j = 0; j < mDestYUV444Image.width; j++) { - int ySrc = yDataSrc[mRawYuv420Image.width * i + j]; - int yDst = yDataDst[mDestYUV444Image.width * i + j]; + for (size_t i = 0; i < mDestYUV444Image.h; i++) { + for (size_t j = 0; j < mDestYUV444Image.w; j++) { + int ySrc = yDataSrc[mRawYuv420Image.stride[0] * i + j]; + int yDst = yDataDst[mDestYUV444Image.stride[0] * i + j]; ySqError += (ySrc - yDst) * (ySrc - yDst); if (i % 2 == 0 && j % 2 == 0) { - int uSrc = uDataSrc[mRawYuv420Image.width / 2 * (i / 2) + j / 2]; - int uDst = uDataDst[mDestYUV444Image.width * i + j]; - uDst += uDataDst[mDestYUV444Image.width * i + j + 1]; - uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j]; - uDst += uDataDst[mDestYUV444Image.width * (i + 1) + j + 1]; + int uSrc = uDataSrc[mRawYuv420Image.stride[1] * (i / 2) + j / 2]; + int uDst = uDataDst[mDestYUV444Image.stride[1] * i + j]; + uDst += uDataDst[mDestYUV444Image.stride[1] * i + j + 1]; + uDst += uDataDst[mDestYUV444Image.stride[1] * (i + 1) + j]; + uDst += uDataDst[mDestYUV444Image.stride[1] * (i + 1) + j + 1]; uDst = (uDst + 2) >> 2; uSqError += (uSrc - uDst) * (uSrc - uDst); - int vSrc = vDataSrc[mRawYuv420Image.width / 2 * (i / 2) + j / 2]; - int vDst = vDataDst[mDestYUV444Image.width * i + j]; - vDst += vDataDst[mDestYUV444Image.width * i + j + 1]; - vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j]; - vDst += vDataDst[mDestYUV444Image.width * (i + 1) + j + 1]; + int vSrc = vDataSrc[mRawYuv420Image.stride[2] * (i / 2) + j / 2]; + int vDst = vDataDst[mDestYUV444Image.stride[2] * i + j]; + vDst += vDataDst[mDestYUV444Image.stride[2] * i + j + 1]; + vDst += vDataDst[mDestYUV444Image.stride[2] * (i + 1) + j]; + vDst += vDataDst[mDestYUV444Image.stride[2] * (i + 1) + j + 1]; vDst = (vDst + 2) >> 2; vSqError += (vSrc - vDst) * (vSrc - vDst); } } } - double meanSquareError = (double)ySqError / (mDestYUV444Image.width * mDestYUV444Image.height); + double meanSquareError = (double)ySqError / (mDestYUV444Image.w * mDestYUV444Image.h); mPsnr[0] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - meanSquareError = (double)uSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); + meanSquareError = (double)uSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4); mPsnr[1] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; - meanSquareError = (double)vSqError / (mDestYUV444Image.width * mDestYUV444Image.height / 4); + meanSquareError = (double)vSqError / (mDestYUV444Image.w * mDestYUV444Image.h / 4); mPsnr[2] = meanSquareError ? 10 * log10((double)255 * 255 / meanSquareError) : 100; std::cout << "psnr y :: " << mPsnr[0] << " psnr u:: " << mPsnr[1] << " psnr v :: " << mPsnr[2] @@ -850,7 +970,7 @@ static void usage(const char* name) { fprintf(stderr, " -h input file height, mandatory. \n"); fprintf(stderr, " -C 10 bit input color gamut, optional. [0:bt709, 1:p3, 2:bt2100] \n"); fprintf(stderr, " -c 8 bit input color gamut, optional. [0:bt709, 1:p3, 2:bt2100] \n"); - fprintf(stderr, " -t input transfer function, optional. [0:linear, 1:hlg, 2:pq] \n"); + fprintf(stderr, " -t 10 bit input transfer function, optional. [0:linear, 1:hlg, 2:pq] \n"); fprintf(stderr, " -q quality factor to be used while encoding 8 bit image, optional. [0-100].\n" " gain map image does not use this quality factor. \n" @@ -859,22 +979,23 @@ static void usage(const char* name) { fprintf(stderr, "\n## decoder options : \n"); fprintf(stderr, " -j ultra hdr input resource, mandatory in decode mode. \n"); fprintf(stderr, - " -o output transfer function, optional. [0:sdr, 1:hdr_linear, 2:hdr_pq, " - "3:hdr_hlg] \n"); + " -o output transfer function, optional. [0:linear, 1:hlg, 2:pq, 3:srgb] \n"); + fprintf(stderr, + " -O output color format, optional. [3:rgba8888, 4:rgbahalffloat, 5:rgba1010102] \n" + "It should be noted that not all combinations of output color format and output transfer " + "function are supported. srgb output color transfer shall be paired with rgba8888 only. " + "hlg, pq shall be paired with rgba1010102. linear shall be paired with rgbahalffloat"); fprintf(stderr, "\n## examples of usage :\n"); fprintf(stderr, "\n## encode api-0 :\n"); fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -w 1920 -h 1080 -q 97\n"); fprintf(stderr, - " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -w 1920 -h 1080 -q 97 -C 2 -t 2\n"); + " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -w 1920 -h 1080 -q 97 -C 1 -t 2\n"); fprintf(stderr, "\n## encode api-1 :\n"); fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 " "-h 1080 -q 97\n"); fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 " - "-h 1080 -q 97\n"); - fprintf(stderr, - " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 " "-h 1080 -q 97 -C 2 -c 1 -t 1\n"); fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -w 1920 " @@ -882,27 +1003,29 @@ static void usage(const char* name) { fprintf(stderr, "\n## encode api-2 :\n"); fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -y cosmat_1920x1080_420.yuv -i " - "cosmat_1920x1080_420_8bit.jpg -w 1920 -h 1080 -t 1 -o 3 -e 1\n"); + "cosmat_1920x1080_420_8bit.jpg -w 1920 -h 1080 -t 1 -o 3 -O 3 -e 1\n"); fprintf(stderr, "\n## encode api-3 :\n"); fprintf(stderr, " ultrahdr_app -m 0 -p cosmat_1920x1080_p010.yuv -i cosmat_1920x1080_420_8bit.jpg -w " - "1920 -h 1080 -t 1 -o 3 -e 1\n"); + "1920 -h 1080 -t 1 -o 1 -O 5 -e 1\n"); fprintf(stderr, "\n## decode api :\n"); fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg \n"); - fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg -o 2\n"); + fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg -o 3 -O 3\n"); + fprintf(stderr, " ultrahdr_app -m 1 -j cosmat_1920x1080_hdr.jpg -o 1 -O 5\n"); fprintf(stderr, "\n"); } int main(int argc, char* argv[]) { - char opt_string[] = "p:y:i:w:h:C:c:t:q:o:m:j:e:"; + char opt_string[] = "p:y:i:w:h:C:c:t:q:o:O:m:j:e:"; char *p010_file = nullptr, *yuv420_file = nullptr, *jpegr_file = nullptr, *yuv420_jpeg_file = nullptr; int width = 0, height = 0; - ultrahdr_color_gamut p010Cg = ULTRAHDR_COLORGAMUT_BT709; - ultrahdr_color_gamut yuv420Cg = ULTRAHDR_COLORGAMUT_BT709; - ultrahdr_transfer_function tf = ULTRAHDR_TF_HLG; + uhdr_color_gamut_t p010Cg = UHDR_CG_BT_709; + uhdr_color_gamut_t yuv420Cg = UHDR_CG_BT_709; + uhdr_color_transfer_t p010Tf = UHDR_CT_HLG; int quality = 100; - ultrahdr_output_format of = ULTRAHDR_OUTPUT_HDR_HLG; + uhdr_color_transfer_t outTf = UHDR_CT_HLG; + uhdr_img_fmt_t outFmt = UHDR_IMG_FMT_32bppRGBA1010102; int mode = 0; int compute_psnr = 0; int ch; @@ -924,19 +1047,22 @@ int main(int argc, char* argv[]) { height = atoi(optarg_s); break; case 'C': - p010Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg_s)); + p010Cg = static_cast<uhdr_color_gamut_t>(atoi(optarg_s)); break; case 'c': - yuv420Cg = static_cast<ultrahdr_color_gamut>(atoi(optarg_s)); + yuv420Cg = static_cast<uhdr_color_gamut_t>(atoi(optarg_s)); break; case 't': - tf = static_cast<ultrahdr_transfer_function>(atoi(optarg_s)); + p010Tf = static_cast<uhdr_color_transfer_t>(atoi(optarg_s)); break; case 'q': quality = atoi(optarg_s); break; + case 'O': + outFmt = static_cast<uhdr_img_fmt_t>(atoi(optarg_s)); + break; case 'o': - of = static_cast<ultrahdr_output_format>(atoi(optarg_s)); + outTf = static_cast<uhdr_color_transfer_t>(atoi(optarg_s)); break; case 'm': mode = atoi(optarg_s); @@ -958,16 +1084,16 @@ int main(int argc, char* argv[]) { return -1; } UltraHdrAppInput appInput(p010_file, yuv420_file, yuv420_jpeg_file, width, height, p010Cg, - yuv420Cg, tf, quality, of); + yuv420Cg, p010Tf, quality, outTf, outFmt); if (!appInput.encode()) return -1; if (compute_psnr == 1) { if (!appInput.decode()) return -1; - if (of == ULTRAHDR_OUTPUT_SDR && yuv420_file != nullptr) { + if (outFmt == UHDR_IMG_FMT_32bppRGBA8888 && yuv420_file != nullptr) { appInput.convertYuv420ToRGBImage(); appInput.computeRGBSdrPSNR(); appInput.convertRgba8888ToYUV444Image(); appInput.computeYUVSdrPSNR(); - } else if (of == ULTRAHDR_OUTPUT_HDR_HLG || of == ULTRAHDR_OUTPUT_HDR_PQ) { + } else if (outFmt == UHDR_IMG_FMT_32bppRGBA1010102) { appInput.convertP010ToRGBImage(); appInput.computeRGBHdrPSNR(); appInput.convertRgba1010102ToYUV444Image(); @@ -979,7 +1105,7 @@ int main(int argc, char* argv[]) { usage(argv[0]); return -1; } - UltraHdrAppInput appInput(jpegr_file, of); + UltraHdrAppInput appInput(jpegr_file, outTf, outFmt); if (!appInput.decode()) return -1; } else { std::cerr << "unrecognized input mode " << mode << std::endl; diff --git a/lib/include/ultrahdr/jpegdecoderhelper.h b/lib/include/ultrahdr/jpegdecoderhelper.h index d0b1f6f..4d5b42e 100644 --- a/lib/include/ultrahdr/jpegdecoderhelper.h +++ b/lib/include/ultrahdr/jpegdecoderhelper.h @@ -35,13 +35,13 @@ extern "C" { #include <memory> #include <vector> +namespace ultrahdr { + // constraint on max width and max height is only due to device alloc constraints // Can tune these values basing on the target device static const int kMaxWidth = 8192; static const int kMaxHeight = 8192; -namespace ultrahdr { - typedef enum { PARSE_ONLY = 0, // Dont decode. Parse for dimensions, EXIF, ICC, XMP DECODE_TO_RGBA = 1, // Parse and decode to rgba diff --git a/lib/include/ultrahdr/ultrahdrcommon.h b/lib/include/ultrahdr/ultrahdrcommon.h index ba3a3b8..8a80f5a 100644 --- a/lib/include/ultrahdr/ultrahdrcommon.h +++ b/lib/include/ultrahdr/ultrahdrcommon.h @@ -19,6 +19,16 @@ //#define LOG_NDEBUG 0 +#include <map> +#include <memory> +#include <vector> + +#include "ultrahdr_api.h" + +// =============================================================================================== +// Function Macros +// =============================================================================================== + #ifdef __ANDROID__ #include "log/log.h" #else @@ -61,4 +71,79 @@ #define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m)) +namespace ultrahdr { + +// =============================================================================================== +// Structure Definitions +// =============================================================================================== + +/**\brief uhdr memory block */ +typedef struct uhdr_memory_block { + uhdr_memory_block(size_t capacity); + + std::unique_ptr<uint8_t[]> m_buffer; /**< data */ + size_t m_capacity; /**< capacity */ +} uhdr_memory_block_t; /**< alias for struct uhdr_memory_block */ + +/**\brief extended raw image descriptor */ +typedef struct uhdr_raw_image_ext : uhdr_raw_image_t { + uhdr_raw_image_ext(uhdr_img_fmt_t fmt, uhdr_color_gamut_t cg, uhdr_color_transfer_t ct, + uhdr_color_range_t range, unsigned w, unsigned h, unsigned align_stride_to); + + private: + std::unique_ptr<ultrahdr::uhdr_memory_block> m_block; +} uhdr_raw_image_ext_t; /**< alias for struct uhdr_raw_image_ext */ + +/**\brief extended compressed image descriptor */ +typedef struct uhdr_compressed_image_ext : uhdr_compressed_image_t { + uhdr_compressed_image_ext(uhdr_color_gamut_t cg, uhdr_color_transfer_t ct, + uhdr_color_range_t range, unsigned sz); + + private: + std::unique_ptr<ultrahdr::uhdr_memory_block> m_block; +} uhdr_compressed_image_ext_t; /**< alias for struct uhdr_compressed_image_ext */ + +} // namespace ultrahdr + +// =============================================================================================== +// Extensions of ultrahdr api definitions, so outside ultrahdr namespace +// =============================================================================================== + +struct uhdr_codec_private {}; + +struct uhdr_encoder_private : uhdr_codec_private { + // config data + std::map<uhdr_img_label, std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t>> m_raw_images; + std::map<uhdr_img_label, std::unique_ptr<ultrahdr::uhdr_compressed_image_ext_t>> + m_compressed_images; + std::map<uhdr_img_label, int> m_quality; + std::vector<uint8_t> m_exif; + uhdr_gainmap_metadata_t m_metadata; + uhdr_codec_t m_output_format; + + // internal data + bool m_sailed; + std::unique_ptr<ultrahdr::uhdr_compressed_image_ext_t> m_compressed_output_buffer; + uhdr_error_info_t m_encode_call_status; +}; + +struct uhdr_decoder_private : uhdr_codec_private { + // config data + std::unique_ptr<ultrahdr::uhdr_compressed_image_ext_t> m_uhdr_compressed_img; + uhdr_img_fmt_t m_output_fmt; + uhdr_color_transfer_t m_output_ct; + float m_output_max_disp_boost; + + // internal data + bool m_sailed; + std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> m_decoded_img_buffer; + std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> m_gainmap_img_buffer; + std::vector<uint8_t> m_exif; + std::vector<uint8_t> m_icc; + std::vector<uint8_t> m_base_xmp; + std::vector<uint8_t> m_gainmap_xmp; + uhdr_gainmap_metadata_t m_metadata; + uhdr_error_info_t m_decode_call_status; +}; + #endif // ULTRAHDR_ULTRAHDRCOMMON_H diff --git a/lib/src/ultrahdr_api.cpp b/lib/src/ultrahdr_api.cpp new file mode 100644 index 0000000..bf97d43 --- /dev/null +++ b/lib/src/ultrahdr_api.cpp @@ -0,0 +1,1146 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <cstdio> +#include <cstring> + +#include "ultrahdr_api.h" +#include "ultrahdr/ultrahdrcommon.h" +#include "ultrahdr/jpegr.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) { + m_buffer = std::make_unique<uint8_t[]>(capacity); + m_capacity = capacity; +} + +uhdr_raw_image_ext::uhdr_raw_image_ext(uhdr_img_fmt_t fmt, uhdr_color_gamut_t cg, + uhdr_color_transfer_t ct, uhdr_color_range_t range, + unsigned w, unsigned h, unsigned align_stride_to) { + this->fmt = fmt; + this->cg = cg; + this->ct = ct; + this->range = range; + + this->w = w; + this->h = h; + + int aligned_width = ALIGNM(w, align_stride_to); + + int bpp = 1; + if (fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + bpp = 2; + } else if (fmt == UHDR_IMG_FMT_32bppRGBA8888 || fmt == UHDR_IMG_FMT_32bppRGBA1010102) { + bpp = 4; + } else if (fmt == UHDR_IMG_FMT_64bppRGBAHalfFloat) { + bpp = 8; + } + + size_t plane_1_sz = bpp * aligned_width * h; + size_t plane_2_sz; + size_t plane_3_sz; + if (fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + plane_2_sz = (2 /* planes */ * ((aligned_width / 2) * (h / 2) * bpp)); + plane_3_sz = 0; + } else if (fmt == UHDR_IMG_FMT_12bppYCbCr420) { + plane_2_sz = (((aligned_width / 2) * (h / 2) * bpp)); + plane_3_sz = (((aligned_width / 2) * (h / 2) * bpp)); + } else { + plane_2_sz = 0; + plane_3_sz = 0; + } + size_t total_size = plane_1_sz + plane_2_sz + plane_3_sz; + this->m_block = std::make_unique<uhdr_memory_block_t>(total_size); + + uint8_t* data = this->m_block->m_buffer.get(); + this->planes[0] = data; + this->stride[0] = aligned_width; + if (fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + this->planes[1] = data + plane_1_sz; + this->stride[1] = aligned_width; + this->planes[2] = nullptr; + this->stride[2] = 0; + } else if (fmt == UHDR_IMG_FMT_12bppYCbCr420) { + this->planes[1] = data + plane_1_sz; + this->stride[1] = aligned_width / 2; + this->planes[2] = data + plane_1_sz + plane_2_sz; + this->stride[2] = aligned_width / 2; + } else { + this->planes[1] = nullptr; + this->stride[1] = 0; + this->planes[2] = nullptr; + this->stride[2] = 0; + } +} + +uhdr_compressed_image_ext::uhdr_compressed_image_ext(uhdr_color_gamut_t cg, + uhdr_color_transfer_t ct, + uhdr_color_range_t range, unsigned size) { + this->m_block = std::make_unique<uhdr_memory_block_t>(size); + this->data = this->m_block->m_buffer.get(); + this->capacity = size; + this->data_sz = 0; + this->cg = cg; + this->ct = ct; + this->range = range; +} + +} // namespace ultrahdr + +ultrahdr::ultrahdr_pixel_format map_pix_fmt_to_internal_pix_fmt(uhdr_img_fmt_t fmt) { + switch (fmt) { + case UHDR_IMG_FMT_12bppYCbCr420: + return ultrahdr::ULTRAHDR_PIX_FMT_YUV420; + case UHDR_IMG_FMT_24bppYCbCrP010: + return ultrahdr::ULTRAHDR_PIX_FMT_P010; + case UHDR_IMG_FMT_32bppRGBA1010102: + return ultrahdr::ULTRAHDR_PIX_FMT_RGBA1010102; + case UHDR_IMG_FMT_32bppRGBA8888: + return ultrahdr::ULTRAHDR_PIX_FMT_RGBA8888; + case UHDR_IMG_FMT_64bppRGBAHalfFloat: + return ultrahdr::ULTRAHDR_PIX_FMT_RGBAF16; + case UHDR_IMG_FMT_8bppYCbCr400: + return ultrahdr::ULTRAHDR_PIX_FMT_MONOCHROME; + default: + return ultrahdr::ULTRAHDR_PIX_FMT_UNSPECIFIED; + } +} + +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'. Either k1 is fractional or k2 is fractional or k1 != k2. " + "currently the library does not handle these scenarios"); + } 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) { + uhdr_error_info_t status = g_no_error; + + if (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"); + } else if (img == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle"); + } else if (img->data == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for compressed img->data field"); + } else if (img->capacity < img->data_sz) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "img->capacity %d is less than img->data_sz %d", + img->capacity, img->data_sz); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(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; + } + + auto entry = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>(img->cg, img->ct, img->range, + img->data_sz); + memcpy(entry->data, img->data, img->data_sz); + entry->data_sz = img->data_sz; + handle->m_compressed_images.insert_or_assign(intent, std::move(entry)); + + return status; +} + +uhdr_codec_private_t* uhdr_create_encoder(void) { + uhdr_encoder_private* handle = new uhdr_encoder_private(); + + if (handle != nullptr) { + uhdr_reset_encoder(handle); + } + return handle; +} + +void uhdr_release_encoder(uhdr_codec_private_t* enc) { + if (enc != nullptr) { + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(enc); + delete handle; + } +} + +uhdr_error_info_t uhdr_enc_set_raw_image(uhdr_codec_private_t* enc, uhdr_raw_image_t* img, + uhdr_img_label_t intent) { + uhdr_error_info_t status = g_no_error; + + if (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"); + } else if (img == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for raw image handle"); + } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG}", intent); + } else if (img->fmt != UHDR_IMG_FMT_12bppYCbCr420 && img->fmt != UHDR_IMG_FMT_24bppYCbCrP010) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid input pixel format %d, expects one of {UHDR_IMG_FMT_12bppYCbCr420, " + "UHDR_IMG_FMT_24bppYCbCrP010}", + img->fmt); + } else if (img->cg != UHDR_CG_BT_2100 && img->cg != UHDR_CG_DISPLAY_P3 && + img->cg != UHDR_CG_BT_709) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid input color gamut %d, expects one of {UHDR_CG_BT_2100, UHDR_CG_DISPLAY_P3, " + "UHDR_CG_BT_709}", + img->cg); + } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420 && img->ct != UHDR_CT_SRGB) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid input color transfer for sdr intent image %d, expects UHDR_CT_SRGB", img->ct); + } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010 && + (img->ct != UHDR_CT_HLG && img->ct != UHDR_CT_LINEAR && img->ct != UHDR_CT_PQ)) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid input color transfer for hdr intent image %d, expects one of {UHDR_CT_HLG, " + "UHDR_CT_LINEAR, UHDR_CT_PQ}", + img->ct); + } else if (img->w % 2 != 0 || img->h % 2 != 0) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "image dimensions cannot be odd, received image dimensions %dx%d", img->w, img->h); + } else if (img->w < ultrahdr::kMinWidth || img->h < ultrahdr::kMinHeight) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "image dimensions cannot be less than %dx%d, received image dimensions %dx%d", + ultrahdr::kMinWidth, ultrahdr::kMinHeight, img->w, img->h); + } else if (img->w > ultrahdr::kMaxWidth || img->h > ultrahdr::kMaxHeight) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "image dimensions cannot be larger than %dx%d, received image dimensions %dx%d", + ultrahdr::kMaxWidth, ultrahdr::kMaxHeight, img->w, img->h); + } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_UV] == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for data field(s), luma ptr %p, chroma_uv ptr %p", + img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_UV]); + } else if (img->stride[UHDR_PLANE_Y] < img->w) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "luma stride must not be smaller than width, stride=%d, width=%d", + img->stride[UHDR_PLANE_Y], img->w); + } else if (img->stride[UHDR_PLANE_UV] < img->w) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "chroma_uv stride must not be smaller than width, stride=%d, width=%d", + img->stride[UHDR_PLANE_UV], img->w); + } + } else if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) { + if (img->planes[UHDR_PLANE_Y] == nullptr || img->planes[UHDR_PLANE_U] == nullptr || + img->planes[UHDR_PLANE_V] == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for data field(s) luma ptr %p, chroma_u ptr %p, chroma_v ptr %p", + img->planes[UHDR_PLANE_Y], img->planes[UHDR_PLANE_U], img->planes[UHDR_PLANE_V]); + } else if (img->stride[UHDR_PLANE_Y] < img->w) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "luma stride must not be smaller than width, stride=%d, width=%d", + img->stride[UHDR_PLANE_Y], img->w); + } else if (img->stride[UHDR_PLANE_U] < img->w / 2) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "chroma_u stride must not be smaller than width / 2, stride=%d, width=%d", + img->stride[UHDR_PLANE_U], img->w); + } else if (img->stride[UHDR_PLANE_V] < img->w / 2) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "chroma_v stride must not be smaller than width / 2, stride=%d, width=%d", + img->stride[UHDR_PLANE_V], img->w); + } + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(enc); + if (intent == UHDR_HDR_IMG && + 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; + if (img->w != sdr_raw_entry->w || img->h != sdr_raw_entry->h) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "image resolutions mismatch: hdr intent: %dx%d, sdr intent: %dx%d", img->w, img->h, + sdr_raw_entry->w, sdr_raw_entry->h); + return status; + } + } + if (intent == UHDR_SDR_IMG && + 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; + if (img->w != hdr_raw_entry->w || img->h != hdr_raw_entry->h) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "image resolutions mismatch: sdr intent: %dx%d, hdr intent: %dx%d", img->w, img->h, + hdr_raw_entry->w, hdr_raw_entry->h); + return status; + } + } + 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; + } + + std::unique_ptr<ultrahdr::uhdr_raw_image_ext_t> entry = + std::make_unique<ultrahdr::uhdr_raw_image_ext_t>(img->fmt, img->cg, img->ct, img->range, + img->w, img->h, 64); + + if (img->fmt == UHDR_IMG_FMT_12bppYCbCr420) { + uint8_t* y_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_Y]); + uint8_t* y_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_Y]); + uint8_t* u_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_U]); + uint8_t* u_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_U]); + uint8_t* v_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_V]); + uint8_t* v_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_V]); + + // copy y + for (size_t i = 0; i < img->h; i++) { + memcpy(y_dst, y_src, img->w); + y_dst += entry->stride[UHDR_PLANE_Y]; + y_src += img->stride[UHDR_PLANE_Y]; + } + // copy cb & cr + for (size_t i = 0; i < img->h / 2; i++) { + memcpy(u_dst, u_src, img->w / 2); + memcpy(v_dst, v_src, img->w / 2); + u_dst += entry->stride[UHDR_PLANE_U]; + v_dst += entry->stride[UHDR_PLANE_V]; + u_src += img->stride[UHDR_PLANE_U]; + v_src += img->stride[UHDR_PLANE_V]; + } + } else if (img->fmt == UHDR_IMG_FMT_24bppYCbCrP010) { + int bpp = 2; + uint8_t* y_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_Y]); + uint8_t* y_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_Y]); + uint8_t* uv_dst = static_cast<uint8_t*>(entry->planes[UHDR_PLANE_UV]); + uint8_t* uv_src = static_cast<uint8_t*>(img->planes[UHDR_PLANE_UV]); + + // copy y + for (size_t i = 0; i < img->h; i++) { + memcpy(y_dst, y_src, img->w * bpp); + y_dst += (entry->stride[UHDR_PLANE_Y] * bpp); + y_src += (img->stride[UHDR_PLANE_Y] * bpp); + } + // copy cbcr + for (size_t i = 0; i < img->h / 2; i++) { + memcpy(uv_dst, uv_src, img->w * bpp); + uv_dst += (entry->stride[UHDR_PLANE_UV] * bpp); + uv_src += (img->stride[UHDR_PLANE_UV] * bpp); + } + } + + handle->m_raw_images.insert_or_assign(intent, std::move(entry)); + + return status; +} + +uhdr_error_info_t uhdr_enc_set_compressed_image(uhdr_codec_private_t* enc, + uhdr_compressed_image_t* img, + uhdr_img_label_t intent) { + uhdr_error_info_t status = g_no_error; + + if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG}", + intent); + } + + return uhdr_enc_validate_and_set_compressed_img(enc, img, intent); +} + +uhdr_error_info_t uhdr_enc_set_gainmap_image(uhdr_codec_private_t* enc, + uhdr_compressed_image_t* img, + uhdr_gainmap_metadata_t* metadata) { + uhdr_error_info_t status = g_no_error; + + if (metadata == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for gainmap metadata descriptor"); + } else if (metadata->max_content_boost < metadata->min_content_boost) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad value for content boost min %f > max %f", metadata->min_content_boost, + metadata->max_content_boost); + } else if (metadata->gamma <= 0.0f) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received bad value for gamma %f, expects > 0.0f", + metadata->gamma); + } else if (metadata->offset_sdr < 0.0f) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad value for offset sdr %f, expects to be >= 0.0f", metadata->offset_sdr); + } else if (metadata->offset_hdr < 0.0f) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad value for offset hdr %f, expects to be >= 0.0f", metadata->offset_hdr); + } else if (metadata->hdr_capacity_max < metadata->hdr_capacity_min) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad value for hdr capacity min %f > max %f", metadata->hdr_capacity_min, + metadata->hdr_capacity_max); + } else if (metadata->hdr_capacity_min < 1.0f) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received bad value for hdr capacity min %f, expects to be >= 1.0f", + metadata->hdr_capacity_min); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + status = uhdr_enc_validate_and_set_compressed_img(enc, img, UHDR_GAIN_MAP_IMG); + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(enc); + memcpy(&handle->m_metadata, metadata, sizeof *metadata); + + return status; +} + +uhdr_error_info_t uhdr_enc_set_quality(uhdr_codec_private_t* enc, int quality, + uhdr_img_label_t intent) { + uhdr_error_info_t status = g_no_error; + + if (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"); + } else if (quality < 0 || quality > 100) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid quality factor %d, expects in range [0-100]", quality); + } else if (intent != UHDR_HDR_IMG && intent != UHDR_SDR_IMG && intent != UHDR_BASE_IMG && + intent != UHDR_GAIN_MAP_IMG) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid intent %d, expects one of {UHDR_HDR_IMG, UHDR_SDR_IMG, UHDR_BASE_IMG, " + "UHDR_GAIN_MAP_IMG}", + intent); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(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_quality.insert_or_assign(intent, quality); + + return status; +} + +uhdr_error_info_t uhdr_enc_set_exif_data(uhdr_codec_private_t* enc, uhdr_mem_block_t* exif) { + uhdr_error_info_t status = g_no_error; + + if (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"); + } else if (exif == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for exif image handle"); + } else if (exif->data == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for exif->data field"); + } else if (exif->capacity < exif->data_sz) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "exif->capacity %d is less than exif->data_sz %d", + exif->capacity, exif->data_sz); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(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; + } + + uint8_t* data = static_cast<uint8_t*>(exif->data); + std::vector<uint8_t> entry(data, data + exif->data_sz); + handle->m_exif = std::move(entry); + + return status; +} + +uhdr_error_info_t uhdr_enc_set_output_format(uhdr_codec_private_t* enc, uhdr_codec_t media_type) { + uhdr_error_info_t status = g_no_error; + + if (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"); + } else if (media_type != UHDR_CODEC_JPG) { + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid output format %d, expects {UHDR_CODEC_JPG}", media_type); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(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_output_format = media_type; + + return status; +} + +uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc) { + if (enc == 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 uhdr codec instance"); + return status; + } + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(enc); + + if (handle->m_sailed) { + return handle->m_encode_call_status; + } + + handle->m_sailed = true; + + uhdr_error_info_t& status = handle->m_encode_call_status; + + ultrahdr::status_t internal_status = ultrahdr::JPEGR_NO_ERROR; + if (handle->m_output_format == UHDR_CODEC_JPG) { + ultrahdr::jpegr_exif_struct exif{}; + if (handle->m_exif.size() > 0) { + exif.data = handle->m_exif.data(); + exif.length = handle->m_exif.size(); + } + + ultrahdr::JpegR jpegr; + 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)); + handle->m_compressed_output_buffer = + std::move(std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>( + 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; + + // api - 4 + internal_status = jpegr.encodeJPEGR(&primary_image, &gainmap_image, &metadata, &dest); + map_internal_error_status_to_error_info(internal_status, status); + } 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; + + size_t size = (std::max)((8u * 1024), hdr_raw_entry->w * hdr_raw_entry->h * 3 * 2); + handle->m_compressed_output_buffer = + std::move(std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>( + 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.pixelFormat = map_pix_fmt_to_internal_pix_fmt(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); + } 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); + } 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 = map_pix_fmt_to_internal_pix_fmt(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); + } 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); + } + } + 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; +} + +uhdr_compressed_image_t* uhdr_get_encoded_stream(uhdr_codec_private_t* enc) { + if (enc == nullptr) { + return nullptr; + } + + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(enc); + if (!handle->m_sailed || handle->m_encode_call_status.error_code != UHDR_CODEC_OK) { + return nullptr; + } + + return handle->m_compressed_output_buffer.get(); +} + +void uhdr_reset_encoder(uhdr_codec_private_t* enc) { + if (enc != nullptr) { + uhdr_encoder_private* handle = reinterpret_cast<uhdr_encoder_private*>(enc); + + // clear entries and restore defaults + handle->m_raw_images.clear(); + handle->m_compressed_images.clear(); + handle->m_quality.clear(); + handle->m_quality.emplace(UHDR_HDR_IMG, 95); + handle->m_quality.emplace(UHDR_SDR_IMG, 95); + handle->m_quality.emplace(UHDR_BASE_IMG, 95); + handle->m_quality.emplace(UHDR_GAIN_MAP_IMG, 85); + handle->m_exif.clear(); + handle->m_output_format = UHDR_CODEC_JPG; + + handle->m_sailed = false; + handle->m_compressed_output_buffer.reset(); + handle->m_encode_call_status = g_no_error; + } +} + +uhdr_codec_private_t* uhdr_create_decoder(void) { + uhdr_decoder_private* handle = new uhdr_decoder_private(); + + if (handle != nullptr) { + uhdr_reset_decoder(handle); + } + return handle; +} + +void uhdr_release_decoder(uhdr_codec_private_t* dec) { + if (dec != nullptr) { + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + delete handle; + } +} + +uhdr_error_info_t uhdr_dec_set_image(uhdr_codec_private_t* dec, uhdr_compressed_image_t* img) { + uhdr_error_info_t status = g_no_error; + + if (dec == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); + } else if (img == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for compressed image handle"); + } else if (img->data == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "received nullptr for compressed img->data field"); + } else if (img->capacity < img->data_sz) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "img->capacity %d is less than img->data_sz %d", + img->capacity, img->data_sz); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + 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_decode() has switched the context from configurable state to " + "end state. The context is no longer configurable. To reuse, call reset()"); + return status; + } + + handle->m_uhdr_compressed_img = std::make_unique<ultrahdr::uhdr_compressed_image_ext_t>( + img->cg, img->ct, img->range, img->data_sz); + memcpy(handle->m_uhdr_compressed_img->data, img->data, img->data_sz); + handle->m_uhdr_compressed_img->data_sz = img->data_sz; + + return status; +} + +uhdr_error_info_t uhdr_dec_set_out_img_format(uhdr_codec_private_t* dec, uhdr_img_fmt_t fmt) { + uhdr_error_info_t status = g_no_error; + + if (dec == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); + } else if (fmt != UHDR_IMG_FMT_32bppRGBA8888 && fmt != UHDR_IMG_FMT_64bppRGBAHalfFloat && + fmt != UHDR_IMG_FMT_32bppRGBA1010102) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid output format %d, expects one of {UHDR_IMG_FMT_32bppRGBA8888, " + "UHDR_IMG_FMT_64bppRGBAHalfFloat, UHDR_IMG_FMT_32bppRGBA1010102}", + fmt); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + 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_decode() has switched the context from configurable state to " + "end state. The context is no longer configurable. To reuse, call reset()"); + return status; + } + + handle->m_output_fmt = fmt; + + return status; +} + +uhdr_error_info_t uhdr_dec_set_out_color_transfer(uhdr_codec_private_t* dec, + uhdr_color_transfer_t ct) { + uhdr_error_info_t status = g_no_error; + + if (dec == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); + } else if (ct != UHDR_CT_HLG && ct != UHDR_CT_PQ && ct != UHDR_CT_LINEAR && ct != UHDR_CT_SRGB) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid output color transfer %d, expects one of {UHDR_CT_HLG, UHDR_CT_PQ, " + "UHDR_CT_LINEAR, UHDR_CT_SRGB}", + ct); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + 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_decode() has switched the context from configurable state to " + "end state. The context is no longer configurable. To reuse, call reset()"); + return status; + } + + handle->m_output_ct = ct; + + return status; +} + +uhdr_error_info_t uhdr_dec_set_out_max_display_boost(uhdr_codec_private_t* dec, + float display_boost) { + uhdr_error_info_t status = g_no_error; + + if (dec == nullptr) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, "received nullptr for uhdr codec instance"); + } else if (display_boost < 1.0f) { + status.error_code = UHDR_CODEC_INVALID_PARAM; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "invalid display boost %f, expects to be >= 1.0f}", display_boost); + } + if (status.error_code != UHDR_CODEC_OK) return status; + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + 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_decode() has switched the context from configurable state to " + "end state. The context is no longer configurable. To reuse, call reset()"); + return status; + } + + handle->m_output_max_disp_boost = display_boost; + + return status; +} + +uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec) { + if (dec == 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 uhdr codec instance"); + return status; + } + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + + if (handle->m_sailed) { + return handle->m_decode_call_status; + } + + handle->m_sailed = true; + + uhdr_error_info_t& status = handle->m_decode_call_status; + ultrahdr::jpeg_info_struct primary_image; + ultrahdr::jpeg_info_struct gainmap_image; + ultrahdr::jpegr_info_struct jpegr_info; + jpegr_info.width = 0; + jpegr_info.height = 0; + 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); + if (status.error_code != UHDR_CODEC_OK) return status; + + handle->m_exif = std::move(primary_image.exifData); + handle->m_icc = std::move(primary_image.iccData); + handle->m_base_xmp = std::move(primary_image.xmpData); + handle->m_gainmap_xmp = std::move(gainmap_image.xmpData); + + handle->m_decoded_img_buffer = std::make_unique<ultrahdr::uhdr_raw_image_ext_t>( + handle->m_output_fmt, UHDR_CG_UNSPECIFIED, handle->m_output_ct, UHDR_CR_UNSPECIFIED, + primary_image.width, primary_image.height, 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<ultrahdr::uhdr_raw_image_ext_t>( + UHDR_IMG_FMT_8bppYCbCr400, UHDR_CG_UNSPECIFIED, UHDR_CT_UNSPECIFIED, UHDR_CR_UNSPECIFIED, + gainmap_image.width, gainmap_image.height, 1); + // alias + ultrahdr::jpegr_uncompressed_struct dest_gainmap; + dest_gainmap.data = handle->m_gainmap_img_buffer->planes[UHDR_PLANE_Y]; + + ultrahdr::ultrahdr_metadata_struct metadata; + internal_status = jpegr.decodeJPEGR( + &uhdr_image, &dest, handle->m_output_max_disp_boost, nullptr, + map_ct_fmt_to_internal_output_fmt(handle->m_output_ct, handle->m_output_fmt), &dest_gainmap, + &metadata); + 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); + + 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; + } + + return status; +} + +uhdr_raw_image_t* uhdr_get_decoded_image(uhdr_codec_private_t* dec) { + if (dec == nullptr) { + return nullptr; + } + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) { + return nullptr; + } + + return handle->m_decoded_img_buffer.get(); +} + +uhdr_raw_image_t* uhdr_get_gain_map_image(uhdr_codec_private_t* dec) { + if (dec == nullptr) { + return nullptr; + } + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) { + return nullptr; + } + + return handle->m_gainmap_img_buffer.get(); +} + +uhdr_gainmap_metadata_t* uhdr_get_gain_map_metadata(uhdr_codec_private_t* dec) { + if (dec == nullptr) { + return nullptr; + } + + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + if (!handle->m_sailed || handle->m_decode_call_status.error_code != UHDR_CODEC_OK) { + return nullptr; + } + + return &handle->m_metadata; +} + +void uhdr_reset_decoder(uhdr_codec_private_t* dec) { + if (dec != nullptr) { + uhdr_decoder_private* handle = reinterpret_cast<uhdr_decoder_private*>(dec); + + // clear entries and restore defaults + handle->m_uhdr_compressed_img.reset(); + handle->m_output_fmt = UHDR_IMG_FMT_64bppRGBAHalfFloat; + handle->m_output_ct = UHDR_CT_LINEAR; + handle->m_output_max_disp_boost = FLT_MAX; + + // ready to be configured + handle->m_sailed = false; + handle->m_decoded_img_buffer.reset(); + handle->m_gainmap_img_buffer.reset(); + handle->m_exif.clear(); + handle->m_icc.clear(); + handle->m_base_xmp.clear(); + handle->m_gainmap_xmp.clear(); + memset(&handle->m_metadata, 0, sizeof handle->m_metadata); + handle->m_decode_call_status = g_no_error; + } +} diff --git a/tests/jpegr_test.cpp b/tests/jpegr_test.cpp index 0b420e6..fb66fe1 100644 --- a/tests/jpegr_test.cpp +++ b/tests/jpegr_test.cpp @@ -24,6 +24,8 @@ #include <fstream> #include <iostream> +#include "ultrahdr_api.h" + #include "ultrahdr/ultrahdrcommon.h" #include "ultrahdr/jpegr.h" #include "ultrahdr/jpegrutils.h" @@ -301,6 +303,34 @@ static bool readFile(const char* fileName, void*& result, int maxLength, int& le return false; } +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; + } +} + +uhdr_color_transfer_t map_internal_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; + } +} + void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) { jpegr_info_struct info{}; JpegR jpegHdr; @@ -319,6 +349,25 @@ void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileN std::cerr << "unable to write output file" << std::endl; } #endif + uhdr_codec_private_t* obj = uhdr_create_decoder(); + uhdr_compressed_image_t uhdr_image{}; + uhdr_image.data = img->data; + uhdr_image.data_sz = img->length; + uhdr_image.capacity = img->length; + uhdr_image.cg = UHDR_CG_UNSPECIFIED; + uhdr_image.ct = UHDR_CT_UNSPECIFIED; + uhdr_image.range = UHDR_CR_UNSPECIFIED; + uhdr_error_info_t status = uhdr_dec_set_image(obj, &uhdr_image); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_decode(obj); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + uhdr_raw_image_t* raw_image = uhdr_get_decoded_image(obj); + ASSERT_NE(nullptr, raw_image); + ASSERT_EQ(map_internal_cg_to_cg(destImage.colorGamut), raw_image->cg); + ASSERT_EQ(destImage.width, raw_image->w); + ASSERT_EQ(destImage.height, raw_image->h); + ASSERT_EQ(0, memcmp(destImage.data, raw_image->planes[0], outSize)); + uhdr_release_decoder(obj); } // ============================================================================ @@ -1398,6 +1447,33 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { uHdrLib.encodeJPEGR(rawImg.getImageHandle(), ultrahdr_transfer_function::ULTRAHDR_TF_HLG, jpgImg.getImageHandle(), kQuality, nullptr), JPEGR_NO_ERROR); + + uhdr_codec_private_t* obj = uhdr_create_encoder(); + uhdr_raw_image_t uhdrRawImg{}; + uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImg.getImageHandle()->data; + uhdrRawImg.stride[0] = kImageWidth; + uhdrRawImg.planes[1] = + ((uint8_t*)(rawImg.getImageHandle()->data)) + kImageWidth * kImageHeight * 2; + uhdrRawImg.stride[1] = kImageWidth; + uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_encode(obj); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj); + ASSERT_NE(nullptr, compressedImage); + ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz); + ASSERT_EQ(0, + memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz)); + uhdr_release_encoder(obj); + // encode with luma stride set { UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010); @@ -1434,6 +1510,30 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) { auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); + + uhdr_codec_private_t* obj = uhdr_create_encoder(); + uhdr_raw_image_t uhdrRawImg{}; + uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImg2.getImageHandle()->data; + uhdrRawImg.stride[0] = rawImg2.getImageHandle()->luma_stride; + uhdrRawImg.planes[1] = rawImg2.getImageHandle()->chroma_data; + uhdrRawImg.stride[1] = rawImg2.getImageHandle()->chroma_stride; + uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_encode(obj); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj); + ASSERT_NE(nullptr, compressedImage); + ASSERT_EQ(jpg1->length, compressedImage->data_sz); + ASSERT_EQ(0, memcmp(jpg1->data, compressedImage->data, jpg1->length)); + uhdr_release_encoder(obj); } // encode with chroma stride set { @@ -1610,6 +1710,49 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) { auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); + + uhdr_codec_private_t* obj = uhdr_create_encoder(); + uhdr_raw_image_t uhdrRawImg{}; + uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImgP010.getImageHandle()->data; + uhdrRawImg.stride[0] = kImageWidth; + uhdrRawImg.planes[1] = + ((uint8_t*)(rawImgP010.getImageHandle()->data)) + kImageWidth * kImageHeight * 2; + uhdrRawImg.stride[1] = kImageWidth; + uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + uhdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420; + uhdrRawImg.cg = map_internal_cg_to_cg(mYuv420ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_SRGB); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImg2420.getImageHandle()->data; + uhdrRawImg.stride[0] = rawImg2420.getImageHandle()->luma_stride; + uhdrRawImg.planes[1] = rawImg2420.getImageHandle()->chroma_data; + uhdrRawImg.stride[1] = rawImg2420.getImageHandle()->chroma_stride; + uhdrRawImg.planes[2] = ((uint8_t*)(rawImg2420.getImageHandle()->chroma_data)) + + rawImg2420.getImageHandle()->chroma_stride * kImageHeight / 2; + uhdrRawImg.stride[2] = rawImg2420.getImageHandle()->chroma_stride; + status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_SDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_encode(obj); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj); + ASSERT_NE(nullptr, compressedImage); + ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz); + ASSERT_EQ( + 0, memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz)); + uhdr_release_encoder(obj); } // encode with chroma stride set 420 { @@ -1773,6 +1916,59 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) { auto jpg2 = jpgImg2.getImageHandle(); ASSERT_EQ(jpg1->length, jpg2->length); ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); + + uhdr_codec_private_t* obj = uhdr_create_encoder(); + uhdr_raw_image_t uhdrRawImg{}; + uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImgP010.getImageHandle()->data; + uhdrRawImg.stride[0] = kImageWidth; + uhdrRawImg.planes[1] = + ((uint8_t*)(rawImgP010.getImageHandle()->data)) + kImageWidth * kImageHeight * 2; + uhdrRawImg.stride[1] = kImageWidth; + uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + uhdrRawImg.fmt = UHDR_IMG_FMT_12bppYCbCr420; + uhdrRawImg.cg = map_internal_cg_to_cg(mYuv420ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_SRGB); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImg2420.getImageHandle()->data; + uhdrRawImg.stride[0] = rawImg2420.getImageHandle()->luma_stride; + uhdrRawImg.planes[1] = rawImg2420.getImageHandle()->chroma_data; + uhdrRawImg.stride[1] = rawImg2420.getImageHandle()->chroma_stride; + uhdrRawImg.planes[2] = ((uint8_t*)(rawImg2420.getImageHandle()->chroma_data)) + + rawImg2420.getImageHandle()->chroma_stride * kImageHeight / 2; + uhdrRawImg.stride[2] = rawImg2420.getImageHandle()->chroma_stride; + status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_SDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + uhdr_compressed_image_t uhdrCompressedImg; + uhdrCompressedImg.data = sdr->data; + uhdrCompressedImg.data_sz = sdr->length; + uhdrCompressedImg.capacity = sdr->length; + uhdrCompressedImg.cg = map_internal_cg_to_cg(sdr->colorGamut); + uhdrCompressedImg.ct = UHDR_CT_UNSPECIFIED; + uhdrCompressedImg.range = UHDR_CR_UNSPECIFIED; + status = uhdr_enc_set_compressed_image(obj, &uhdrCompressedImg, UHDR_SDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_encode(obj); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj); + ASSERT_NE(nullptr, compressedImage); + ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz); + ASSERT_EQ( + 0, memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz)); + uhdr_release_encoder(obj); } // encode with chroma stride set { @@ -1897,6 +2093,45 @@ TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) { ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length)); } + { + uhdr_codec_private_t* obj = uhdr_create_encoder(); + uhdr_raw_image_t uhdrRawImg{}; + uhdrRawImg.fmt = UHDR_IMG_FMT_24bppYCbCrP010; + uhdrRawImg.cg = map_internal_cg_to_cg(mP010ColorGamut); + uhdrRawImg.ct = map_internal_ct_to_ct(ultrahdr_transfer_function::ULTRAHDR_TF_HLG); + uhdrRawImg.range = UHDR_CR_UNSPECIFIED; + uhdrRawImg.w = kImageWidth; + uhdrRawImg.h = kImageHeight; + uhdrRawImg.planes[0] = rawImgP010.getImageHandle()->data; + uhdrRawImg.stride[0] = kImageWidth; + uhdrRawImg.planes[1] = + ((uint8_t*)(rawImgP010.getImageHandle()->data)) + kImageWidth * kImageHeight * 2; + uhdrRawImg.stride[1] = kImageWidth; + uhdr_error_info_t status = uhdr_enc_set_raw_image(obj, &uhdrRawImg, UHDR_HDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + uhdr_compressed_image_t uhdrCompressedImg; + uhdrCompressedImg.data = sdr->data; + uhdrCompressedImg.data_sz = sdr->length; + uhdrCompressedImg.capacity = sdr->length; + uhdrCompressedImg.cg = map_internal_cg_to_cg(sdr->colorGamut); + uhdrCompressedImg.ct = UHDR_CT_UNSPECIFIED; + uhdrCompressedImg.range = UHDR_CR_UNSPECIFIED; + status = uhdr_enc_set_compressed_image(obj, &uhdrCompressedImg, UHDR_SDR_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + + status = uhdr_enc_set_quality(obj, kQuality, UHDR_BASE_IMG); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + status = uhdr_encode(obj); + ASSERT_EQ(UHDR_CODEC_OK, status.error_code) << status.detail; + uhdr_compressed_image_t* compressedImage = uhdr_get_encoded_stream(obj); + ASSERT_NE(nullptr, compressedImage); + ASSERT_EQ(jpgImg.getImageHandle()->length, compressedImage->data_sz); + ASSERT_EQ( + 0, memcmp(jpgImg.getImageHandle()->data, compressedImage->data, compressedImage->data_sz)); + uhdr_release_encoder(obj); + } + auto jpg1 = jpgImg.getImageHandle(); #ifdef DUMP_OUTPUT diff --git a/ultrahdr_api.h b/ultrahdr_api.h new file mode 100644 index 0000000..889877f --- /dev/null +++ b/ultrahdr_api.h @@ -0,0 +1,515 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** \file ultrahdr_api.h + * + * \brief + * Describes the encoder or decoder algorithm interface to applications. + */ + +#ifndef ULTRAHDR_API_H +#define ULTRAHDR_API_H + +#ifdef __cplusplus +#define UHDR_EXTERN extern "C" +#else +#define UHDR_EXTERN extern +#endif + +// =============================================================================================== +// Enum Definitions +// =============================================================================================== + +/*!\brief List of supported image formats */ +typedef enum uhdr_img_fmt { + UHDR_IMG_FMT_UNSPECIFIED = -1, /**< Unspecified */ + UHDR_IMG_FMT_24bppYCbCrP010, /**< 10-bit-per component 4:2:0 YCbCr semiplanar format. + Each chroma and luma component has 16 allocated bits in + little-endian configuration with 10 MSB of actual data.*/ + UHDR_IMG_FMT_12bppYCbCr420, /**< 8-bit-per component 4:2:0 YCbCr planar format */ + UHDR_IMG_FMT_8bppYCbCr400, /**< 8-bit-per component Monochrome format */ + UHDR_IMG_FMT_32bppRGBA8888, /**< 32 bits per pixel RGBA color format, with 8-bit red, green, blue + and alpha components. Using 32-bit little-endian representation, + colors stored as Red 7:0, Green 15:8, Blue 23:16, Alpha 31:24. */ + UHDR_IMG_FMT_64bppRGBAHalfFloat, /**< 64 bits per pixel RGBA color format, with 16-bit signed + floating point red, green, blue, and alpha components */ + UHDR_IMG_FMT_32bppRGBA1010102, /**< 32 bits per pixel RGBA color format, with 10-bit red, green, + blue, and 2-bit alpha components. Using 32-bit little-endian + representation, colors stored as Red 9:0, Green 19:10, Blue + 29:20, and Alpha 31:30. */ +} uhdr_img_fmt_t; /**< alias for enum uhdr_img_fmt */ + +/*!\brief List of supported color gamuts */ +typedef enum uhdr_color_gamut { + UHDR_CG_UNSPECIFIED = -1, /**< Unspecified */ + UHDR_CG_BT_709, /**< BT.709 */ + UHDR_CG_DISPLAY_P3, /**< Display P3 */ + UHDR_CG_BT_2100, /**< BT.2100 */ +} uhdr_color_gamut_t; /**< alias for enum uhdr_color_gamut */ + +/*!\brief List of supported color transfers */ +typedef enum uhdr_color_transfer { + UHDR_CT_UNSPECIFIED = -1, /**< Unspecified */ + UHDR_CT_LINEAR, /**< Linear */ + UHDR_CT_HLG, /**< Hybrid log gamma */ + UHDR_CT_PQ, /**< Perceptual Quantizer */ + UHDR_CT_SRGB, /**< Gamma */ +} uhdr_color_transfer_t; /**< alias for enum uhdr_color_transfer */ + +/*!\brief List of supported color ranges */ +typedef enum uhdr_color_range { + UHDR_CR_UNSPECIFIED = -1, /**< Unspecified */ + UHDR_CR_LIMITED_RANGE, /**< Y {[16..235], UV [16..240]} * pow(2, (bpc - 8)) */ + UHDR_CR_FULL_RANGE, /**< YUV/RGB {[0..255]} * pow(2, (bpc - 8)) */ +} uhdr_color_range_t; /**< alias for enum uhdr_color_range */ + +/*!\brief List of supported codecs */ +typedef enum uhdr_codec { + UHDR_CODEC_JPG, /**< Compress {Hdr, Sdr rendition} to an {Sdr rendition + Gain Map} using + jpeg */ +} uhdr_codec_t; /**< alias for enum uhdr_codec */ + +/*!\brief Image identifiers in gain map technology */ +typedef enum uhdr_img_label { + UHDR_HDR_IMG, /**< Hdr rendition image */ + UHDR_SDR_IMG, /**< Sdr rendition image */ + UHDR_BASE_IMG, /**< Base rendition image */ + UHDR_GAIN_MAP_IMG, /**< Gain map image */ +} uhdr_img_label_t; /**< alias for enum uhdr_img_label */ + +/*!\brief Algorithm return codes */ +typedef enum uhdr_codec_err { + + /*!\brief Operation completed without error */ + UHDR_CODEC_OK, + + /*!\brief Unspecified error */ + UHDR_CODEC_UNKNOWN_ERROR, + + /*!\brief An application-supplied parameter is not valid. */ + UHDR_CODEC_INVALID_PARAM, + + /*!\brief Memory operation failed */ + UHDR_CODEC_MEM_ERROR, + + /*!\brief An application-invoked operation is not valid. */ + UHDR_CODEC_INVALID_OPERATION, + + /*!\brief The library does not implement a feature required for the operation */ + UHDR_CODEC_UNSUPPORTED_FEATURE, + + /*!\brief An iterator reached the end of list. */ + UHDR_CODEC_LIST_END, + +} uhdr_codec_err_t; /**< alias for enum uhdr_codec_err */ + +// =============================================================================================== +// Structure Definitions +// =============================================================================================== + +/*!\brief Detailed return status */ +typedef struct uhdr_error_info { + uhdr_codec_err_t error_code; + int has_detail; + char detail[128]; +} uhdr_error_info_t; /**< alias for struct uhdr_error_info */ + +/**\brief Raw Image Descriptor */ +typedef struct uhdr_raw_image { + /* Color model, primaries, transfer, range */ + uhdr_img_fmt_t fmt; /**< Image Format */ + uhdr_color_gamut_t cg; /**< Color Gamut */ + uhdr_color_transfer_t ct; /**< Color Transfer */ + uhdr_color_range_t range; /**< Color Range */ + + /* Image storage dimensions */ + unsigned int w; /**< Stored image width */ + unsigned int h; /**< Stored image height */ + + /* Image data pointers. */ +#define UHDR_PLANE_PACKED 0 /**< To be used for all packed formats */ +#define UHDR_PLANE_Y 0 /**< Y (Luminance) plane */ +#define UHDR_PLANE_U 1 /**< U (Chroma) plane */ +#define UHDR_PLANE_UV 1 /**< UV (Chroma plane interleaved) To be used for semi planar format */ +#define UHDR_PLANE_V 2 /**< V (Chroma) plane */ + void* planes[3]; /**< pointer to the top left pixel for each plane */ + unsigned int stride[3]; /**< stride in pixels between rows for each plane */ +} uhdr_raw_image_t; /**< alias for struct uhdr_raw_image */ + +/**\brief Compressed Image Descriptor */ +typedef struct uhdr_compressed_image { + void* data; /**< Pointer to a block of data to decode */ + unsigned int data_sz; /**< size of the data buffer */ + unsigned int capacity; /**< maximum size of the data buffer */ + uhdr_color_gamut_t cg; /**< Color Gamut */ + uhdr_color_transfer_t ct; /**< Color Transfer */ + uhdr_color_range_t range; /**< Color Range */ +} uhdr_compressed_image_t; /**< alias for struct uhdr_compressed_image */ + +/**\brief Buffer Descriptor */ +typedef struct uhdr_mem_block { + void* data; /**< Pointer to a block of data to decode */ + unsigned int data_sz; /**< size of the data buffer */ + unsigned int capacity; /**< maximum size of the data buffer */ +} uhdr_mem_block_t; /**< alias for struct uhdr_mem_block */ + +/**\brief Gain map metadata. + * Note: all values stored in linear space. This differs from the metadata encoded in XMP, where + * max_content_boost (aka gainMapMax), min_content_boost (aka gainMapMin), hdr_capacity_min, and + * hdr_capacity_max are stored in log2 space. + */ +typedef struct uhdr_gainmap_metadata { + float max_content_boost; /**< Max Content Boost for the map */ + float min_content_boost; /**< Min Content Boost for the map */ + float gamma; /**< Gamma of the map data */ + float offset_sdr; /**< Offset for SDR data in map calculations */ + float offset_hdr; /**< Offset for HDR data in map calculations */ + float hdr_capacity_min; /**< Min HDR capacity values for interpolating the Gain Map */ + float hdr_capacity_max; /**< Max HDR capacity value for interpolating the Gain Map */ +} uhdr_gainmap_metadata_t; /**< alias for struct uhdr_gainmap_metadata */ + +/**\brief ultrahdr codec context opaque descriptor */ +typedef struct uhdr_codec_private uhdr_codec_private_t; + +// =============================================================================================== +// Function Declarations +// =============================================================================================== + +// =============================================================================================== +// Encoder APIs +// =============================================================================================== + +/*!\brief Create a new encoder instance. The instance is initialized with default settings. + * To override the settings use uhdr_enc_set_*() + * + * \return nullptr if there was an error allocating memory else a fresh opaque encoder handle + */ +UHDR_EXTERN uhdr_codec_private_t* uhdr_create_encoder(void); + +/*!\brief Release encoder instance. + * Frees all allocated storage associated with encoder instance. + * + * \param[in] enc encoder instance. + * + * \return none + */ +UHDR_EXTERN void uhdr_release_encoder(uhdr_codec_private_t* enc); + +/*!\brief Add raw image descriptor to encoder context. The function goes through all the fields of + * the image descriptor and checks for their sanity. If no anomalies are seen then the image is + * added to internal list. Repeated calls to this function will replace the old entry with the + * current. + * + * \param[in] enc encoder instance. + * \param[in] img image descriptor. + * \param[in] intent UHDR_HDR_IMG for hdr intent and UHDR_SDR_IMG for sdr intent. + * + * \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_raw_image(uhdr_codec_private_t* enc, + uhdr_raw_image_t* img, + uhdr_img_label_t intent); + +/*!\brief Add compressed image descriptor to encoder context. The function goes through all the + * fields of the image descriptor and checks for their sanity. If no anomalies are seen then the + * image is added to internal list. Repeated calls to this function will replace the old entry with + * the current. + * + * If both uhdr_enc_add_raw_image() and uhdr_enc_add_compressed_image() are called during a session + * for the same intent, it is assumed that raw image descriptor and compressed image descriptor are + * relatable via compress <-> decompress process. + * + * \param[in] enc encoder instance. + * \param[in] img image descriptor. + * \param[in] intent UHDR_HDR_IMG for hdr intent, + * UHDR_SDR_IMG for sdr intent, + * UHDR_BASE_IMG for base image intent + * + * \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_compressed_image(uhdr_codec_private_t* enc, + uhdr_compressed_image_t* img, + uhdr_img_label_t intent); + +/*!\brief Add gain map image descriptor and gainmap metadata info to encoder context. The function + * internally goes through all the fields of the image descriptor and checks for their sanity. If no + * anomalies are seen then the image is added to internal list. Repeated calls to this function will + * replace the old entry with the current. + * + * \param[in] enc encoder instance. + * \param[in] img gain map image desciptor. + * \param[in] metadata gainmap metadata descriptor + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, + * #UHDR_CODEC_INVALID_PARAM otherwise. + */ +uhdr_error_info_t uhdr_enc_set_gainmap_image(uhdr_codec_private_t* enc, + uhdr_compressed_image_t* img, + uhdr_gainmap_metadata_t* metadata); + +/*!\brief Set quality for compression + * + * \param[in] enc encoder instance. + * \param[in] quality quality factor. + * \param[in] intent UHDR_BASE_IMG for base image and UHDR_GAIN_MAP_IMG for gain map image. + * + * \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_quality(uhdr_codec_private_t* enc, int quality, + uhdr_img_label_t intent); + +/*!\brief Set Exif data that needs to be inserted in the output compressed stream + * + * \param[in] enc encoder instance. + * \param[in] img exif data descriptor. + * + * \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_exif_data(uhdr_codec_private_t* enc, + uhdr_mem_block_t* exif); + +/*!\brief Set output image compression format. + * + * \param[in] enc encoder instance. + * \param[in] media_type output image compression format. + * + * \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_output_format(uhdr_codec_private_t* enc, + uhdr_codec_t media_type); + +/*!\brief Encode process call + * After initializing the encoder context, call to this function will submit data for encoding. If + * the call is successful, the encoded output is stored internally and is accessible via + * uhdr_get_encoded_stream(). + * + * The basic usage of uhdr encoder is as follows: + * - The program creates an instance of an encoder using, + * - uhdr_create_encoder(). + * - The program registers input images to the encoder using, + * - uhdr_enc_set_raw_image(ctxt, img, UHDR_HDR_IMG) + * - uhdr_enc_set_raw_image(ctxt, img, UHDR_SDR_IMG) + * - The program overrides the default settings using uhdr_enc_set_*() functions + * - If the application wants to control the compression level + * - uhdr_enc_set_quality() + * - If the application wants to insert exif data + * - uhdr_enc_set_exif_data() + * - 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 + * computing gain map from hdr intent and sdr intent. The sdr intent and gain map image are + * compressed at the set quality using the codec of choice. + * - On success, the program can access the encoded output with uhdr_get_encoded_stream(). + * - The program finishes the encoding with uhdr_release_encoder(). + * + * The library allows setting Hdr and/or Sdr intent in compressed format, + * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_HDR_IMG) + * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_SDR_IMG) + * In this mode, the compressed image(s) are first decoded to raw image(s). These raw image(s) go + * through the aforth mentioned gain map computation and encoding process. In this case, the usage + * shall be like this: + * - uhdr_create_encoder() + * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_HDR_IMG) + * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_SDR_IMG) + * - uhdr_encode() + * - uhdr_get_encoded_stream() + * - uhdr_release_encoder() + * If the set compressed image media type of intent UHDR_SDR_IMG and output media type are + * identical, then this image is directly used for primary image. No re-encode of raw image is done. + * This implies base image quality setting is un-used. Only gain map image is encoded at the set + * quality using codec of choice. On the other hand, if the set compressed image media type and + * output media type are different, then transcoding is done. + * + * The library also allows directly setting base and gain map image in compressed format, + * - uhdr_enc_set_compressed_image(ctxt, img, UHDR_BASE_IMG) + * - uhdr_enc_set_gainmap_image(ctxt, img, metadata) + * In this mode, gain map computation is by-passed. The input images are transcoded (if necessary), + * combined and sent back. + * + * It is possible to create a uhdr image solely from Hdr intent. In this case, the usage shall look + * like this: + * - uhdr_create_encoder() + * - uhdr_enc_set_raw_image(ctxt, img, UHDR_HDR_IMG) + * - uhdr_enc_set_quality() // optional + * - uhdr_enc_set_exif_data() // optional + * - uhdr_enc_set_output_format() // optional + * - uhdr_encode() + * - uhdr_get_encoded_stream() + * - uhdr_release_encoder() + * In this mode, the Sdr rendition is created from Hdr intent by tone-mapping. The tone-mapped sdr + * image and hdr image go through the aforth mentioned gain map computation and encoding process to + * create uhdr image. + * + * In all modes, Exif data is inserted if requested. + * + * \param[in] enc encoder instance. + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ +UHDR_EXTERN uhdr_error_info_t uhdr_encode(uhdr_codec_private_t* enc); + +/*!\brief Get encoded ultra hdr stream + * + * \param[in] enc encoder instance. + * + * \return nullptr if encode process call is unsuccessful, uhdr image descriptor otherwise + */ +UHDR_EXTERN uhdr_compressed_image_t* uhdr_get_encoded_stream(uhdr_codec_private_t* enc); + +/*!\brief Reset encoder instance. + * Clears all previous settings and resets to default state and ready for re-initialization + * + * \param[in] enc encoder instance. + * + * \return none + */ +UHDR_EXTERN void uhdr_reset_encoder(uhdr_codec_private_t* enc); + +// =============================================================================================== +// Decoder APIs +// =============================================================================================== + +/*!\brief Create a new decoder instance. The instance is initialized with default settings. + * To override the settings use uhdr_dec_set_*() + * + * \return nullptr if there was an error allocating memory else a fresh opaque decoder handle + */ +UHDR_EXTERN uhdr_codec_private_t* uhdr_create_decoder(void); + +/*!\brief Release decoder instance. + * Frees all allocated storage associated with decoder instance. + * + * \param[in] dec decoder instance. + * + * \return none + */ +UHDR_EXTERN void uhdr_release_decoder(uhdr_codec_private_t* dec); + +/*!\brief Add compressed image descriptor to decoder context. The function goes through all the + * fields of the image descriptor and checks for their sanity. If no anomalies are seen then the + * image is added to internal list. Repeated calls to this function will replace the old entry with + * the current. + * + * \param[in] dec decoder instance. + * \param[in] img image descriptor. + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, + * #UHDR_CODEC_INVALID_PARAM otherwise. + */ +UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_image(uhdr_codec_private_t* dec, + uhdr_compressed_image_t* img); + +/*!\brief Set output image format + * + * \param[in] dec decoder instance. + * \param[in] fmt output image format. + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, + * #UHDR_CODEC_INVALID_PARAM otherwise. + */ +UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_out_img_format(uhdr_codec_private_t* dec, + uhdr_img_fmt_t fmt); + +/*!\brief Set output color transfer + * + * \param[in] dec decoder instance. + * \param[in] ct output color transfer + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, + * #UHDR_CODEC_INVALID_PARAM otherwise. + */ +UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_out_color_transfer(uhdr_codec_private_t* dec, + uhdr_color_transfer_t ct); + +/*!\brief Set output max display boost + * + * \param[in] dec decoder instance. + * \param[in] display_boost max display boost + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, + * #UHDR_CODEC_INVALID_PARAM otherwise. + */ +UHDR_EXTERN uhdr_error_info_t uhdr_dec_set_out_max_display_boost(uhdr_codec_private_t* dec, + float display_boost); + +/*!\brief Decode process call + * After initializing the decoder context, call to this function will submit data for decoding. If + * the call is successful, the decoded output is stored internally and is accessible via + * uhdr_get_decoded_image(). + * + * The basic usage of uhdr decoder is as follows: + * - The program creates an instance of a decoder using, + * - uhdr_create_decoder(). + * - The program registers input images to the decoder using, + * - uhdr_dec_set_image(ctxt, img) + * - The program overrides the default settings using uhdr_dec_set_*() functions. + * - If the application wants to control the output image format, + * - uhdr_dec_set_out_img_format() + * - If the application wants to control the output transfer characteristics, + * - uhdr_dec_set_out_color_transfer() + * - If the application wants to control the output display boost, + * - uhdr_dec_set_out_max_display_boost() + * - The program calls uhdr_decompress() to decode uhdr stream. This call would initiate the process + * of decoding base image and gain map image. These two are combined to give the final rendition + * image. + * - The program can access the decoded output with uhdr_get_decoded_image(). + * - The program finishes the decoding with uhdr_release_decoder(). + * + * \param[in] dec decoder instance. + * + * \return uhdr_error_info_t #UHDR_CODEC_OK if operation succeeds, uhdr_codec_err_t otherwise. + */ +UHDR_EXTERN uhdr_error_info_t uhdr_decode(uhdr_codec_private_t* dec); + +/*!\brief Get final rendition image + * + * \param[in] dec decoder instance. + * + * \return nullptr if decoded process call is unsuccessful, raw image descriptor otherwise + */ +UHDR_EXTERN uhdr_raw_image_t* uhdr_get_decoded_image(uhdr_codec_private_t* dec); + +/*!\brief Get gain map image + * + * \param[in] dec decoder instance. + * + * \return nullptr if decoded process call is unsuccessful, raw image descriptor otherwise + */ +UHDR_EXTERN uhdr_raw_image_t* uhdr_get_gain_map_image(uhdr_codec_private_t* dec); + +/*!\brief Get gain map metadata + * + * \param[in] dec decoder instance. + * + * \return nullptr if decoded process call is unsuccessful, gainmap metadata descriptor otherwise + */ +UHDR_EXTERN uhdr_gainmap_metadata_t* uhdr_get_gain_map_metadata(uhdr_codec_private_t* dec); + +/*!\brief Reset decoder instance. + * Clears all previous settings and resets to default state and ready for re-initialization + * + * \param[in] dec decoder instance. + * + * \return none + */ +UHDR_EXTERN void uhdr_reset_decoder(uhdr_codec_private_t* dec); + +#endif // ULTRAHDR_API_H |