diff --git a/lib/include/ultrahdr/gainmapmath.h b/lib/include/ultrahdr/gainmapmath.h index a8f0b35e..762c1e59 100644 --- a/lib/include/ultrahdr/gainmapmath.h +++ b/lib/include/ultrahdr/gainmapmath.h @@ -595,6 +595,8 @@ uhdr_error_info_t copy_raw_image(uhdr_raw_image_t* src, uhdr_raw_image_t* dst); std::unique_ptr convert_raw_input_to_ycbcr( uhdr_raw_image_t* src, bool chroma_sampling_enabled = false); +uhdr_error_info_t convert_ycbcr_input_to_rgb(uhdr_raw_image_t* src, uhdr_raw_image_t* dst); + bool floatToSignedFraction(float v, int32_t* numerator, uint32_t* denominator); bool floatToUnsignedFraction(float v, uint32_t* numerator, uint32_t* denominator); diff --git a/lib/src/gainmapmath.cpp b/lib/src/gainmapmath.cpp index 6b7d3cad..1820c30d 100644 --- a/lib/src/gainmapmath.cpp +++ b/lib/src/gainmapmath.cpp @@ -1526,6 +1526,72 @@ std::unique_ptr convert_raw_input_to_ycbcr(uhdr_raw_image_ return dst; } +uhdr_error_info_t convert_ycbcr_input_to_rgb(uhdr_raw_image_t* src, uhdr_raw_image_t* dst) { + if (dst->w != src->w || dst->h != src->h) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_MEM_ERROR; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "destination image dimensions %dx%d and source image dimensions %dx%d are not " + "identical for copy_raw_image", + dst->w, dst->h, src->w, src->h); + return status; + } + + if (isPixelFormatRgb(src->fmt) || !isPixelFormatRgb(dst->fmt)) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "Unexpected source or destination color format, src fmt %d, dst fmt %d", src->fmt, + dst->fmt); + return status; + } + + GetPixelFn get_pixel_fn = getPixelFn(src->fmt); + if (get_pixel_fn == nullptr) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "No implementation available for reading pixels for color format %d", src->fmt); + return status; + } + + PutPixelFn put_pixel_fn = putPixelFn(dst->fmt); + if (put_pixel_fn == nullptr) { + uhdr_error_info_t status; + status.error_code = UHDR_CODEC_UNSUPPORTED_FEATURE; + status.has_detail = 1; + snprintf(status.detail, sizeof status.detail, + "No implementation available for writing pixels for color format %d", dst->fmt); + return status; + } + + Color (*yuvToRgb)(Color) = nullptr; + if (src->cg == UHDR_CG_BT_709) { + yuvToRgb = srgbYuvToRgb; + } else if (src->cg == UHDR_CG_BT_2100) { + yuvToRgb = bt2100YuvToRgb; + } else if (src->cg == UHDR_CG_DISPLAY_P3) { + yuvToRgb = p3YuvToRgb; + } else { + yuvToRgb = Bt601YuvToRgb; + } + + dst->cg = src->cg; + dst->ct = src->ct; + dst->range = UHDR_CR_FULL_RANGE; + for (unsigned int i = 0; i < src->h; i++) { + for (unsigned int j = 0; j < src->w; j++) { + Color yuv = get_pixel_fn(src, j, i); + Color rgb = yuvToRgb(yuv); + put_pixel_fn(dst, j, i, rgb); + } + } + return g_no_error; +} + std::unique_ptr copy_raw_image(uhdr_raw_image_t* src) { std::unique_ptr dst = std::make_unique( src->fmt, src->cg, src->ct, src->range, src->w, src->h, 64); diff --git a/lib/src/gpu/applygainmap_gl.cpp b/lib/src/gpu/applygainmap_gl.cpp index 934a93dd..68b4ba48 100644 --- a/lib/src/gpu/applygainmap_gl.cpp +++ b/lib/src/gpu/applygainmap_gl.cpp @@ -98,13 +98,46 @@ static const std::string getYuv420PixelShader = R"__SHADER__( } )__SHADER__"; +static const std::string Bt601YUVToRGBShader = R"__SHADER__( + vec3 yuvToRgb(const vec3 color) { + const vec3 offset = vec3(0.0, 128.0f / 255.0f, 128.0f / 255.0f); + const mat3 transform = mat3( + 1.0, 1.0, 1.0, + 0.0, -0.34414, 1.772, + 1.402, -0.71414, 0.0); + return clamp(transform * (color - offset), 0.0, 1.0); + } +)__SHADER__"; + +static const std::string Bt709YUVToRGBShader = R"__SHADER__( + vec3 yuvToRgb(const vec3 color) { + const vec3 offset = vec3(0.0, 128.0f / 255.0f, 128.0f / 255.0f); + const mat3 transform = mat3( + 1.0, 1.0, 1.0, + 0.0, -0.18732, 1.8556, + 1.5748, -0.46812, 0.0); + return clamp(transform * (color - offset), 0.0, 1.0); + } +)__SHADER__"; + static const std::string p3YUVToRGBShader = R"__SHADER__( - vec3 p3YuvToRgb(const vec3 color) { + vec3 yuvToRgb(const vec3 color) { const vec3 offset = vec3(0.0, 128.0f / 255.0f, 128.0f / 255.0f); const mat3 transform = mat3( 1.0, 1.0, 1.0, - 0.0, -0.344136286, 1.772, - 1.402, -0.714136286, 0.0); + 0.0, -0.21106, 1.841426, + 1.542051, -0.51044, 0.0); + return clamp(transform * (color - offset), 0.0, 1.0); + } +)__SHADER__"; + +static const std::string Bt2100YUVToRGBShader = R"__SHADER__( + vec3 yuvToRgb(const vec3 color) { + const vec3 offset = vec3(0.0, 128.0f / 255.0f, 128.0f / 255.0f); + const mat3 transform = mat3( + 1.0, 1.0, 1.0, + 0.0, -0.16455, 1.8814, + 1.4746, -0.57135, 0.0); return clamp(transform * (color - offset), 0.0, 1.0); } )__SHADER__"; @@ -198,7 +231,8 @@ static const std::string IdentityInverseOOTFShader = R"__SHADER__( )__SHADER__"; std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_fmt, - uhdr_color_transfer output_ct) { + uhdr_color_transfer output_ct, + uhdr_color_gamut_t sdr_cg) { std::string shader_code = R"__SHADER__(#version 300 es precision highp float; precision highp int; @@ -214,7 +248,15 @@ std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_ } else if (sdr_fmt == UHDR_IMG_FMT_12bppYCbCr420) { shader_code.append(getYuv420PixelShader); } - shader_code.append(p3YUVToRGBShader); + if (sdr_cg == UHDR_CG_BT_709) { + shader_code.append(Bt709YUVToRGBShader); + } else if (sdr_cg == UHDR_CG_DISPLAY_P3) { + shader_code.append(p3YUVToRGBShader); + } else if (sdr_cg == UHDR_CG_BT_2100) { + shader_code.append(Bt2100YUVToRGBShader); + } else { + shader_code.append(Bt601YUVToRGBShader); + } shader_code.append(sRGBEOTFShader); shader_code.append(gm_fmt == UHDR_IMG_FMT_8bppYCbCr400 ? getGainMapSampleSingleChannel : getGainMapSampleMultiChannel); @@ -233,7 +275,7 @@ std::string getApplyGainMapFragmentShader(uhdr_img_fmt sdr_fmt, uhdr_img_fmt gm_ shader_code.append(R"__SHADER__( void main() { vec3 yuv_gamma_sdr = getYUVPixel(); - vec3 rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); + vec3 rgb_gamma_sdr = yuvToRgb(yuv_gamma_sdr); vec3 rgb_sdr = sRGBEOTF(rgb_gamma_sdr); vec3 gain = sampleMap(gainMapTexture); vec3 rgb_hdr = applyGain(rgb_sdr, gain); @@ -292,7 +334,8 @@ uhdr_error_info_t applyGainMapGLES(uhdr_raw_image_t* sdr_intent, uhdr_raw_image_ shaderProgram = opengl_ctxt->create_shader_program( vertex_shader.c_str(), - getApplyGainMapFragmentShader(sdr_intent->fmt, gainmap_img->fmt, output_ct).c_str()); + getApplyGainMapFragmentShader(sdr_intent->fmt, gainmap_img->fmt, output_ct, sdr_intent->cg) + .c_str()); RET_IF_ERR() yuvTexture = opengl_ctxt->create_texture(sdr_intent->fmt, sdr_intent->w, sdr_intent->h, diff --git a/lib/src/jpegr.cpp b/lib/src/jpegr.cpp index 0ee53813..f1203c84 100644 --- a/lib/src/jpegr.cpp +++ b/lib/src/jpegr.cpp @@ -270,13 +270,13 @@ uhdr_error_info_t JpegR::encodeJPEGR(uhdr_raw_image_t* hdr_intent, uhdr_raw_imag } // convert to bt601 YUV encoding for JPEG encode -#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) - UHDR_ERR_CHECK( - convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, (uhdr_color_gamut_t)UHDR_CG_BT_601)); -#else - UHDR_ERR_CHECK( - convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, (uhdr_color_gamut_t)UHDR_CG_BT_601)); -#endif +//#if (defined(UHDR_ENABLE_INTRINSICS) && (defined(__ARM_NEON__) || defined(__ARM_NEON))) +// UHDR_ERR_CHECK( +// convertYuv_neon(sdr_intent_yuv, sdr_intent_yuv->cg, (uhdr_color_gamut_t)UHDR_CG_BT_601)); +//#else +// UHDR_ERR_CHECK( +// convertYuv(sdr_intent_yuv, sdr_intent_yuv->cg, (uhdr_color_gamut_t)UHDR_CG_BT_601)); +//#endif // compress sdr image JpegEncoderHelper jpeg_enc_obj_sdr; @@ -1313,9 +1313,8 @@ uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_im extractPrimaryImageAndGainMap(uhdr_compressed_img, &primary_jpeg_image, &gainmap_jpeg_image)) JpegDecoderHelper jpeg_dec_obj_sdr; - UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage( - primary_jpeg_image.data, primary_jpeg_image.data_sz, - (output_ct == UHDR_CT_SRGB) ? DECODE_TO_RGB_CS : DECODE_TO_YCBCR_CS)); + UHDR_ERR_CHECK(jpeg_dec_obj_sdr.decompressImage(primary_jpeg_image.data, + primary_jpeg_image.data_sz, DECODE_TO_YCBCR_CS)); JpegDecoderHelper jpeg_dec_obj_gm; uhdr_raw_image_t gainmap; @@ -1349,7 +1348,7 @@ uhdr_error_info_t JpegR::decodeJPEGR(uhdr_compressed_image_t* uhdr_compressed_im sdr_intent.cg = IccHelper::readIccColorGamut(jpeg_dec_obj_sdr.getICCPtr(), jpeg_dec_obj_sdr.getICCSize()); if (output_ct == UHDR_CT_SRGB) { - UHDR_ERR_CHECK(copy_raw_image(&sdr_intent, dest)); + UHDR_ERR_CHECK(convert_ycbcr_input_to_rgb(&sdr_intent, dest)); return g_no_error; } @@ -1486,13 +1485,18 @@ uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ima return status; } + ColorTransformFn sdrYuvToRgbFn = getYuvToRgbFn(sdr_intent->cg); + if (sdrYuvToRgbFn == nullptr) { + sdrYuvToRgbFn = Bt601YuvToRgb; + } + JobQueue jobQueue; std::function applyRecMap = [sdr_intent, gainmap_img, dest, &jobQueue, &idwTable, output_ct, &gainLUT, #if !USE_APPLY_GAIN_LUT gainmap_metadata, gainmap_weight, #endif - map_scale_factor, get_pixel_fn]() -> void { + map_scale_factor, get_pixel_fn, sdrYuvToRgbFn]() -> void { size_t width = sdr_intent->w; size_t rowStart, rowEnd; @@ -1500,8 +1504,7 @@ uhdr_error_info_t JpegR::applyGainMap(uhdr_raw_image_t* sdr_intent, uhdr_raw_ima for (size_t y = rowStart; y < rowEnd; ++y) { for (size_t x = 0; x < width; ++x) { Color yuv_gamma_sdr = get_pixel_fn(sdr_intent, x, y); - // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients - Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr); + Color rgb_gamma_sdr = sdrYuvToRgbFn(yuv_gamma_sdr); // We are assuming the SDR base image is always sRGB transfer. #if USE_SRGB_INVOETF_LUT Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);