Skip to content

utils: Encode high-bit-depth sources to sRGB in screenshots - #16

Merged
zackees merged 2 commits into
obs-rust:masterfrom
rdoupe:fix/screenshot-linear-srgb
Oct 17, 2026
Merged

zackees merged 2 commits into
obs-rust:masterfrom
rdoupe:fix/screenshot-linear-srgb

Conversation

@rdoupe

@rdoupe rdoupe commented Oct 17, 2026

Copy link
Copy Markdown
Contributor

GetSourceScreenshot and SaveSourceScreenshot of a 10- or 12-bit source (P010, I010, I210, I412: HEVC Main10, VP9 profile 2, ProRes, 10-bit capture cards) came out too dark: orange (255, 96, 0) as (255, 30, 0), mid grey 128 as 55, while the program output, the scene screenshot and recordings showed the right colours. libobs keeps these frames as a linear float texture (need_linear_output(), GS_CS_SRGB_16F), and the framebuffer's sRGB encode is only on while linear sRGB is on. TakeSourceScreenshot() drew the source on its own with linear sRGB off, so the linear values went into the 8-bit image unencoded. Scenes (render_item()) and the frontend's Properties/Filters/Interaction previews draw with gs_set_linear_srgb(true), which is why they were right. Found by our release check; no upstream report.

Two commits:

  • utils: Move the source screenshot render into a helper: TakeSourceScreenshot() moves unchanged into src/utils/SourceScreenshot.cpp as Utils::TakeSourceScreenshot(), so it can be tested on its own.
  • utils: Encode high-bit-depth sources to sRGB in screenshots: the source is drawn with linear sRGB on when its colour space (obs_source_get_color_space() with SRGB, SRGB_16F and 709_EXTENDED preferred, the list transitions use) is not 8-bit sRGB. 8-bit sources keep the direct path, so their pixels and their straight alpha are unchanged. With linear sRGB on, libobs premultiplies semi-transparent 8-bit async sources (DrawNonlinearAlpha), and the PNG would have darker edges. HDR (PQ/HLG) async sources were already tone mapped with linear sRGB forced on by libobs, so nothing changes for them.

Tested in obs-rust/obs-studio with test_ws_screenshot_srgb, which builds the helper against libobs and the built OpenGL module and shows a flat colour from a test async source as the only item of the program scene. At the helper-only commit, 4 of 6 cases fail in both libobs lanes: P010 grey, and P010/I010/I210 orange. With the fix, all 6 pass in both lanes, and the 8-bit NV12 and half-transparent BGRA controls give the same values as before. Also tested in a full OBS build (frontend + obs-websocket) in both lanes, and end to end. The release v0.1.0-beta.20261014 returns (255, 30, 0) for ProRes 422, ProRes 4444 and HEVC Main10 Media Sources, and builds with this branch return (255, 96, 0), the same as the scene screenshot and the recording. The 8-bit H.264 and VP9-alpha screenshots are byte-identical to the release's below the pattern's changing strip.

Refs obs-rust/obs-studio#1141. The obs-rust/obs-studio PR that moves the submodule to this branch carries the regression test and closes the issue.

🤖 Generated with Claude Code

rdoupe and others added 2 commits October 16, 2026 20:43
GetSourceScreenshot and SaveSourceScreenshot render the source with
TakeSourceScreenshot(), which now lives in utils/SourceScreenshot.cpp as
Utils::TakeSourceScreenshot(), so it can be tested on its own. No
change in behaviour.

Refs obs-rust/obs-studio#1141.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetSourceScreenshot and SaveSourceScreenshot of a 10- or 12-bit source
(P010, I010, I210, I412: HEVC Main10, VP9 profile 2, ProRes, 10-bit
capture cards) came out too dark: orange (255, 96, 0) as (255, 30, 0),
while the program output, the previews and recordings showed it right.
libobs keeps these frames as linear float, and TakeSourceScreenshot()
drew the source with linear sRGB off, so the linear values went into the
8-bit image unencoded.

The source is now drawn with linear sRGB on when its colour space is not
8-bit sRGB, as scenes and the frontend previews draw it, so libobs
encodes the colours to sRGB. 8-bit sources keep the direct path: their
pixels and their straight (not premultiplied) alpha are unchanged.

Refs obs-rust/obs-studio#1141.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants