detached f2d166b

hdrcursor.go RawHistoryFolder

1// Package hdrcursor converts cursor images to the HDR10 output encoding
2// (BT.2020 PQ) for cursor planes, whichever adapter owns the image memory.
3package hdrcursor
4
5import (
6	"crypto/sha256"
7	"encoding/binary"
8	"math"
9)
10
11var srgbTable = func() (table [256]float64) {
12	for i := range table {
13		table[i] = SRGBToLinear(float64(i) / 255)
14	}
15	return
16}()
17
18// SRGBToLinear decodes a unit-range sRGB component.
19func SRGBToLinear(v float64) float64 {
20	if v <= 0.04045 {
21		return v / 12.92
22	}
23	return math.Pow((v+0.055)/1.055, 2.4)
24}
25
26// PQEncode encodes absolute luminance in nits using SMPTE ST 2084.
27func PQEncode(nits float64) float64 {
28	if nits <= 0 {
29		return 0
30	}
31	x := math.Pow(min(nits, 10000)/10000, 2610.0/16384)
32	return math.Pow((3424.0/4096+2413.0/128*x)/(1+2392.0/128*x), 2523.0/32)
33}
34
35// Pixel converts premultiplied BGRA bytes in place, preserving alpha. The
36// source is quantized to its 8-bit unassociated sRGB value for the LUT.
37func Pixel(p []byte, nits float64) {
38	a := float64(p[3]) / 255
39	if a == 0 {
40		clear(p[:3])
41		return
42	}
43	index := func(v byte) int { return min(255, int(math.Round(float64(v)/a))) }
44	r, g, b := srgbTable[index(p[2])], srgbTable[index(p[1])], srgbTable[index(p[0])]
45	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)
46	p[0], p[1], p[2] = byte(math.Round(b*a*255)), byte(math.Round(g*a*255)), byte(math.Round(r*a*255))
47}
48
49// Cache converts cursor images and keeps only the last result, bounded by
50// the cursor plane's maximum side regardless of cursor theme size. The
51// zero value is ready; it is not safe for concurrent use.
52type Cache struct {
53	key    [32]byte
54	pixels []byte
55}
56
57// Len is the size of the cached conversion in bytes (0: empty).
58func (c *Cache) Len() int { return len(c.pixels) }
59
60// Key identifies the cached conversion (the zero key: empty).
61func (c *Cache) Key() [32]byte { return c.key }
62
63// Reset drops the cached conversion.
64func (c *Cache) Reset() { *c = Cache{} }
65
66func key(pixels []byte, w, h, side int, nits float64) [32]byte {
67	var header [32]byte
68	binary.LittleEndian.PutUint64(header[:8], uint64(w))
69	binary.LittleEndian.PutUint64(header[8:16], uint64(h))
70	binary.LittleEndian.PutUint64(header[16:24], math.Float64bits(nits))
71	binary.LittleEndian.PutUint64(header[24:], uint64(side))
72	digest := sha256.New()
73	_, _ = digest.Write(header[:])
74	_, _ = digest.Write(pixels)
75	var k [32]byte
76	digest.Sum(k[:0])
77	return k
78}
79
80// Convert returns the w×h premultiplied BGRA image (w*4 per row) cropped
81// to side×side and converted for an SDR white of nits, packed without row
82// padding: min(w, side) pixels per row, min(h, side) rows. The result is
83// owned by the cache and valid until the next call.
84func (c *Cache) Convert(pixels []byte, w, h, side int, nits float64) []byte {
85	pixels = pixels[:w*h*4]
86	k := key(pixels, w, h, side, nits)
87	if c.pixels != nil && c.key == k {
88		return c.pixels
89	}
90	cw, ch := min(w, side)*4, min(h, side)
91	out := make([]byte, cw*ch)
92	for y := range ch {
93		copy(out[y*cw:(y+1)*cw], pixels[y*w*4:y*w*4+cw])
94		for x := 0; x < cw; x += 4 {
95			Pixel(out[y*cw+x:y*cw+x+4], nits)
96		}
97	}
98	c.key, c.pixels = k, out
99	return out
100}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 23

Tags 10

↑↓ moveenter openesc close