Haio 0.2.X
Homebrew Asset IO/CDN
Loading...
Searching...
No Matches
encode.cpp
Go to the documentation of this file.
1#include <haio_codec.hpp>
2#include <haio_codecs.hpp>
3#include <haio_convert.hpp>
4
5#include <string>
6
7namespace {
8
9/** a cell is two spaces because a terminal cell is about twice as tall as it is wide */
10constexpr std::string_view cell = " ";
11
12void appendNumber(std::string& out, int value) {
13 out += std::to_string(value);
14}
15
16}
17
18namespace Haio::Codecs {
19
20/**
21 * one terminal cell per pixel, coloured by its background.
22 *
23 * the picture ends up half the height of the utf8 one for the same number of rows,
24 * and it works on anything that can colour a background, including terminals with no
25 * font to draw a half block with.
26 *
27 * @todo a terminal that cannot do 24 bit colour renders this as noise. detecting that
28 * means reading COLORTERM, and falling back means quantising to the 256 colour cube.
29 */
30template <>
31Result<Blob> Encode<Format::ANSI, Color::RGB888>(Image<Color::RGB888> img) {
32 const Size size{img.width, img.height};
33 HAIO_TRY(expected, sizeOf(Color::RGB888, size));
34 if (img.data.size() != expected) HAIO_FAIL(InvalidInput, "invalid rgb888 image for ansi encode");
35
36 std::string out;
37 // every cell carries an escape sequence of its own, so the text is far larger
38 // than the picture: reserving up from the start saves growing it a hundred times
39 out.reserve(static_cast<size_t>(img.width) * static_cast<size_t>(img.height) * 20);
40
41 for (int y = 0; y < img.height; y++) {
42 // a terminal keeps the colour it was last told, and a run of one colour is
43 // the common case in the pictures anybody renders here
44 uint32_t last = 0;
45 bool anySoFar = false;
46
47 for (int x = 0; x < img.width; x++) {
48 const auto at = (static_cast<size_t>(y) * static_cast<size_t>(img.width) + static_cast<size_t>(x)) * 3;
49 const uint32_t colour = (static_cast<uint32_t>(img.data[at + 0]) << 16)
50 | (static_cast<uint32_t>(img.data[at + 1]) << 8)
51 | img.data[at + 2];
52
53 if (!anySoFar || colour != last) {
54 out += "\x1b[48;2;";
55 appendNumber(out, img.data[at + 0]);
56 out += ';';
57 appendNumber(out, img.data[at + 1]);
58 out += ';';
59 appendNumber(out, img.data[at + 2]);
60 out += 'm';
61 last = colour;
62 anySoFar = true;
63 }
64 out += cell;
65 }
66 // reset at the end of every row, so a terminal that wraps does not paint the
67 // rest of the line in whatever colour the last pixel was
68 out += "\x1b[0m\n";
69 }
70
71 return Blob{Format::ANSI, Color::RGB888, "text/x-ansi", {}, std::vector<uint8_t>(out.begin(), out.end())};
72}
73
74}
#define HAIO_TRY(name, expr)
#define HAIO_FAIL(why, message)
Result< Blob > Encode< Format::ANSI, Color::RGB888 >(Image< Color::RGB888 > img)
Definition encode.cpp:31
std::expected< T, Error > Result
Result< size_t > sizeOf(Color color, Size size)
Definition sizes.cpp:6