Added a simple linear scale for tonemapped HDR to SDR surface conversion

main
Sam Lantinga 2024-02-01 10:34:59 -08:00
parent fc35b7e121
commit 19dde63e7c
2 changed files with 57 additions and 24 deletions

View File

@ -232,6 +232,7 @@ extern DECLSPEC void SDLCALL SDL_DestroySurface(SDL_Surface *surface);
* cd/m2 or nits) of the entire playback sequence. MaxFALL is calculated by * cd/m2 or nits) of the entire playback sequence. MaxFALL is calculated by
* averaging the decoded luminance values of all the pixels within a frame. * averaging the decoded luminance values of all the pixels within a frame.
* MaxFALL is usually much lower than MaxCLL. * MaxFALL is usually much lower than MaxCLL.
* - `SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING`: the tone mapping operator used when converting between different colorspaces. Currently this supports the form "*=N", where N is a floating point scale factor applied in linear space.
* *
* \param surface the SDL_Surface structure to query * \param surface the SDL_Surface structure to query
* \returns a valid property ID on success or 0 on failure; call * \returns a valid property ID on success or 0 on failure; call
@ -247,6 +248,7 @@ extern DECLSPEC SDL_PropertiesID SDLCALL SDL_GetSurfaceProperties(SDL_Surface *s
#define SDL_PROP_SURFACE_COLORSPACE_NUMBER "SDL.surface.colorspace" #define SDL_PROP_SURFACE_COLORSPACE_NUMBER "SDL.surface.colorspace"
#define SDL_PROP_SURFACE_MAXCLL_NUMBER "SDL.surface.maxCLL" #define SDL_PROP_SURFACE_MAXCLL_NUMBER "SDL.surface.maxCLL"
#define SDL_PROP_SURFACE_MAXFALL_NUMBER "SDL.surface.maxFALL" #define SDL_PROP_SURFACE_MAXFALL_NUMBER "SDL.surface.maxFALL"
#define SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING "SDL.surface.tonemap"
/** /**
* Set the colorspace used by a surface. * Set the colorspace used by a surface.

View File

@ -664,12 +664,44 @@ static void WriteFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_Pixel
} }
} }
static float CompressPQtoSDR(float v) typedef enum
{ {
/* This gives generally good results for PQ HDR -> SDR conversion, scaling from 400 nits to 80 nits, SDL_TONEMAP_NONE,
* which is scRGB 1.0 SDL_TONEMAP_LINEAR,
*/ } SDL_TonemapOperator;
return (v / 5.0f);
typedef struct
{
SDL_TonemapOperator op;
union {
struct {
float scale;
} linear;
} data;
} SDL_TonemapContext;
static void ApplyTonemap(SDL_TonemapContext *ctx, float *r, float *g, float *b)
{
switch (ctx->op) {
case SDL_TONEMAP_LINEAR:
*r *= ctx->data.linear.scale;
*g *= ctx->data.linear.scale;
*b *= ctx->data.linear.scale;
break;
default:
break;
}
}
static SDL_bool IsHDRColorspace(SDL_Colorspace colorspace)
{
if (colorspace == SDL_COLORSPACE_SCRGB ||
SDL_COLORSPACETRANSFER(colorspace) == SDL_TRANSFER_CHARACTERISTICS_PQ) {
return SDL_TRUE;
}
return SDL_FALSE;
} }
/* The SECOND TRUE BLITTER /* The SECOND TRUE BLITTER
@ -694,29 +726,30 @@ void SDL_Blit_Slow_Float(SDL_BlitInfo *info)
SlowBlitPixelAccess src_access; SlowBlitPixelAccess src_access;
SlowBlitPixelAccess dst_access; SlowBlitPixelAccess dst_access;
SDL_Colorspace src_colorspace; SDL_Colorspace src_colorspace;
SDL_ColorPrimaries src_primaries;
SDL_TransferCharacteristics src_transfer;
SDL_Colorspace dst_colorspace; SDL_Colorspace dst_colorspace;
SDL_ColorPrimaries dst_primaries; const float *color_primaries_matrix = NULL;
SDL_TransferCharacteristics dst_transfer; SDL_TonemapContext tonemap;
const float *color_primaries_matrix;
SDL_bool compress_PQ = SDL_FALSE;
if (SDL_GetSurfaceColorspace(info->src_surface, &src_colorspace) < 0 || if (SDL_GetSurfaceColorspace(info->src_surface, &src_colorspace) < 0 ||
SDL_GetSurfaceColorspace(info->dst_surface, &dst_colorspace) < 0) { SDL_GetSurfaceColorspace(info->dst_surface, &dst_colorspace) < 0) {
return; return;
} }
src_primaries = SDL_COLORSPACEPRIMARIES(src_colorspace); tonemap.op = SDL_TONEMAP_NONE;
src_transfer = SDL_COLORSPACETRANSFER(src_colorspace); if (src_colorspace != dst_colorspace) {
dst_primaries = SDL_COLORSPACEPRIMARIES(dst_colorspace); SDL_ColorPrimaries src_primaries = SDL_COLORSPACEPRIMARIES(src_colorspace);
dst_transfer = SDL_COLORSPACETRANSFER(dst_colorspace); SDL_ColorPrimaries dst_primaries = SDL_COLORSPACEPRIMARIES(dst_colorspace);
color_primaries_matrix = SDL_GetColorPrimariesConversionMatrix(src_primaries, dst_primaries); color_primaries_matrix = SDL_GetColorPrimariesConversionMatrix(src_primaries, dst_primaries);
if (src_transfer == SDL_TRANSFER_CHARACTERISTICS_PQ && if (IsHDRColorspace(src_colorspace) != IsHDRColorspace(dst_colorspace)) {
dst_transfer != SDL_TRANSFER_CHARACTERISTICS_PQ && const char *tonemap_operator = SDL_GetStringProperty(SDL_GetSurfaceProperties(info->src_surface), SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING, NULL);
dst_colorspace != SDL_COLORSPACE_SCRGB) { if (tonemap_operator) {
//compress_PQ = SDL_TRUE; if (SDL_strncmp(tonemap_operator, "*=", 2) == 0) {
tonemap.op = SDL_TONEMAP_LINEAR;
tonemap.data.linear.scale = SDL_atof(tonemap_operator + 2);
}
}
}
} }
src_access = GetPixelAccessMethod(src_fmt); src_access = GetPixelAccessMethod(src_fmt);
@ -742,10 +775,8 @@ void SDL_Blit_Slow_Float(SDL_BlitInfo *info)
SDL_ConvertColorPrimaries(&srcR, &srcG, &srcB, color_primaries_matrix); SDL_ConvertColorPrimaries(&srcR, &srcG, &srcB, color_primaries_matrix);
} }
if (compress_PQ) { if (tonemap.op) {
srcR = CompressPQtoSDR(srcR); ApplyTonemap(&tonemap, &srcR, &srcG, &srcB);
srcG = CompressPQtoSDR(srcG);
srcB = CompressPQtoSDR(srcB);
} }
if (flags & SDL_COPY_COLORKEY) { if (flags & SDL_COPY_COLORKEY) {