Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
haio_registry.hpp
Go to the documentation of this file.
1#pragma once
2
3// the generated list comes first on purpose. everything below asks the codecs what
4// they can do, and a concept evaluated before they are declared caches the answer
5// "no" for the rest of the build, silently.
6#include "haio_codecs.hpp"
7
8#include "haio_codec.hpp"
9#include "haio_convert.hpp"
10
11namespace Haio {
12
13constexpr std::string_view formatName(Format wanted) {
15 if (std::meta::extract<Format>(e) == wanted) return lowerOf(e);
16 }
17 return "raw";
18}
19
20constexpr std::string_view colorName(Color wanted) {
22 if (std::meta::extract<Color>(e) == wanted) return lowerOf(e);
23 }
24 return "rgba8888";
25}
26
27namespace Detail {
28
29constexpr std::string_view lowered(std::string_view name, char (&buffer)[32]) {
30 if (name.size() >= sizeof(buffer)) return {};
31 for (size_t i = 0; i < name.size(); i++) {
32 const char c = name[i];
33 buffer[i] = (c >= 'A' && c <= 'Z') ? static_cast<char>(c - 'A' + 'a') : c;
34 }
35 return {buffer, name.size()};
36}
37
38}
39
40constexpr Format formatFromName(std::string_view name) {
41 char buffer[32]{};
42 const auto key = Detail::lowered(name, buffer);
44 if (lowerOf(e) == key) return std::meta::extract<Format>(e);
45 }
46 return Format::RAW;
47}
48
49/**
50 * the colour somebody typed, or nothing when no colour answers to that name.
51 *
52 * the enumerator names come first and then colorAliases, which is every other name
53 * the same layouts go by. borrowing ffmpeg's option without its vocabulary would be
54 * borrowing the half that does not help.
55 */
56constexpr std::optional<Color> colorNamed(std::string_view name) {
57 char buffer[32]{};
58 const auto key = Detail::lowered(name, buffer);
60 if (lowerOf(e) == key) return std::meta::extract<Color>(e);
61 }
62
63 for (const auto& alias : colorAliases) {
64 if (alias.spelling == key) return alias.color;
65 }
66 return std::nullopt;
67}
68
69/** the same question where an unknown name is not worth stopping for */
70constexpr Color colorFromName(std::string_view name) {
71 const auto color = colorNamed(name);
72 return color ? *color : Color::RGBA8888;
73}
74
75/** what a file turned out to be: the container and the colour it holds */
76struct Found {
79
80 explicit operator bool() const noexcept { return format != Format::RAW; }
81};
82
83/** asks every declared pair, so "which payload is inside" needs no separate reader */
84inline Found Detect(Bytes data) {
85 Found found;
87 constexpr Format format = std::meta::extract<Format>(f);
89 constexpr Color color = std::meta::extract<Color>(c);
91 if (!found && Codecs::Detect<format, color>(data)) found = Found{format, color};
92 }
93 }
94 }
95 return found;
96}
97
98constexpr bool detectable(Format format, Color color) {
100 constexpr Format ff = std::meta::extract<Format>(f);
102 constexpr Color cc = std::meta::extract<Color>(c);
103 if (ff == format && cc == color) return Codecs::Detectable<ff, cc>;
104 }
105 }
106 return false;
107}
108
109constexpr bool encodable(Format format, Color color) {
111 constexpr Format ff = std::meta::extract<Format>(f);
113 constexpr Color cc = std::meta::extract<Color>(c);
114 if (ff == format && cc == color) return Codecs::Encodable<ff, cc>;
115 }
116 }
117 return false;
118}
119
120/**
121 * the colour a container is written in when nobody names one: the one it declares as
122 * its own, and failing that the first it can write at all.
123 *
124 * before this there was only the second half, so the colour a file came out in was
125 * whichever one happened to be declared earliest in the enum. that is fine while a
126 * container writes one colour and arbitrary the moment it writes six.
127 */
128constexpr std::optional<Color> encodeColorFor(Format to) {
129 std::optional<Color> first;
131 constexpr Format format = std::meta::extract<Format>(f);
132 if (format != to) continue;
134 constexpr Color color = std::meta::extract<Color>(c);
135 if constexpr (Codecs::Encodable<format, color>) {
136 if constexpr (Codecs::HasDefaultColor<format>) {
137 if (Codecs::DefaultColor<format>::value == color) return color;
138 }
139 if (!first) first = color;
140 }
141 }
142 }
143 return first;
144}
145
146/**
147 * the runtime way in: reads the bytes, routes to the pair that matched, and brings
148 * the result to rgba8888 so the caller has one type to hold.
149 *
150 * @todo everything funnels through rgba8888, so ktx2(etc1) -> ktx2 decompresses and
151 * recompresses for nothing. keeping the payload needs a typed path, which is exactly
152 * what Codecs::Decode<Format, Color> already offers; only this runtime bridge flattens it.
153 */
155 const auto found = blob.format != Format::RAW ? Found{blob.format, blob.color} : Detect(blob.data);
156 if (!found) {
157 return std::unexpected(Error{ErrorCode::UnsupportedFormat, "unrecognised input"});
158 }
159
161 std::unexpected(Error{ErrorCode::UnsupportedFormat,
162 "cannot decode " + std::string(formatName(found.format))
163 + " " + std::string(colorName(found.color))});
164
166 constexpr Format format = std::meta::extract<Format>(f);
168 constexpr Color color = std::meta::extract<Color>(c);
169 if constexpr (Codecs::Decodable<format, color>) {
170 if (format != found.format || color != found.color) continue;
171
172 auto decoded = Codecs::Decode<format, color>(blob);
173 if (!decoded) {
174 out = std::unexpected(decoded.error());
175 } else if constexpr (color == Color::RGBA8888) {
176 out = *std::move(decoded);
177 } else if constexpr (Codecs::Convertible<color, Color::RGBA8888>) {
178 out = Codecs::Convert<color, Color::RGBA8888>(*std::move(decoded));
179 } else {
180 out = std::unexpected(Error{ErrorCode::UnsupportedFormat,
181 "no route from " + std::string(colorName(color)) + " to rgba8888"});
182 }
183 }
184 }
185 }
186 return out;
187}
188
189/**
190 * the same picture in another colour, named at runtime.
191 *
192 * it hands back bytes rather than an image because at runtime there is no Image<P> to
193 * hand back: the colour is a value here, and the type it would pick is a compile time
194 * thing. the callers that want one already know which they want and say so through
195 * Codecs::Convert.
196 */
198 if (to == Color::RGBA8888) return image.data;
199
201 std::unexpected(Error{ErrorCode::UnsupportedFormat,
202 "no route from rgba8888 to " + std::string(colorName(to))});
203
205 constexpr Color color = std::meta::extract<Color>(c);
207 if (color != to) continue;
208
209 auto converted = Codecs::Convert<Color::RGBA8888, color>(image);
210 if (!converted) {
211 out = std::unexpected(converted.error());
212 } else {
213 out = std::move(converted->data);
214 }
215 }
216 }
217 return out;
218}
219
220/**
221 * the runtime way out. rgba8888 goes in because that is what the runtime decode
222 * hands back; the colour stored inside is the one asked for, or the one the container
223 * calls its own when nobody asked.
224 *
225 * naming it is what -pix_fmt does, and it is the only way to say "a ktx2 holding
226 * etc1" or "a tga holding bgr888" from a command line: the pair was always there in
227 * Codecs::Encode, with nothing but this bridge between it and a string.
228 */
229inline Result<Blob> Encode(Image<Color::RGBA8888> image, Format to, std::optional<Color> as = std::nullopt) {
230 const auto wanted = as ? as : encodeColorFor(to);
231 if (!wanted) {
232 return std::unexpected(Error{ErrorCode::UnsupportedFormat,
233 "cannot encode " + std::string(formatName(to))});
234 }
235
236 Result<Blob> out = std::unexpected(Error{ErrorCode::UnsupportedFormat,
237 "a " + std::string(formatName(to)) + " cannot hold "
238 + std::string(colorName(*wanted))});
239 bool done = false;
240
242 constexpr Color color = std::meta::extract<Color>(c);
244 constexpr Format format = std::meta::extract<Format>(f);
245 if constexpr (Codecs::Encodable<format, color>) {
246 if (done || format != to || color != *wanted) continue;
247
248 if constexpr (color == Color::RGBA8888) {
250 done = true;
251 } else if constexpr (Codecs::Convertible<Color::RGBA8888, color>) {
252 auto converted = Codecs::Convert<Color::RGBA8888, color>(image);
253 out = converted ? Codecs::Encode<format, color>(*std::move(converted))
254 : Result<Blob>{std::unexpected(converted.error())};
255 done = true;
256 } else {
257 // the pair exists and the road to it does not, which is a different
258 // sentence from the container not holding that colour at all
259 out = std::unexpected(Error{ErrorCode::UnsupportedFormat,
260 "no route from rgba8888 to " + std::string(colorName(color))});
261 done = true;
262 }
263 }
264 }
265 }
266 return out;
267}
268
269/**
270 * the indexed way out, for the containers that store a palette rather than expanding
271 * it. there is no conversion into it here on purpose: haio does not pick colours for
272 * anybody, so the picture arrives already fitted to a palette or not at all.
273 */
275 Result<Blob> out = std::unexpected(Error{ErrorCode::UnsupportedFormat,
276 "a " + std::string(formatName(to)) + " cannot hold a palette"});
278 constexpr Format format = std::meta::extract<Format>(f);
280 if (format != to) continue;
281 out = Codecs::Encode<format, Color::PALETTE>(std::move(image));
282 }
283 }
284 return out;
285}
286
287}
#define HAIO_FOR_EACH_COLOR(e)
#define HAIO_FOR_EACH_FORMAT(e)
Result< Image< To > > Convert(Image< From >)=delete
Result< Image< P > > Decode(const Blob &)=delete
bool Detect(Bytes)=delete
Result< Blob > Encode(Image< P >)=delete
constexpr std::string_view lowered(std::string_view name, char(&buffer)[32])
Definition haio.hpp:11
Result< Blob > Encode(Image< Color::RGBA8888 > image, Format to, std::optional< Color > as=std::nullopt)
constexpr std::optional< Color > encodeColorFor(Format to)
Found Detect(Bytes data)
constexpr bool detectable(Format format, Color color)
constexpr ColorAlias colorAliases[]
constexpr bool encodable(Format format, Color color)
constexpr Format formatFromName(std::string_view name)
constexpr std::optional< Color > colorNamed(std::string_view name)
std::vector< uint8_t > data
std::expected< T, Error > Result
consteval std::string_view lowerOf(std::meta::info enumerator)
constexpr std::string_view formatName(Format wanted)
Result< Image< Color::RGBA8888 > > Decode(const Blob &blob)
constexpr std::string_view colorName(Color wanted)
constexpr Color colorFromName(std::string_view name)
std::span< const uint8_t > Bytes
Result< std::vector< uint8_t > > Convert(const Image< Color::RGBA8888 > &image, Color to)
Format format
std::vector< uint8_t > data