Skip to content

[RFC] command: let overlay-add declare a colorspace tag - #18394

Open
posilash wants to merge 2 commits into
mpv-player:masterfrom
posilash:overlay-colorspace-tag
Open

[RFC] command: let overlay-add declare a colorspace tag#18394
posilash wants to merge 2 commits into
mpv-player:masterfrom
posilash:overlay-colorspace-tag

Conversation

@posilash

Copy link
Copy Markdown

Depends on #18392. That PR is the first commit here and is unchanged, only the second commit, command: let overlay-add declare a colorspace, is new for review.

Why

#18392 settles overlay-add bitmaps as sRGB, which is right for a client drawing its own interface and wrong for content taken from the video. thumbfast decodes thumbnails and hands them over unconverted, expecting them to be tone mapped along with the video. The command carried no colorspace information, so one behavior had to serve both cases, depending on the situation one case is left broken.

What

An optional twelfth argument:

overlay-add <id> <x> <y> <file> <offset> <fmt> <w> <h> <stride> <dw> <dh> <colorspace>

srgb is the default and keeps the behavior #18392 settles on. video uses the video's exact colorspace and tone maps the bitmap with it. Existing callers are unaffected.

A bitmap declared as video is left exactly as the video, without the --image-subs-hdr-peak override that keeps image subtitles from blowing out (a thumbnail has to match the video it came from) and clamping it to a different peak means it cannot.

How

Overlay ids share one packed texture, so each part carries its own colorspace. Appending the parts one colorspace at a time leaves each group a single run of the array, which is the form pl_overlay takes, and the VO draws one overlay per group over the shared texture. Ids with different colorspaces can then be mixed.

Grouping is one pass per colorspace over the parts, so it stays linear and doesn't need a scratch buffer or sorting. struct sub_bitmap gains a uint8_t, placed so it's in the existing padding, sizeof is 56 before and after, verified with gdb -ex 'ptype /o'.

Stereo 3D duplication had to move inside the grouping loop. Leaving it where it was makes the copies land outside every group's range and they don't get drawn.

MP_CMD_DEF_MAX_ARGS

It allowed eleven arguments, an additional one was added, raising it to twelve.

Testing

Tested on Arch Linux, vo=gpu-next, Vulkan (x11vk), NVIDIA (nvidia-open 610.57.04), against a BT.2020/PQ clip and an SDR clip.

Case Result
srgb, HDR source exact passthrough, 60,80,100,120,140,160 in and out
video, HDR source takes the video's colorspace, 19,28,40,54,72,94
omitted argument identical to srgb
all three in one frame each rendered with its own colorspace
8 ids, alternating tags 4/4 and 4/4 correct
overlay-remove removes the right id, leaves the rest untouched
invalid value rejected, invalid parameter

pl_color_space was also printed where it is assigned: for an overlay tagged video every field matches the video exactly, the primaries, transfer and the float luminances. While an srgb-tagged overlay keeps BT.709/sRGB.

Bitmap overlays took the video's colorspace, so that image subtitles,
which are muxed with the video and authored in its space, reproduce
correctly. Bitmaps supplied through the overlay-add command are not in
that space: they come from the client, are documented only as
premultiplied BGRA with no colorspace attached, and are in practice
sRGB. Reinterpreting them as PQ or HLG broke any client drawing its
interface through overlay-add whenever the file was HDR.

Measured on a BT.2020/PQ clip by pushing an sRGB overlay and reading
the rendered pixels back, with SDR output:

  in (192,192,192) -> out (255,255,255)   light greys clipped to white
  in (128,128,128) -> out (190,190,190)
  in (255,  0,  0) -> out (255,117, 37)   primaries skewed

Mean error was 36.5/255; the overlay now round-trips exactly. With an
HDR target the overlay was passed through untouched instead, so sRGB
white went out as PQ 255 and requested 10000 nits.

struct sub_bitmaps already carries video_color_space for exactly this
question, and the SUBBITMAP_LIBASS branch honors it; only the BGRA
branch inferred unconditionally. Set the flag where image subtitles
are packed and honor it in the VO, so overlay-add keeps the default
assigned a few lines above. Image subtitles are unaffected.

Refs mpv-player#10678
@posilash posilash changed the title command: let overlay-add declare a colorspace [RFC] command: let overlay-add declare a colorspace tag Aug 20, 2026
@posilash
posilash force-pushed the overlay-colorspace-tag branch from b61665a to 7550dba Compare August 20, 2026 13:08
The previous commit, mpv-player#18392, made bitmaps from overlay-add sRGB. That
was right for content a client drew itself and wrong for content taken
from the video: thumbfast passes thumbnails in unconverted and expects
them tone mapped along with the video. The command carried no colorspace
information, so one behavior had to serve both.

Add an optional colorspace argument taking srgb or video. srgb is the
default and keeps the behavior that commit settled on. video uses the
video's exact colorspace and tone maps the bitmap with it. Existing
callers are unaffected.

All overlay ids share one packed texture, so each part carries its own
colorspace. Appending the parts a colorspace at a time leaves each group
a single run of the array, which is the form pl_overlay takes, and the
VO draws one overlay per group over the shared texture. Ids with
different colorspaces can then be mixed. The grouping costs one pass per
colorspace and no extra memory.

MP_CMD_DEF_MAX_ARGS allowed eleven arguments, raise it to twelve.

A bitmap declared as being in the video's colorspace is left exactly as
the video, without the --image-subs-hdr-peak override that keeps image
subtitles from blowing out. A thumbnail has to match the video it came
from, and clamping it to a different peak means it cannot.

Verified by printing pl_color_space where it is assigned: for an overlay
tagged video every field matches the video exactly, primaries, transfer
and the float luminances alike, while an srgb-tagged overlay of the same
pixels keeps BT.709/sRGB.
@posilash
posilash force-pushed the overlay-colorspace-tag branch from 7550dba to dd62695 Compare August 20, 2026 13:10
@posilash

Copy link
Copy Markdown
Author

@iwalton3 would you mind testing this out.

Nothing changes for existing code. The argument is optional and defaults to srgb, which is the behaviour #18392 settles on, so existing clients still work, just now defaults to srgb.

To opt in, append the colorspace argument with either option of srgb or video to the overlay-add command.

Comment thread player/command.c
Comment on lines +7901 to +7904
{"colorspace", OPT_CHOICE(v.i,
{"srgb", SUB_BITMAP_CSP_SRGB},
{"video", SUB_BITMAP_CSP_VIDEO}),
OPTDEF_INT(SUB_BITMAP_CSP_SRGB)} }},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason to not let the user specify any arbitrary colorspace mpv supports now that we're doing this properly?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh, I personally do not see the usecase for that, but it's not impossible. Explicit primaries/transfer would be easy to add on top, mpv already exposes pl_csp_prim_names/pl_csp_trc_names from --target-prim/--target-trc.

From my pov i think video should be sufficient, it carries the source's HDR metadata, unless there really is a usecase for explicit colorspaces for overlays. Either way, explicit HDR transfers would need to include the peak metadata (from where?), or else the overlay blowout issue would persist. Also, the VO groups parts by making one pass per possible value, which is fine at two, but there are a few hundred possible prim/transfer combinations, so it'd need to collect the distinct values actually present instead.

is there a specific usecase you have in mind?

@iwalton3

Copy link
Copy Markdown

This PR also works for my needs, it's still not enough for thumbfast to send non-sRGB thumbnails reliably. (More details posted on #18392)

@posilash

Copy link
Copy Markdown
Author

That settles the usecase question then @llyyr. The partially converted thumbnail case needs explicit primaries, which neither srgb nor video can express. Worth noting it needs primaries rather than an HDR transfer, so it sidesteps the peak metadata problem entirely.

Either way I don't think this blocks it. The enum can gain values, or another optional argument can be appended exactly the way this one was, so explicit primaries/transfer can land on top without disturbing the tag that's already there. So merging this in the meantime seems reasonable to me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants