1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
|
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0+
"""
Derive the VKMS YCbCr -> RGB conversion constants and the vkms-format KUnit
reference values.
It produces:
1. The S31.32 fixed-point conversion matrices used in
drivers/gpu/drm/vkms/vkms_formats.c and asserted by
vkms_format_test_conversion_matrix.
2. The 16-bit reference YCbCr inputs for the limited-range
yuv_u16_to_argb_u16 round-trip cases (the values changed by the fix).
Requires numpy.
"""
import numpy as np
FP = 1 << 32 # S31.32 scale: fixed = round(coeff * 2^32)
# ITU-R luma weights (Kr, Kb); Kg = 1 - Kr - Kb.
ENCODINGS = {
"BT601": (0.299, 0.114),
"BT709": (0.2126, 0.0722),
"BT2020": (0.2627, 0.0593), # non-constant luminance
}
# 8-bit studio-range levels: luma 16..235 (range 219), chroma 16..240
# (range 224, neutral 128); full-range maximum is 2^n - 1 = 255.
FULL_MAX = 255
Y_RANGE = 235 - 16 # 219
C_RANGE = 240 - 16 # 224
# 16-bit RGB test colors.
COLORS = {
"white": (0xffff, 0xffff, 0xffff),
"gray": (0x8080, 0x8080, 0x8080),
"black": (0x0000, 0x0000, 0x0000),
"red": (0xffff, 0x0000, 0x0000),
"green": (0x0000, 0xffff, 0x0000),
"blue": (0x0000, 0x0000, 0xffff),
}
def rgb_matrix(kr, kb, limited):
"""Normalized YCbCr -> RGB (Y in [0,1], Cb/Cr in [-0.5, 0.5])."""
kg = 1.0 - kr - kb
# Standard full-range relations.
m = [[1.0, 0.0, 2 * (1 - kr)],
[1.0, -2 * (1 - kb) * kb / kg, -2 * (1 - kr) * kr / kg],
[1.0, 2 * (1 - kb), 0.0]]
if limited:
# Expand studio input range to full range: luma by 255/219,
# chroma by 255/224 (relative to full-range maximum 2^n - 1).
ys, cs = FULL_MAX / Y_RANGE, FULL_MAX / C_RANGE
m = [[r[0] * ys, r[1] * cs, r[2] * cs] for r in m]
return m
def ref_yuv(m, y_offset):
"""16-bit YCbCr that VKMS decodes back to each RGB color.
VKMS decode: [R,G,B] = M . [Y - y_offset*257, Cb - 128*257, Cr - 128*257]
(8-bit offsets lifted to 16-bit by *257 = 65535/255), so invert and
re-add the offsets.
"""
inv = np.linalg.inv(np.array(m, float))
off = np.array([y_offset * 257, 128 * 257, 128 * 257])
return {name: tuple(int(min(max(round(v), 0), 0xffff))
for v in inv @ np.array(rgb, float) + off)
for name, rgb in COLORS.items()}
print(f"== RGB REFERENCE VALUES ==")
for name, rgb in COLORS.items():
print(f" {name:6} {{ {rgb[0]:#06x}, {rgb[1]:#06x}, {rgb[2]:#06x} }}")
print()
for enc, (kr, kb) in ENCODINGS.items():
for limited in (False, True):
m = rgb_matrix(kr, kb, limited)
y_offset = 16 if limited else 0
print(f"== {enc} {'LIMITED' if limited else 'FULL'} (y_offset={y_offset}) ==")
for row in m:
print(" { " + ", ".join(str(round(v * FP)) for v in row) + " },")
for name, yuv in ref_yuv(m, y_offset).items():
print(f" {name:6} {{ {yuv[0]:#06x}, {yuv[1]:#06x}, {yuv[2]:#06x} }}")
print()
|