Browse Source

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

Sam Lantinga 1 year ago
parent
commit
19dde63e7c
2 changed files with 58 additions and 25 deletions
  1. 2 0
      include/SDL3/SDL_surface.h
  2. 56 25
      src/video/SDL_blit_slow.c

+ 2 - 0
include/SDL3/SDL_surface.h

@@ -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
  *   averaging the decoded luminance values of all the pixels within a frame.
  *   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
  * \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_MAXCLL_NUMBER                      "SDL.surface.maxCLL"
 #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.

+ 56 - 25
src/video/SDL_blit_slow.c

@@ -664,12 +664,44 @@ static void WriteFloatPixel(Uint8 *pixels, SlowBlitPixelAccess access, SDL_Pixel
     }
 }
 
-static float CompressPQtoSDR(float v)
+typedef enum
+{
+    SDL_TONEMAP_NONE,
+    SDL_TONEMAP_LINEAR,
+} SDL_TonemapOperator;
+
+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)
 {
-    /* This gives generally good results for PQ HDR -> SDR conversion, scaling from 400 nits to 80 nits,
-     * which is scRGB 1.0
-     */
-    return (v / 5.0f);
+    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
@@ -694,29 +726,30 @@ void SDL_Blit_Slow_Float(SDL_BlitInfo *info)
     SlowBlitPixelAccess src_access;
     SlowBlitPixelAccess dst_access;
     SDL_Colorspace src_colorspace;
-    SDL_ColorPrimaries src_primaries;
-    SDL_TransferCharacteristics src_transfer;
     SDL_Colorspace dst_colorspace;
-    SDL_ColorPrimaries dst_primaries;
-    SDL_TransferCharacteristics dst_transfer;
-    const float *color_primaries_matrix;
-    SDL_bool compress_PQ = SDL_FALSE;
+    const float *color_primaries_matrix = NULL;
+    SDL_TonemapContext tonemap;
 
     if (SDL_GetSurfaceColorspace(info->src_surface, &src_colorspace) < 0 ||
         SDL_GetSurfaceColorspace(info->dst_surface, &dst_colorspace) < 0) {
         return;
     }
 
-    src_primaries = SDL_COLORSPACEPRIMARIES(src_colorspace);
-    src_transfer = SDL_COLORSPACETRANSFER(src_colorspace);
-    dst_primaries = SDL_COLORSPACEPRIMARIES(dst_colorspace);
-    dst_transfer = SDL_COLORSPACETRANSFER(dst_colorspace);
-    color_primaries_matrix = SDL_GetColorPrimariesConversionMatrix(src_primaries, dst_primaries);
-
-    if (src_transfer == SDL_TRANSFER_CHARACTERISTICS_PQ &&
-        dst_transfer != SDL_TRANSFER_CHARACTERISTICS_PQ &&
-        dst_colorspace != SDL_COLORSPACE_SCRGB) {
-        //compress_PQ = SDL_TRUE;
+    tonemap.op = SDL_TONEMAP_NONE;
+    if (src_colorspace != dst_colorspace) {
+        SDL_ColorPrimaries src_primaries = SDL_COLORSPACEPRIMARIES(src_colorspace);
+        SDL_ColorPrimaries dst_primaries = SDL_COLORSPACEPRIMARIES(dst_colorspace);
+        color_primaries_matrix = SDL_GetColorPrimariesConversionMatrix(src_primaries, dst_primaries);
+
+        if (IsHDRColorspace(src_colorspace) != IsHDRColorspace(dst_colorspace)) {
+            const char *tonemap_operator = SDL_GetStringProperty(SDL_GetSurfaceProperties(info->src_surface), SDL_PROP_SURFACE_TONEMAP_OPERATOR_STRING, NULL);
+            if (tonemap_operator) {
+                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);
@@ -742,10 +775,8 @@ void SDL_Blit_Slow_Float(SDL_BlitInfo *info)
                 SDL_ConvertColorPrimaries(&srcR, &srcG, &srcB, color_primaries_matrix);
             }
 
-            if (compress_PQ) {
-                srcR = CompressPQtoSDR(srcR);
-                srcG = CompressPQtoSDR(srcG);
-                srcB = CompressPQtoSDR(srcB);
+            if (tonemap.op) {
+                ApplyTonemap(&tonemap, &srcR, &srcG, &srcB);
             }
 
             if (flags & SDL_COPY_COLORKEY) {