src/recolor-assets.py 3.6 K raw
1
#!/usr/bin/env python3
2
"""Recolour the GTK widget assets from Nordic's palette to Darkmatter's.
3
4
Run from the theme root:  python3 src/recolor-assets.py
5
Idempotent only against the pristine Nordic assets -- re-running over already
6
converted files will drift, so restore from Nordic first if you need to redo.
7
"""
8
9
import glob
10
import os
11
import sys
12
13
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "lib"))
14
15
import darkmatter as dm
16
from png import Png
17
from remap import Remapper
18
19
R = dm.rgb
20
21
# Nordic source colour -> Darkmatter target, for every asset that has no
22
# override below. Surfaces keep their stacking order; the cyan ramp that
23
# upstream used for selection collapses onto the orange accent ramp.
24
ANCHORS = [
25
    # surfaces
26
    ("#000000", "#000000"),
27
    ("#0b0d10", dm.SHADOW),
28
    ("#1d2129", dm.DARK),
29
    ("#232831", dm.BG),
30
    ("#3b4252", dm.BASE),
31
    ("#5d6a83", "#4a494b"),   # control outlines (unchecked check/radio)
32
    ("#7f8a99", "#6a696b"),
33
    ("#a0a0a6", dm.DIM),
34
    ("#b7b7bf", dm.SUBTLE),
35
    ("#c4c4c4", "#c4c4c4"),
36
    ("#f0f0f3", "#f0f0f0"),
37
    ("#f5f5f5", "#f5f5f5"),
38
    ("#fafafa", "#fafafa"),
39
    ("#fefefe", "#fefefe"),
40
    ("#ffffff", dm.FG),
41
    # the accent ramp
42
    ("#0f8099", dm.ORANGE_D2),
43
    ("#779c9b", "#c2733f"),
44
    ("#1396b2", dm.ORANGE_D1),
45
    ("#13a0bf", dm.ORANGE),
46
    ("#37b9a5", dm.ORANGE),
47
    ("#8fbcbb", dm.ORANGE),
48
    ("#42b3cc", dm.ORANGE_L1),
49
    ("#13b1d5", dm.ORANGE_L1),
50
    ("#5ec3d8", dm.ORANGE_L2),
51
    ("#13d5ff", dm.ORANGE_L3),
52
    # remaining hues
53
    ("#ebcb8b", dm.CREAM),
54
    ("#bf616a", dm.RED),
55
    ("#a3be8c", dm.TEAL),
56
]
57
58
# The window controls are traffic lights: a coloured disc on a dark rim, with
59
# the hue carrying the state. Darkmatter keeps them grey and spends the accent
60
# on hover instead, so the disc colour depends on which file it appears in.
61
DISC = {
62
    "close":              dm.DIM,
63
    "close_prelight":     dm.ORANGE,
64
    "close_unfocused":    dm.BORDER_HI,
65
    "maximize":           dm.DIM,
66
    "maximize_prelight":  dm.SUBTLE,
67
    "maximize_unfocused": dm.BORDER_HI,
68
    "min":                dm.DIM,
69
    "min_prelight":       dm.SUBTLE,
70
    "min_unfocused":      dm.BORDER_HI,
71
}
72
# which source colour holds the disc in each of those files
73
DISC_SRC = {
74
    "close": "#bf616a", "close_prelight": "#bf616a", "close_unfocused": "#3b4252",
75
    "maximize": "#a3be8c", "maximize_prelight": "#a3be8c", "maximize_unfocused": "#3b4252",
76
    "min": "#ebcb8b", "min_prelight": "#ebcb8b", "min_unfocused": "#3b4252",
77
}
78
79
80
def anchors_for(path):
81
    stem = os.path.basename(path)[:-4].replace("@2", "")
82
    if stem not in DISC:
83
        return ANCHORS
84
    over = {R(DISC_SRC[stem]): R(DISC[stem]), R("#232831"): R(dm.SHADOW)}
85
    return [(s, over.get(s, t)) for s, t in
86
            [(R(a), R(b)) for a, b in ANCHORS]]
87
88
89
def normalise(anchors):
90
    if anchors and isinstance(anchors[0][0], str):
91
        return [(R(a), R(b)) for a, b in anchors]
92
    return anchors
93
94
95
def main():
96
    files = sorted(glob.glob("assets/*.png"))
97
    if not files:
98
        sys.exit("run me from the theme root (no assets/*.png found)")
99
    changed = 0
100
    for path in files:
101
        remap = Remapper(normalise(anchors_for(path)))
102
        img = Png(path)
103
        if img.color_type == 6:
104
            px = img.rgba()
105
            img.set_rgba([remap(p[:3]) + (p[3],) for p in px])
106
        elif img.color_type == 3:
107
            img.set_palette([remap(c) for c in img.palette()])
108
        else:
109
            print("  skipped (unsupported colour type %d): %s" % (img.color_type, path))
110
            continue
111
        img.save()
112
        changed += 1
113
    print("recoloured %d of %d assets" % (changed, len(files)))
114
115
116
if __name__ == "__main__":
117
    main()