A header/source C++23 library that renders images and graphics in a terminal using
24-bit truecolor ANSI escape sequences. Pairs of pixels are packed into the
lower-half block character ▄ (U+2584) so each output cell shows two vertical
pixels at once.
- Pure C++23, no external dependencies
- 24-bit truecolor output (
\033[38;2;r;g;bm/\033[48;2;...m) Image— pixel buffer with bounds-checked access and terminal renderingCanvas— drawing primitives: pixel, line, rect, circle, image blit, and 5×3 bitmap digits (draw_uint)- Alpha blending through
overlay() - HSL color helper, grayscale helper
- Terminal capability detection:
supports_truecolor()— actively probes the terminal with an OSC 11 queryenable_vt100()— probes with a Device Attributes query and enables VT100 application mode
- Error handling with
std::expected(C++23)
- C++23 compiler (GCC 13+, Clang 16+)
- EazyMake for building (optional — you can compile the sources with any build system)
POSIX required for terminal support — the
terminal.hfunctions (supports_truecolor(),enable_vt100(),terminal_size()) andImage::draw()rely on POSIX facilities (termios,poll,ioctl,isatty). These are available on Linux/macOS and in MSYS2 (MinGW-w64), but not in a native MSVC build. The rest of the library (color, image, canvas) is portable C++.On Windows, run the binaries under Windows Terminal or mintty so the terminal answers ANSI queries (the library does not call
SetConsoleMode(ENABLE_VIRTUAL_TERMINAL_PROCESSING)).
ezmk build # produces build/libterm_pic.a
ezmk test # build and run testsOr compile manually:
g++ -std=c++23 -O2 -Iinclude -c src/*.cpp
ar rcs libterm_pic.a *.o#include "term_pic.h"
#include <cstdint>
int main() {
if (!term_pic::supports_truecolor()) {
return 1;
}
term_pic::enable_vt100();
term_pic::Canvas canvas({ 64, 64 }, term_pic::gray(30));
for (size_t y = 0; y < 64; y += 8) {
term_pic::RgbColor c = term_pic::hsl(360.0f * y / 64.0f, 1.0f, 0.5f);
canvas.draw_rect({8, y}, {55, y + 6}, c, 1.0f, true);
}
canvas.draw_circle({32, 32}, 12, term_pic::gray(255), 0.5f, false);
canvas.draw_uint({26, 27}, 42, term_pic::gray(255), 1.0f);
canvas.draw();
}All types live in namespace term_pic. The umbrella header term_pic.h (compiled
with -Iinclude) includes everything; individual headers such as
term_pic/canvas.h are also available.
struct Pos { size_t x, y; };
struct Size { size_t w, h; };| Declaration | Description |
|---|---|
struct RgbColor { uint8_t r, g, b; } |
RGB color value |
RgbColor gray(uint8_t g) |
Gray color |
RgbColor overlay(const RgbColor &raw, const RgbColor &mask, float alpha = 0.5) |
Alpha blend raw over mask |
RgbColor hsl(float h, float s, float l) |
HSL → RGB (h in degrees) |
std::string gen_seq(const std::optional<RgbColor> &fg, const std::optional<RgbColor> &bg) |
Build a truecolor ANSI SGR sequence |
enum class Error : size_t { OutOfRange = 0, NotTerminal = 1 };
template<typename T> using Res = std::expected<T, Error>;
using Err = std::unexpected<Error>;
std::string to_string(Error e) noexcept;| Member | Description |
|---|---|
Image(const Size &size, const RgbColor &bg = {255,255,255}) |
Create buffer |
Res<RgbColor> get(const Pos &) const |
Bounds-checked read |
Res<void> set(const Pos &, const RgbColor &) |
Bounds-checked write |
void resize(size_t w, size_t h, const std::optional<RgbColor> &bg = std::nullopt) |
Resize |
void draw(std::ostream &os = std::cout) const |
Render to stream |
void clear() |
Reset to background |
Size size() const |
Width, height |
Canvas wraps an Image and adds clipping to the drawing primitives below.
Every draw call takes float alpha = 1.0f for blending and is noexcept.
| Member | Description |
|---|---|
Canvas(const Size &size, const RgbColor &bg) |
Create canvas |
Image &image_ref() |
Access the underlying image |
void clear() |
Reset to background |
void draw(std::ostream &os = std::cout) const |
Render |
RgbColor get_bg() const / void set_bg(const RgbColor &) |
Background access |
void draw_pixel(Pos, const RgbColor &, float alpha = 1) |
Single pixel |
void draw_line(Pos start, Pos end, const RgbColor &, float alpha = 1) |
Bresenham line |
void draw_rect(Pos start, Pos end, const RgbColor &, float alpha = 1, bool fill = true) |
Rectangle |
void draw_circle(Pos center, float radius, const RgbColor &, float alpha = 1, bool fill = true) |
Circle |
void draw_image(Pos offset, const Image &, float alpha = 1) |
Blit an image |
void draw_uint(Pos pos, unsigned int num, const RgbColor &, float alpha = 1) |
Render a decimal number with the 5×3 bitmap font |
void fill(const RgbColor &, float alpha = 1) |
Fill entire canvas |
| Member | Description |
|---|---|
bool supports_truecolor() noexcept |
Sends an OSC 11 query (\033]11;?\033\\) and returns true if the terminal answers with an rgb: value. Requires a TTY on stdin/stdout. |
bool enable_vt100() noexcept |
Sends a Device Attributes query (\033[0c), and if the terminal confirms (\033[?…c), emits the VT100 application-mode sequence (\033[?1h) and returns true. |
Res<Size> terminal_size() noexcept |
Returns the terminal size in character cells (Size{w, h}) via ioctl(TIOCGWINSZ). Returns Err(Error::NotTerminal) when stdin/stdout is not a TTY or the query fails. |
Both probes temporarily switch stdin to raw mode (termios) and read the
response with a poll() timeout, restoring the original settings afterwards.
POSIX — available on Linux/macOS and MSYS2 (MinGW-w64); not available in a native MSVC build.
ezmk testtest/a.cpp— renders a 64×64 gradienttest/terminal_test.cpp— terminal detection smoke tests