HDR brightness protocol¶
Added in version 0.50.0.
Many modern displays can show colors brighter than the white used for normal content, which Apple calls extended dynamic range (EDR). This protocol lets programs request that extra brightness for text, for images and for the cursor, without changing how anything else is displayed.
Everything is expressed relative to SDR white, the white of normal text,
which is rendered exactly as bright as it always has been. A brightness of
100 percent is SDR white, 200 percent is twice as bright in linear
light, and so on. How much extra brightness the display can show is called the
headroom, also a multiple of SDR white: a headroom of 1 means the display
has no extra range, a headroom of 4 means it can show up to four times SDR
white. The headroom changes with the brightness setting of the display and
when a window moves between screens.
The terminal clamps every request to the headroom it currently has. On a
display without HDR, or when the user has HDR turned off, every request is
clamped to 1 and the output is identical to what it was before this
protocol existed. Requests are never used to make anything dimmer, so programs
written without HDR in mind look the same in a terminal that implements this
protocol as in one that does not.
Text¶
Two new SGR attributes set the brightness of the foreground and the background of subsequently written cells, as a percent of SDR white, using the colon sub-parameter form:
<ESC>[56:<percent>m foreground brightness
<ESC>[57:<percent>m background brightness
For example, to show text in orange at two and a half times SDR white:
<ESC>[38;2;255;136;0m<ESC>[56:250mHello<ESC>[m
The brightness is independent of how the color was chosen. It applies to
colors set with 38/48, to the sixteen basic colors, to colors from the
256 color palette and to the default foreground and background, so a program
can ask for the default text color, brighter, without knowing what that color
is. The text decorations of a cell (underline, strikethrough) use the
foreground brightness. Under reverse video the brightness follows the color it
was set with, so a reversed cell with a foreground brightness of 200 percent
shows a background at 200 percent.
The attributes are reset by <ESC>[0m, by 56 and 57 without a
sub-parameter, and by a value of 100 or less. Values are rounded to quarter
steps (125, 150, 175…) and clamped to 1675. A request larger
than the headroom is shown at the headroom. The dim attribute (2) and
brightness are independent, dim text at 200 percent is dim relative to a
white twice as bright as usual.
Terminals that do not know these attributes ignore them, since that is what
most terminals do with unknown SGR parameters and their sub-parameters, so a
program can emit them unconditionally. A terminal that drops a whole SGR
sequence containing an unknown parameter would also lose any color set in the
same sequence, so send the brightness in its own <ESC>[...m after the
color, as in the example above. The percent is only recognized as a colon
sub-parameter, <ESC>[56;250m is 56 with no value followed by an
unrelated parameter.
Images¶
The Terminal graphics protocol gains a pixel format for HDR images, f=64.
The data is RGBA with four 16-bit floating point numbers (IEEE 754 half
precision, little endian) per pixel, in linear extended sRGB where 1.0 is
SDR white and values above it are brighter. Alpha is straight (not
pre-multiplied), as for f=32. The other keys work as for f=32: the
width and height must be given with s and v, the data may be
compressed with o=z, and it may be sent by any transmission medium.
Animation frames of such an image must also be f=64 (and f=64 frames
cannot be added to other images), the terminal responds with EINVAL
otherwise. Frames are blended in linear light, and the background color of a
frame (the Y key) is taken as linear values, 255 being SDR white. The
gamut key of the Wide gamut colors, G, is ignored for an HDR
image: its colors are extended sRGB by definition, which reaches the colors
outside sRGB with values below zero and above one.
Pixel values larger than the headroom are shown at the headroom, which
clips the bright parts of an image on a display with little headroom. An
image can ask instead for its bright parts to be compressed into the headroom
with T=1 on the command that transmits it. Then, with H the headroom,
P the largest color component in the image (at least 1.0, and including
the animation frames loaded so far) and k = 0.75 H the knee, a pixel
whose largest component m is above the knee is scaled by f(m)/m where:
f(m) = k + (m - k) / (1 + s (m - k)) s = 1 / (H - k) - 1 / (P - k)
so that everything up to the knee is unchanged, P lands exactly at H, the
curve is continuous at the knee and the hue of a pixel is preserved. When P
is within the headroom nothing is changed. T=0, the default, clips.
To check whether a terminal supports f=64, use the query action described
in the graphics protocol with f=64 and a single pixel of data, a terminal
that does not know the format responds with EINVAL.
Cursor¶
In Silkie the brightness of the block cursor is a configuration option,
cursor_brightness. A cursor brighter than any text makes it easy to
find on a crowded screen.
Querying the headroom¶
A program that wants to know how much extra brightness is available, for
example to pick between several renderings of an image, can ask for the
headroom of the window it is running in with the XTGETTCAP query
mechanism, see Query terminal. The query name is
kitty-query-hdr_headroom and the answer is the headroom as a decimal
number, 1 when there is none. For example, encoding the query name as
hex:
<ESC>P+q6b697474792d71756572792d6864725f68656164726f6f6d<ESC>\
The headroom changes over time, so re-query it before relying on a value
obtained a while ago. This query sends no notification of changes; a program
that wants to be told turns on the events of the Display protocol,
whose headroom field is the same value. On macOS the display fades its
extended range in over the first second or so after an HDR window appears, so
a value obtained at startup is often smaller than one obtained a moment later.
Configuration¶
In Silkie, HDR output is off by default and enabled with the hdr option.
The user can cap how bright anything gets with hdr_max_headroom, which
the terminal applies before clamping to the headroom of the display, and that
cap is what the query above reports.
HDR content is displayed on macOS, and on Wayland when two things hold:
The compositor implements the
color-management-v1protocol with parametric image descriptions, the extended linear transfer function and settable luminances. Mutter 50 does. A window then draws linear values and tells the compositor that they reach from black to as many times the reference white of the display as the content gets bright.The graphics driver has EGL window surfaces with floating point color (
EGL_EXT_pixel_format_float), since eight bits per channel cannot hold values above white. Mesa’s drivers for graphics hardware are expected to have them, which is untested. The NVIDIA driver (version 595) does not, and neither does Mesa when it renders in software, so there the window stays as it is withouthdr.
The headroom on Wayland is the brightest luminance the compositor says the
display of the window can show, divided by the luminance of its reference
white. It is 1 on a display that is not in HDR mode and changes when the
display is switched into or out of it. A compositor that does not know the
peak of a display, a virtual one for instance, reports the limit of the signal
instead, which makes the headroom about 49; hdr_max_headroom is what
keeps Silkie from using that.
The values of an HDR window on Wayland are in the sRGB primaries unless
wayland_colorspace asks for a wider gamut, see
Wide gamut colors. The compositor is then told of the wide primaries
in the same description that says the values are linear and how bright they
get, since a window has only one, and what is drawn is converted to those
primaries. A compositor that takes linear values only in the sRGB primaries
leaves the window an HDR window in sRGB. This combination has been checked
against Mutter 50 in the same way and to the same extent as HDR itself.
kitty --debug-rendering says whether a window got a floating point surface
and what the compositor was told. The Wayland support has been run against
Mutter 50 with every step checked except the floating point surface itself,
for want of a driver that has one, and it has not been seen on an HDR monitor.
On X11 and on Wayland without the above the headroom is always 1.