Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
haio_codec.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "haio_common.hpp"
4#include "haio_formats.hpp"
5#include "haio_object.hpp"
6
7#include <expected>
8#include <meta>
9#include <optional>
10#include <span>
11
12namespace Haio {
13
14using Bytes = std::span<const uint8_t>;
15
25
30
31template <typename T>
32using Result = std::expected<T, Error>;
33
34/** pixels in memory. it has a colour and no container, because it is not a file yet */
35template <Color P>
36struct Image {
37 static constexpr Color color = P;
38 int width = 0;
39 int height = 0;
40 std::vector<uint8_t> data;
41};
42
43/**
44 * how wide one pixel is, or zero when the colour has no such number: etc1 packs four
45 * by four blocks and yuv420 splits into planes, so neither can be measured a pixel at
46 * a time. sizeOf reads this too, so the widths are written down once.
47 */
48constexpr size_t strideOf(Color color) {
49 switch (color) {
50 case Color::RGBA8888: return 4;
51 case Color::RGB888: return 3;
52 case Color::RGB565: return 2;
53 case Color::RGB555: return 2;
54 case Color::RGBA5551: return 2;
55 case Color::BGR888: return 3;
56 case Color::BGRA8888: return 4;
57 case Color::GRAY8: return 1;
58 // one byte per pixel, so a crop or a resize can index it like any other
59 case Color::PALETTE: return 1;
60 // tiled and planar, so no pixel has an address of its own
61 case Color::CHR_NES: break;
62 case Color::ETC1: break;
63 case Color::YUV420: break;
64 }
65 return 0;
66}
67
68/** a transform can index these; having no stride is exactly what rules the others out */
69template <Color P> concept Addressable = strideOf(P) != 0;
70
71/**
72 * which byte of a pixel holds its alpha, or a negative when the colour has none.
73 * the two eight bit colours answer the same, because where the alpha sits is all a
74 * masking loop ever wanted to know and neither of them moved it. written as a rule so
75 * that the day another arrives, rgba4444 in a ktx or a palette that keeps a
76 * transparent entry, it is one line here rather than a second copy of the loop.
77 */
78constexpr int alphaOffsetOf(Color color) {
79 switch (color) {
80 case Color::RGBA8888: return 3;
81 case Color::BGRA8888: return 3;
82 case Color::RGB888: break;
83 case Color::RGB565: break;
84 case Color::BGR888: break;
85 case Color::RGB555: break;
86 // it has an alpha, but it is one bit sharing a byte with the blue, so there
87 // is nothing here to clear on its own
88 case Color::RGBA5551: break;
89 case Color::GRAY8: break;
90 // tiled and planar, so no pixel has an address of its own
91 case Color::CHR_NES: break;
92 case Color::ETC1: break;
93 case Color::YUV420: break;
94 }
95 return -1;
96}
97
98/** and these can have a corner rounded away, because there is an alpha to clear */
99template <Color P> concept Maskable = Addressable<P> && alphaOffsetOf(P) >= 0;
100
101/**
102 * the one colour that is not self describing: a byte per pixel means nothing without
103 * the colours it points at, so the palette travels with the picture.
104 *
105 * it is a specialisation rather than a member on every Image, because every other
106 * colour would carry an empty vector around for nothing. the cost is that Move, which
107 * moves spans of bytes, has no way to carry the entries: palette conversions go
108 * through Convert directly and never through convertVia.
109 */
110template <>
112 static constexpr Color color = Color::PALETTE;
113 int width = 0;
114 int height = 0;
115 std::vector<uint8_t> data;
116
117 /** 0xAARRGGBB, at most 256 of them, and an index past the end is an error */
118 std::vector<uint32_t> entries;
119};
120
121/** bytes of a file. it has a container and the colour it was found to hold */
122struct Blob {
125 std::string contentType = "application/octet-stream";
126 std::string path;
127 std::vector<uint8_t> data;
128};
129
130/**
131 * the verbs. they live apart from the vocabulary above because the runtime facade in
132 * haio_registry.hpp offers Decode(const Blob&) and Detect(Bytes) with the very same
133 * parameter lists: without a namespace between them, Decode(blob) and
134 * Decode<Format::PNG>(blob) read alike and do different things.
135 */
136namespace Codecs {
137
138/**
139 * the colour a format carries when nobody says otherwise. only formats with a real
140 * answer declare it: a ktx2 holds any of three, so Decode<Format::KTX2> alone is a
141 * compile error telling the caller to be specific.
142 */
143template <Format F> struct DefaultColor;
144
145/**
146 * deleted on purpose. a codec declares only the pairs it can do, and naming one it
147 * left out is ill-formed, which is what lets the concepts below answer for themselves
148 * instead of failing at link time. drop a codec from the build and its format stays
149 * in the enum, naming itself, with every capability gone.
150 */
151template <Format F, Color P = DefaultColor<F>::value> bool Detect(Bytes) = delete;
152template <Format F, Color P = DefaultColor<F>::value> Result<Image<P>> Decode(const Blob&) = delete;
153template <Format F, Color P> Result<Blob> Encode(Image<P>) = delete;
154
155/**
156 * @name Convert
157 * @{
158 */
159/** colour to colour, no file involved. the heavy ones live behind Move */
160template <Color From, Color To> Result<Image<To>> Convert(Image<From>) = delete;
161/**
162 * @}
163 */
164
165/**
166 * the hot loop under Convert, and what a codec calls when it wants to write pixels
167 * straight into a buffer it already owns. dst is sized by the caller so the simd
168 * paths can rely on it, and size is here because block formats like etc1 cannot be
169 * transformed pixel by pixel.
170 *
171 * it reports rather than assumes: the loop runs once per image, so one Result on the
172 * way out costs nothing next to the work inside it.
173 */
174template <Color From, Color To> Result<void> Move(Bytes src, std::span<uint8_t> dst, Size size) = delete;
175
176/**
177 * @defgroup detect Detect
178 * every container and colour pair this build can recognise. these are exactly the
179 * specialisations that satisfy Detectable, so a codec dropped from the build leaves
180 * this page and the concept together, while its format keeps its name in the enum.
181 */
182
183/**
184 * @defgroup decode Decode
185 * every pair this build can read into pixels. a format may be recognised here and
186 * still be missing from @ref decode, which is how haio names a file it cannot open.
187 */
188
189/**
190 * @defgroup encode Encode
191 * every pair this build can write out. the colour is the one stored inside the
192 * container, not the one the caller happens to hold.
193 */
194
195/**
196 * @defgroup convert Convert
197 * colour to colour with a whole image in hand. each one delegates to convertVia,
198 * which sizes the destination and hands the work to the matching @ref move.
199 */
200
201/**
202 * @defgroup move Move
203 * the hot loops, writing pixels into a buffer the caller already owns. this is where
204 * the simd lives.
205 */
206
207/**
208 * does this format name a colour of its own. a container that holds any of three has
209 * nothing to declare, so this is asked rather than assumed.
210 */
211template <Format F> concept HasDefaultColor = requires { DefaultColor<F>::value; };
212
213/**
214 * @ingroup detect
215 * can these bytes be recognised as this pair, asked of the pair rather than answered by hand.
216 */
217template <Format F, Color P> concept Detectable = requires (Bytes d) { { Detect<F, P>(d) } -> std::same_as<bool>; };
218/**
219 * @ingroup decode
220 * can this pair be read into pixels, asked of the pair rather than answered by hand.
221 */
222template <Format F, Color P> concept Decodable = requires (const Blob& b) { { Decode<F, P>(b) } -> std::same_as<Result<Image<P>>>; };
223/**
224 * @ingroup encode
225 * can an image of this colour be written into this container, asked of the pair rather than answered by hand.
226 */
227template <Format F, Color P> concept Encodable = requires (Image<P> i) { { Encode<F, P>(std::move(i)) } -> std::same_as<Result<Blob>>; };
228/**
229 * @ingroup move
230 * is there a loop from one colour to the other, asked of the pair rather than answered by hand.
231 */
232template <Color From, Color To> concept Movable = requires (Bytes s, std::span<uint8_t> d, Size z) { { Move<From, To>(s, d, z) } -> std::same_as<Result<void>>; };
233/**
234 * @ingroup convert
235 * is there a whole image conversion from one colour to the other, asked of the pair rather than answered by hand.
236 */
237template <Color From, Color To> concept Convertible = requires (Image<From> i) { { Convert<From, To>(std::move(i)) } -> std::same_as<Result<Image<To>>>; };
238
239}
240
241/**
242 * go style early return. c++26 still has no "?" operator, so unwrapping a Result
243 * and leaving on failure is either this or a monadic chain that reads badly when
244 * the steps are independent.
245 */
246#define HAIO_TRY(name, expr) \
247 auto name##Result = (expr); \
248 if (!name##Result) return std::unexpected(name##Result.error()); \
249 auto& name = *name##Result
250
251/** for a check that only reports a problem and produces no value */
252#define HAIO_CHECK(expr) \
253 do { \
254 if (auto problem = (expr)) return std::unexpected(*problem); \
255 } while (false)
256
257/** a plain failure, for the common case of a message and a cause */
258#define HAIO_FAIL(why, message) \
259 return std::unexpected(::Haio::Error{::Haio::ErrorCode::why, message})
260
261/** the enums are the loops: a new enumerator grows every table on its own */
262#define HAIO_FOR_EACH_FORMAT(e) \
263 template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^Haio::Format)))
264
265#define HAIO_FOR_EACH_COLOR(e) \
266 template for (constexpr auto e : std::define_static_array(std::meta::enumerators_of(^^Haio::Color)))
267
268/** the enumerator is PNG, the name people type is png */
269consteval std::string_view lowerOf(std::meta::info enumerator) {
270 std::string out{std::meta::identifier_of(enumerator)};
271 for (auto& c : out) {
272 if (c >= 'A' && c <= 'Z') c = static_cast<char>(c - 'A' + 'a');
273 }
274 return std::define_static_string(out);
275}
276
277}
Result< void > Move(Bytes src, std::span< uint8_t > dst, Size size)=delete
Result< Image< To > > Convert(Image< From >)=delete
Result< Image< P > > Decode(const Blob &)=delete
bool Detect(Bytes)=delete
Result< Blob > Encode(Image< P >)=delete
Definition haio.hpp:11
ErrorCode code
std::string message
std::vector< uint8_t > data
std::expected< T, Error > Result
consteval std::string_view lowerOf(std::meta::info enumerator)
constexpr size_t strideOf(Color color)
std::string contentType
constexpr int alphaOffsetOf(Color color)
std::span< const uint8_t > Bytes
std::string path
Format format
std::vector< uint32_t > entries
static constexpr Color color
std::vector< uint8_t > data
static constexpr Color color
std::vector< uint8_t > data