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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
#!/usr/bin/env python3
# Generate src/color.asm: CGB palette data + the lookup tables the terminal
# uses to render per-glyph FOREGROUND *and* BACKGROUND color.
#
# The 7 colors are the points of a FANO PLANE (Steiner triple system S(2,3,7)):
# 7 palettes = 7 lines, and every PAIR of colors lies on exactly one line, so a
# tile that needs at most two distinct (non-black) colors always has a palette
# holding both. slot0 of every palette is black, so a black background costs no
# color id; a colored background is drawn by giving the cell's empty pixels the
# background color's slot instead of slot0.
#
# Because a tile packs two independent cells (each with fg+bg) it can need up to
# 3 non-black colors. Every color pair is covered exactly; a 3-color tile is
# covered only if that triple happens to be a line - otherwise BestPal picks the
# line covering the most of them and the odd one out is approximated.
#
# Color index: 0=black(bg/none) 1=red 2=green 3=yellow 4=blue 5=magenta 6=cyan
# 7=white. BG palette 1 is reserved for the on-screen keyboard.
def gbc15(r, g, b):
return r | (g << 5) | (b << 10)
COLOR_RGB = {
1: (31, 0, 0), # red
2: ( 0, 31, 0), # green
3: (31, 31, 0), # yellow
4: ( 0, 0, 31), # blue
5: (31, 0, 31), # magenta
6: ( 0, 31, 31), # cyan
7: (31, 31, 31), # white
}
# Hardware BG palette -> its 3 colors in slot1,slot2,slot3 order (slot0=black).
# The 7 triples form a Fano plane over colors 1..7. Palette 0 keeps white at
# slot1 (the OSK and plain white text draw with color-id 1).
PALETTES = {
0: [7, 1, 6],
2: [1, 2, 3],
3: [1, 4, 5],
4: [2, 4, 6],
5: [2, 5, 7],
6: [3, 4, 7],
7: [3, 5, 6],
}
PAL_ORDER = [0, 2, 3, 4, 5, 6, 7]
# --- verify Fano: every pair of 1..7 lies on exactly one line ------------------
seen = {}
for pal, line in PALETTES.items():
assert len(set(line)) == 3
for i in range(3):
for j in range(i + 1, 3):
k = frozenset((line[i], line[j]))
assert k not in seen, ("pair on two lines", k)
seen[k] = pal
assert set(seen) == {frozenset((a, b)) for a in range(1, 8)
for b in range(1, 8) if a != b}
def slot_of(pal, color):
"""id (0..3) of color in palette pal. black->0; absent->1 (visible fallback)."""
if color == 0:
return 0
line = PALETTES.get(pal, [])
return (line.index(color) + 1) if color in line else 1
def best_pal(mask):
"""Palette covering the most colors present in the 7-bit set 'mask'."""
if mask == 0:
return 0
best, bestn = 0, -1
for pal in PAL_ORDER:
n = sum(1 for c in PALETTES[pal] if mask & (1 << (c - 1)))
if n > bestn:
best, bestn = pal, n
return best
# --- emit ----------------------------------------------------------------------
def pal_colors(hw):
if hw == 1: # OSK: white bg, black ink (inverse)
return [(31,31,31), (0,0,0), (0,0,0), (0,0,0)]
return [(0,0,0)] + [COLOR_RGB[c] for c in PALETTES[hw]]
L = []
L.append("; auto-generated by tools/gencolor.py - Fano-plane color tables")
L.append("; (foreground + background). DO NOT EDIT.")
L.append('SECTION "color", ROM0')
L.append("")
L.append("; 8 CGB BG palettes, 4 colors each (c0=black). Loaded at BCPS $80.")
L.append("PalData::")
for hw in range(8):
words = []
for (r, g, b) in pal_colors(hw):
v = gbc15(r, g, b)
words.append("$%02X,$%02X" % (v & 0xFF, (v >> 8) & 0xFF))
L.append(" db " + ",".join(words) + " ; palette %d" % hw)
L.append("")
L.append("; BitTab[color] = bit for that color in the 'colors present' set (black=0)")
L.append("BitTab::")
L.append(" db " + ",".join("$%02X" % (0 if c == 0 else (1 << (c - 1))) for c in range(8)))
L.append("")
L.append("; BestPal[set] = palette (0..7) covering the most colors in a 7-bit set")
L.append("BestPal::")
for base in range(0, 128, 16):
L.append(" db " + ",".join("$%02X" % best_pal(base + j) for j in range(16))
+ " ; %d..%d" % (base, base + 15))
L.append("")
L.append("; SlotOf[palette*8 + color] = that color's slot/id (0..3) in the palette")
L.append("SlotOf::")
for pal in range(8):
L.append(" db " + ",".join("$%02X" % slot_of(pal, c) for c in range(8))
+ " ; palette %d" % pal)
open("/home/user/dev/gbos/src/color.asm", "w").write("\n".join(L) + "\n")
print("wrote src/color.asm")
|