Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
known.cpp
Go to the documentation of this file.
1#include <haio_palette.hpp>
2
3#include <algorithm>
4#include <array>
5#include <charconv>
6#include <string>
7
8namespace {
9
10struct Known {
11 std::string_view name;
12 std::span<const uint32_t> colours;
13};
14
15/**
16 * the master the console itself draws from, sixty four entries deep. a nes picture
17 * never holds more than four at once, and which four is what a named palette below
18 * is really choosing.
19 */
20constexpr std::array<uint32_t, 64> nes = {
21 0x808080, 0x003DA6, 0x0012B0, 0x440096, 0xA1005E, 0xC70028, 0xBA0600, 0x8C1700,
22 0x5C2F00, 0x104500, 0x054A00, 0x00472E, 0x004166, 0x000000, 0x050505, 0x050505,
23 0xC7C7C7, 0x0077FF, 0x2155FF, 0x8237FA, 0xEB2FB5, 0xFF2950, 0xFF2200, 0xD63200,
24 0xC46200, 0x358000, 0x058F00, 0x008A55, 0x0099CC, 0x212121, 0x090909, 0x090909,
25 0xFFFFFF, 0x0FD7FF, 0x69A2FF, 0xD480FF, 0xFF45F3, 0xFF618B, 0xFF8833, 0xFF9C12,
26 0xFABC20, 0x9FE30E, 0x2BF035, 0x0CF0A4, 0x05FBFF, 0x5E5E5E, 0x0D0D0D, 0x0D0D0D,
27 0xFFFFFF, 0xA6FCFF, 0xB3ECFF, 0xDAABEB, 0xFFA8F9, 0xFFABB3, 0xFFD2B0, 0xFFEFA6,
28 0xFFF79C, 0xD7E895, 0xA6EDAF, 0xA2F2DA, 0x99FFFC, 0xDDDDDD, 0x111111, 0x111111,
29};
30
31/** ibm's, the sixteen an ega card shows in text mode and everybody recognises */
32constexpr std::array<uint32_t, 16> cga = {
33 0x000000, 0x0000AA, 0x00AA00, 0x00AAAA, 0xAA0000, 0xAA00AA, 0xAA5500, 0xAAAAAA,
34 0x555555, 0x5555FF, 0x55FF55, 0x55FFFF, 0xFF5555, 0xFF55FF, 0xFFFF55, 0xFFFFFF,
35};
36
37constexpr std::array<uint32_t, 16> pico8 = {
38 0x000000, 0x1D2B53, 0x7E2553, 0x008751, 0xAB5236, 0x5F574F, 0xC2C3C7, 0xFFF1E8,
39 0xFF004D, 0xFFA300, 0xFFEC27, 0x00E436, 0x29ADFF, 0x83769C, 0xFF77A8, 0xFFCCAA,
40};
41
42constexpr std::array<uint32_t, 16> tic80 = {
43 0x1A1C2C, 0x5D275D, 0xB13E53, 0xEF7D57, 0xFFCD75, 0xA7F070, 0x38B764, 0x257179,
44 0x29366F, 0x3B5DC9, 0x41A6F6, 0x73EFF7, 0xF4F4F4, 0x94B0C2, 0x566C86, 0x333C57,
45};
46
47/**
48 * the four colour sets chrfonts ships, written here as the colours they resolve to
49 * rather than as indices into the nes master.
50 *
51 * keeping the resolved colour means a palette is a palette whatever console it came
52 * from, and a picture converted with "mario" does not have to be a nes picture.
53 */
54constexpr std::array<uint32_t, 4> cgaGraphics = {0x050505, 0xFFFFFF, 0xFF45F3, 0x05FBFF};
55constexpr std::array<uint32_t, 4> kokobatoru = {0x050505, 0xFFFFFF, 0xFF9C12, 0xFF2200};
56constexpr std::array<uint32_t, 4> mario = {0x69A2FF, 0xFF2200, 0xFF9C12, 0xC46200};
57constexpr std::array<uint32_t, 4> world = {0x69A2FF, 0x9FE30E, 0x058F00, 0x050505};
58
59constexpr std::array<Known, 8> known = {{
60 {"nes", nes},
61 {"cga", cga},
62 {"pico8", pico8},
63 {"tic80", tic80},
64 {"cgagraphics", cgaGraphics},
65 {"kokobatoru", kokobatoru},
66 {"mario", mario},
67 {"world", world},
68}};
69
70std::optional<size_t> readNumber(std::string_view text) {
71 size_t value = 0;
72 const auto* end = text.data() + text.size();
73 const auto parsed = std::from_chars(text.data(), end, value);
74 if (parsed.ec != std::errc{} || parsed.ptr != end) return std::nullopt;
75 return value;
76}
77
78}
79
80namespace Haio {
81
82std::span<const std::string_view> paletteNames() {
83 static const auto names = [] {
84 std::array<std::string_view, known.size()> out{};
85 std::ranges::transform(known, out.begin(), [](const Known& one) { return one.name; });
86 return out;
87 }();
88 return names;
89}
90
91Result<std::vector<uint32_t>> paletteNamed(std::string_view spec, size_t wanted) {
92 auto name = spec;
93 std::string_view slice;
94
95 /**
96 * a colon rather than brackets, because a shell eats "<" and ">" as redirections
97 * and would need the whole thing quoted to reach here at all.
98 */
99 if (const auto colon = spec.find(':'); colon != std::string_view::npos) {
100 name = spec.substr(0, colon);
101 slice = spec.substr(colon + 1);
102 if (slice.empty()) {
103 return std::unexpected(Error{ErrorCode::InvalidInput,
104 "\"" + std::string(spec) + "\" names nothing after its colon"});
105 }
106 }
107
108 const auto found = std::ranges::find_if(known, [name](const Known& one) { return one.name == name; });
109 if (found == known.end()) {
110 std::string message = "no palette called \"" + std::string(name) + "\"; haio knows ";
111 for (const auto& one : known) {
112 message += std::string(one.name);
113 if (&one != &known.back()) message += ", ";
114 }
115 return std::unexpected(Error{ErrorCode::InvalidInput, message});
116 }
117
118 const auto colours = found->colours;
119 if (slice.empty()) return std::vector<uint32_t>(colours.begin(), colours.end());
120
121 std::vector<uint32_t> out;
122
123 /**
124 * a bare number on its own is a bank; anything else spells out which colours.
125 *
126 * the two cannot be told apart by looking at the number, so they are told apart
127 * by shape: "tic80:2" is the second bank of whatever the picture needs, while
128 * "tic80:2..4" and "tic80:2,5" are colours, counted from one. a range is there
129 * because asking for the first three of a palette should not mean writing them
130 * all out.
131 */
132 const bool isBank = slice.find(',') == std::string_view::npos
133 && slice.find("..") == std::string_view::npos;
134
135 if (isBank) {
136 const auto bank = readNumber(slice);
137 if (!bank || *bank == 0) {
138 return std::unexpected(Error{ErrorCode::InvalidInput,
139 "a palette bank is counted from one, as in " + std::string(name)
140 + ":1, and a span is written " + std::string(name) + ":1..3"});
141 }
142 if (wanted == 0) {
143 return std::unexpected(Error{ErrorCode::InvalidInput,
144 "a bank is as wide as the picture needs, and this picture needs nothing"});
145 }
146
147 const auto from = (*bank - 1) * wanted;
148 if (from + wanted > colours.size()) {
149 return std::unexpected(Error{ErrorCode::InvalidInput,
150 std::string(name) + " has " + std::to_string(colours.size())
151 + " colours, which is not enough for bank " + std::to_string(*bank)
152 + " of " + std::to_string(wanted)});
153 }
154 out.assign(colours.begin() + from, colours.begin() + from + wanted);
155 return out;
156 }
157
158 const auto colourAt = [&](std::string_view piece) -> Result<size_t> {
159 const auto at = readNumber(piece);
160 if (!at || *at == 0 || *at > colours.size()) {
161 return std::unexpected(Error{ErrorCode::InvalidInput,
162 "\"" + std::string(piece) + "\" is not a colour of " + std::string(name)
163 + ", which has " + std::to_string(colours.size())
164 + " counted from one"});
165 }
166 return *at;
167 };
168
169 while (!slice.empty()) {
170 const auto comma = slice.find(',');
171 const auto piece = slice.substr(0, comma);
172
173 if (const auto dots = piece.find(".."); dots != std::string_view::npos) {
174 const auto first = colourAt(piece.substr(0, dots));
175 if (!first) return std::unexpected(first.error());
176 const auto last = colourAt(piece.substr(dots + 2));
177 if (!last) return std::unexpected(last.error());
178
179 if (*first > *last) {
180 return std::unexpected(Error{ErrorCode::InvalidInput,
181 "\"" + std::string(piece) + "\" runs backwards"});
182 }
183 for (auto at = *first; at <= *last; at++) out.push_back(colours[at - 1]);
184 } else {
185 const auto at = colourAt(piece);
186 if (!at) return std::unexpected(at.error());
187 out.push_back(colours[*at - 1]);
188 }
189
190 if (comma == std::string_view::npos) break;
191 slice.remove_prefix(comma + 1);
192 }
193 return out;
194}
195
196}
Definition haio.hpp:11
Result< std::vector< uint32_t > > paletteNamed(std::string_view spec, size_t wanted)
Definition known.cpp:91
std::expected< T, Error > Result
std::span< const std::string_view > paletteNames()
Definition known.cpp:82