summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/vkms/tests/gen_yuv_conversion.py
blob: f4d226137cc2f479c0c1c68e3fc1d84b3aea6624 (plain) (blame)
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()