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/** U+2580, which fills the top half of the cell and leaves the bottom to the background */
10constexpr std::string_view halfBlock = "▀";
11
12void appendNumber(std::string& out, int value) {
13 out += std::to_string(value);
14}
15
16}
17
18namespace Haio::Codecs {
19
20/**
21 * two pixel rows per terminal row, using the upper half block.
22 *
23 * the foreground paints the top pixel and the background the bottom one, which buys
24 * twice the vertical resolution of the ansi rendering for the same number of lines
25 * and makes the aspect ratio come out roughly square.
26 *
27 * an odd number of rows leaves the last cell with nothing below it, and its
28 * background is left black rather than repeating the row above: a doubled last line
29 * reads as part of the picture, a dark one reads as the edge.
30 */
31template <>
32Result<Blob> Encode<Format::UTF8, Color::RGB888>(Image<Color::RGB888> img) {
33 const Size size{img.width, img.height};
34 HAIO_TRY(expected, sizeOf(Color::RGB888, size));
35 if (img.data.size() != expected) HAIO_FAIL(InvalidInput, "invalid rgb888 image for utf8 encode");
36
37 const auto width = static_cast<size_t>(img.width);
38 const auto height = static_cast<size_t>(img.height);
39
40 std::string out;
41 out.reserve(width * ((height + 1) / 2) * 40);
42
43 const auto colourAt = [&](size_t row, size_t x, bool present) -> uint32_t {
44 if (!present) return 0;
45 const auto at = (row * width + x) * 3;
46 return (static_cast<uint32_t>(img.data[at + 0]) << 16)
47 | (static_cast<uint32_t>(img.data[at + 1]) << 8)
48 | img.data[at + 2];
49 };
50
51 const auto appendColour = [&](std::string_view lead, uint32_t colour) {
52 out += lead;
53 appendNumber(out, static_cast<int>((colour >> 16) & 0xFF));
54 out += ';';
55 appendNumber(out, static_cast<int>((colour >> 8) & 0xFF));
56 out += ';';
57 appendNumber(out, static_cast<int>(colour & 0xFF));
58 };
59
60 for (size_t y = 0; y < height; y += 2) {
61 /**
62 * a terminal keeps the colour it was last told, so saying it again is bytes
63 * nobody reads. a picture in few colours repeats itself constantly, and this
64 * is exactly the picture somebody renders in a terminal.
65 *
66 * the pair is tracked rather than each half, because a cell sets both at once.
67 */
68 uint32_t lastTop = 0;
69 uint32_t lastBottom = 0;
70 bool anySoFar = false;
71
72 for (size_t x = 0; x < width; x++) {
73 const auto top = colourAt(y, x, true);
74 const auto bottom = colourAt(y + 1, x, y + 1 < height);
75
76 if (!anySoFar || top != lastTop || bottom != lastBottom) {
77 appendColour("\x1b[38;2;", top);
78 appendColour(";48;2;", bottom);
79 out += 'm';
80 lastTop = top;
81 lastBottom = bottom;
82 anySoFar = true;
83 }
84 out += halfBlock;
85 }
86 // the reset ends the row, so the next one starts with nothing assumed
87 out += "\x1b[0m\n";
88 }
89
90 return Blob{Format::UTF8, Color::RGB888, "text/x-ansi-halfblock", {},
91 std::vector<uint8_t>(out.begin(), out.end())};
92}
93
94}
#define HAIO_TRY(name, expr)
#define HAIO_FAIL(why, message)
Result< Blob > Encode< Format::UTF8, Color::RGB888 >(Image< Color::RGB888 > img)
Definition encode.cpp:32
std::expected< T, Error > Result
Result< size_t > sizeOf(Color color, Size size)
Definition sizes.cpp:6