1package vulkan
2
3import "math"
4
5var srgbTable = func() (table [256]float64) {
6 for i := range table {
7 table[i] = srgbToLinear(float64(i) / 255)
8 }
9 return
10}()
11
12// srgbToLinear decodes a unit-range sRGB component.
13func srgbToLinear(v float64) float64 {
14 if v <= 0.04045 {
15 return v / 12.92
16 }
17 return math.Pow((v+0.055)/1.055, 2.4)
18}
19
20// pqEncode encodes absolute luminance in nits using SMPTE ST 2084.
21func pqEncode(nits float64) float64 {
22 if nits <= 0 {
23 return 0
24 }
25 x := math.Pow(min(nits, 10000)/10000, 2610.0/16384)
26 return math.Pow((3424.0/4096+2413.0/128*x)/(1+2392.0/128*x), 2523.0/32)
27}
28
29// hdrPixel transforms an unassociated sRGB triplet into BT.2020 PQ.
30func hdrPixel(red, green, blue, nits float64) (float64, float64, float64) {
31 r, g, b := srgbToLinear(red), srgbToLinear(green), srgbToLinear(blue)
32 return pqEncode((0.627404*r + 0.329283*g + 0.043313*b) * nits),
33 pqEncode((0.069097*r + 0.919540*g + 0.011362*b) * nits),
34 pqEncode((0.016391*r + 0.088013*g + 0.895595*b) * nits)
35}
36
37// hdrCursorPixel converts premultiplied BGRA bytes, preserving alpha.
38// The source is quantized to its 8-bit unassociated sRGB value for the LUT.
39func hdrCursorPixel(p []byte, nits float64) {
40 a := float64(p[3]) / 255
41 if a == 0 {
42 clear(p[:3])
43 return
44 }
45 index := func(v byte) int { return min(255, int(math.Round(float64(v)/a))) }
46 r, g, b := srgbTable[index(p[2])], srgbTable[index(p[1])], srgbTable[index(p[0])]
47 r, g, b = pqEncode((0.627404*r+0.329283*g+0.043313*b)*nits), pqEncode((0.069097*r+0.919540*g+0.011362*b)*nits), pqEncode((0.016391*r+0.088013*g+0.895595*b)*nits)
48 p[0], p[1], p[2] = byte(math.Round(b*a*255)), byte(math.Round(g*a*255)), byte(math.Round(r*a*255))
49}