55 lines
1.9 KiB
Python
55 lines
1.9 KiB
Python
"""Luminance-controlled palettes shared by SDR and HDR output."""
|
|
|
|
import math
|
|
|
|
SRGB_Y = (0.2126, 0.7152, 0.0722)
|
|
BT2020_Y = (0.2627, 0.6780, 0.0593)
|
|
TO_2020 = ((0.6274039, 0.3292830, 0.0433131),
|
|
(0.0690973, 0.9195404, 0.0113623),
|
|
(0.0163914, 0.0880133, 0.8955953))
|
|
ORIGINAL_BLUE = '#3b73fa'
|
|
GRADIENT_STEPS = 32
|
|
|
|
|
|
def linear_srgb(color):
|
|
values = (int(color[i:i + 2], 16) / 255 for i in (1, 3, 5))
|
|
return tuple(v / 12.92 if v <= 0.04045 else ((v + .055) / 1.055) ** 2.4 for v in values)
|
|
|
|
|
|
def gradient_level(position):
|
|
"""32 perceptually separated levels, with exact dark and bright endpoints."""
|
|
t = round(min(1, max(0, position)) * (GRADIENT_STEPS - 1)) / (GRADIENT_STEPS - 1)
|
|
return .005 + .995 * t ** 2.2
|
|
|
|
|
|
SDR_PEAK_Y = sum(v * w for v, w in zip(linear_srgb(ORIGINAL_BLUE), SRGB_Y))
|
|
|
|
|
|
def equal_luminance(color, target, channel_limit, *, hdr=False):
|
|
"""Scale linear RGB equally, preserving chromaticity without adding white."""
|
|
rgb = linear_srgb(color)
|
|
weights = BT2020_Y if hdr else SRGB_Y
|
|
if hdr:
|
|
rgb = tuple(sum(a * b for a, b in zip(row, rgb)) for row in TO_2020)
|
|
luminance = sum(a * b for a, b in zip(rgb, weights))
|
|
if luminance <= 0:
|
|
return (target,) * 3
|
|
rgb = tuple(v * target / luminance for v in rgb)
|
|
maximum = max(rgb)
|
|
if maximum > channel_limit + 1e-6:
|
|
raise ValueError('Requested luminance exceeds the color-preserving channel limit')
|
|
return rgb
|
|
|
|
|
|
def sdr_color(base, level=1.0):
|
|
# Scale a fixed peak color to keep chromaticity constant along the trail.
|
|
peak = equal_luminance(base, SDR_PEAK_Y, 1.0)
|
|
rgb = [max(0, min(1, v * level)) for v in peak]
|
|
rgb = [12.92 * v if v <= .0031308 else 1.055 * v ** (1 / 2.4) - .055 for v in rgb]
|
|
return '#' + ''.join(f'{round(v * 255):02x}' for v in rgb)
|
|
|
|
|
|
def validate_peak(peak):
|
|
if not math.isfinite(peak) or not 100 <= peak <= 2000:
|
|
raise ValueError('--peak-nits must be between 100 and 2000')
|