
constixel is a single header minimalistic constexpr C++20 palette based 2D graphics rendering library with the ability to output to a sixel image stream and png images which can be viewed in a modern terminal like Windows Terminal.
Table of Contents
Primary features and goals
Applications
Requirements
Minimal example
Text drawing example
Consteval sixel example
Consteval image example
Saving a PNG to disk example
API
Primary features and goals
- Completely constexpr. All graphics rendering, including generating the sixel output stream can happen during compilation.
- No dynamic allocations. The backbuffer and the very few internal data structures can live as global static variables.
- Minimalistic interface and single header implementation.
- 1, 2, 4 and 8bit palette based back buffers for minimal memory usage. Reasonable standard palettes are provided. 24bit and 32bit back buffers are also provided if the target is something else than sixel.
- Simple fill_rect(), fill_round_rect(), draw_line() and fill_circle() drawing functions among others.
- Render proportional text, optionally with kerning, using fonts genenerated by a custom version of fontbm. Repository includes a set of pre-made (open source) fonts which are trivial to use. UTF-8 is supported.
- A uncompressed png encoder is included to reduce dependencies.
- Blit raw 32-bit RGBA image buffers into the palette based back buffer (with or without dithering). Also convert back into a RGBA buffer when needed.
- Code is cpplint compliant, has a .clang-tidy profile, passes cppcheck and is of course consteval runnable.
- Code compiles with "-Wall -Wextra -Wpedantic -Weffc++ -Werror" on so it can be easily used in any existing C++ project without creating noise.
- Resistance to unbound behavior (i.e. potential for DoS) when passing unreasonable values.
- Various other simple image manipulation operations.
- Note
- This library is not designed for high fidelity graphics generation and should be more thought of a utility library for software development purposes. Despite that, on HiDPI screens like on Macs the results generally look fairly good.
Applications
- Interface rendering on embedded devices. Perfect to target monochrome or grayscale OLED screens or just to save memory when rendering to full RGB OLED screens.
- Send graphical data to your terminal through a serial or ssh connections from embedded or remote devices.
- Add graphical output to unit tests.
- Programmatically render static graphical assets.
- Help debug dynamic memory handling issues in complex C++ projects.
- Add text overlays to RGBA8 buffers.
- ...
Requirements
- C++20, i.e. gcc 13.3 or newer, clang 16 or newer, MSVC 17 or newer
- For viewing the sixel image you will need a sixel capable terminal. Windows Terminal, iTerm2 on MacOS and some Linux terminals will work. In Visual Studio Code sixel rendering can be enabled for all terminals using the
"terminal.integrated.enableImages": true
setting (Seems to be broken on Windows as of 4/2025, but works on MacOS and Linux). There is also an option to output to a kitty graphics enabled terminal.
- Note
- The Terminal app on MacOS does not support sixel, please use iTerm2.
-
If you want to consteval either the sixel or image data you likely need to increase the constexpr ops limit. With g++ use '-fconstexpr-ops-limit=268435456' with clang use '-fconstexpr-steps=33554432'. The default limit in MSVC usually seems adequate.
Usage
Option 1: Simply copy the header file to your project and include it. Optionally copy the fonts you want from the fonts directory.
Option 2: If you prefer you can add this git repo as a cmake library:
_package(constixel REQUIRED)
...
add_subdirectory(constixel)
...
target_link_libraries([your target name] PRIVATE constixel::constixel)
...
After that you can simply do this:
#include "constixel/constixel.hpp"
#include "constixel/fonts/[whatever font you want].hpp"
Don't forget to enable C++20/23:
set(CMAKE_CXX_STANDARD 20)
Minimal example
#include "constixel.hpp"
int main() {
for (int32_t y = 0; y < 16; y++) {
for (int32_t x = 0; x < 16; x++) {
image.
fill_rect({.x = x * 16, .y = y * 16, .w = 16, .h = 16,
.col = static_cast<uint8_t>(y * 16 + x)});
}
}
}
Core class of constixel. Holds a buffer of an image width a certain size and format....
Definition constixel.hpp:3502
void sixel_to_cout() const
Convert the current instance into a sixel stream and output it to std::cout.
Definition constixel.hpp:5131
constexpr void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col)
Fill a rectangle with the specified color. Example:
Definition constixel.hpp:4302

Text drawing example
Include a text font and pass the struct name as a template parameter to the draw_string function. The pre-made available fonts in this repo are viewable here. More can be easily added/modified, for instance if you need more than just ASCII character ranges or want symbols/icons.
#include "constixel.hpp"
#include "fonts/ibmplexsans_medium_48_mono.hpp"
using large_font = constixel::ibmplexsans_medium_48_mono;
int main() {
using namespace constixel;
static constexpr std::array<const char *, 5> strings {
"ABCDEFGHIJKLM", "NOPQRTSUVWXYZ", "abcdefghijklm", "nopqrstuvwxyz","1234567890&@.,?!'"""
};
for (size_t i = 0; i < strings.size(); i++) {
uint8_t col = color::GRAY_RAMP_STOP - static_cast<uint8_t>(i * 3);
.str = strings.at(i), .col = col});
}
}
constexpr int32_t draw_string_mono(int32_t x, int32_t y, const char *str, uint8_t col, size_t character_count=std::numeric_limits< size_t >::max(), size_t *character_actual=nullptr)
Draw text at the specified coordinate. The template parameter selects which mono font to use....
Definition constixel.hpp:4053

Consteval sixel example
As std::vector can not escape consteval (yet) so we use std::array. Output of this example should be "Actual byte size: 18537" and the sixel image. The binary will contain the evaluated sixel string.
Compile as such (clang needs -fconstexpr-steps=33554432):
> g++ -fconstexpr-ops-limit=268435456 -std=c++23 constixel.cpp -o constixel -Os
#include "constixel.hpp"
#include <cstring>
consteval auto gen_sixel() {
for (int32_t y = 0; y < 16; y++) {
for (int32_t x = 0; x < 16; x++) {
image.
fill_rect(x * 16, y * 16, 16, 16,
static_cast<uint8_t
>(y * 16 + x));
}
}
std::array<char, 32767> sixel{};
char *ptr = sixel.data();
*ptr++ = ch;
});
*ptr++ = '\n';
*ptr++ = 0;
return sixel;
}
int main() {
static const auto sixel = gen_sixel();
std::cout << "Actual byte size: " << strlen(sixel.data()) << "\n";
std::cout << sixel.data() << std::endl;
}
constexpr void sixel(F &&char_out) const
Convert the current instance into a sixel stream. Typically an implementation looks like this:
Definition constixel.hpp:5102
Consteval embedded image data example
This example will consteval gen_image_1bit() into a std::array, while dynamically generating the sixel string.
#include "constixel.hpp"
#include <cstring>
consteval auto gen_image_1bit() {
for (int32_t y = 0; y < 16; y++) {
for (int32_t x = 0; x < 16; x++) {
image.
fill_rect(x * 16, y * 16, 16, 16,
static_cast<uint8_t
>(y + x) & 1);
}
}
}
int main() {
static const auto image = gen_image_1bit();
printf(
"image instance byte size: %d\n",
int(
image.
size()));
size_t count = 0;
putc(ch, stdout);
count++;
});
putc('\n', stdout);
printf("sixel byte size: %d\n", int(count));
}
static constexpr int32_t width()
Width in pixels of the image.
Definition constixel.hpp:3572
static constexpr size_t size()
Size in bytes of the image data. This does not include the image instance size.
Definition constixel.hpp:3556
static constexpr int32_t height()
Height in pixels of the image.
Definition constixel.hpp:3580

Saving a PNG to disk example
#include "constixel.hpp"
#include <iostream>
#include <fstream>
#include <filesystem>
int main() {
for (int32_t y = 0; y < 16; y++) {
for (int32_t x = 0; x < 16; x++) {
image.
fill_rect(x * 16, y * 16, 16, 16,
static_cast<uint8_t
>(y * 16 + x));
}
}
std::vector<char> out{};
out.push_back(ch);
});
std::ofstream file("constixel.png", std::ios::binary);
file.write(reinterpret_cast<const char*>(out.data()), static_cast<std::streamsize>(out.size()));
}
constexpr void png(F &&char_out) const
Convert the current instance into a png image. Typically an implementation looks like this:
Definition constixel.hpp:5084
API
Full doxygen generated API docs are at constixel.dev
The following image formats are available. [Width] is the width in pixels, [Height] is the height in pixels. [Grayscale] will set the palette to a grayscale palette with black being the first entry in the palette and white being the last entry in the palette. [UseSpan] allows to pass in an existing std::span/std::array to be used as a backbuffer if so desired.
A quick overview of some functionality of image, refer to the full documention here:
enum color : uint8_t {
BLACK = 0,
BLACK_OPAQUE = 16,
WHITE = 1,
RED = 2,
GREEN = 3,
BLUE = 4,
YELLOW = 5,
CYAN = 6,
MAGENTA = 7,
GRAY_80 = 8,
GRAY_60 = 9,
GRAY_40 = 10,
GRAY_20 = 11,
DARK_RED = 12,
DARK_GREEN = 13,
DARK_BLUE = 14,
DARK_YELLOW = 15
};
int32_t size();
int32_t width();
int32_t height();
std::array<uint8_t, T<W, H, S>::image_size> &data_ref();
void clear();
template <typename FONT, bool KERNING = false, ROTATION = DEGREE_0>
constexpr int32_t draw_string_mono(int32_t x, int32_t y, const char *str, uint8_t col);
template <typename FONT, bool KERNING = false, ROTATION = DEGREE_0>
constexpr int32_t draw_string_aa(int32_t x, int32_t y, const char *str, uint8_t col);
template <typename FONT, bool KERNING = false>
constexpr int32_t string_width(const char *str);
void draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col, uint32_t stroke_width = 1);
void draw_line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col);
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col);
void stroke_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col, int32_t stroke_width = 1);
void fill_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col);
void stroke_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col, int32_t stroke_width = 1);
void fill_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col);
void stroke_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col, int32_t stroke_width = 1);
void fill_circle(int32_t x, int32_t y, int32_t radius, uint8_t col);
void fill_circle_aa(int32_t x, int32_t y, int32_t radius, uint8_t col);
uint8_t get_nearest_color(uint8_t r, uint8_t g, uint8_t b);
std::array<uint32_t, W * H> RGBA_uint32();
std::array<uint8_t, W * H * 4> RGBA_uint8();
void flip_h();
void flip_v();
void flip_hv();
template <bool FLIP_H = false, bool FLIP_V = false>
template <bool FLIP_H = false, bool FLIP_V = false>
void blit_RGBA(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride);
void blit_RGBA(
const rect<int32_t> &r,
const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride);
void blit_RGBA_diffused(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride);
void blit_RGBA_diffused(
const rect<int32_t> &r,
const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride);
void blit_RGBA_diffused_linear(int32_t x, int32_t y, int32_t w, int32_t h, const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride);
void blit_RGBA_diffused_linear(
const rect<int32_t> &r,
const uint8_t *ptr, int32_t iw, int32_t ih, int32_t stride);
void png(F &&charOut);
template<SCALE_FACTOR = 1>
void sixel(F &&charOut);
template<SCALE_FACTOR = 1>
template<SCALE_FACTOR = 1>
void sixel_to_cout();
void png_to_iterm();
void png_to_kitty();
void vt100_clear();
void vt100_home();
enum device_format {
STRAIGHT_THROUGH,
RGB565_8BIT_SERIAL,
RGB666_8BIT_SERIAL_1,
RGB666_8BIT_SERIAL_2
};
bool convert_chunk<device_format>(char *dst, size_t chunk_size, size_t &chunk_actual, size_t &chunk_index);
}
constexpr void convert(F &&uint8_out)
Convert the current instance into a byte stream formatted for embedded displays.
Definition constixel.hpp:5203
Definition constixel.hpp:2878
basic rectangle structure
Definition constixel.hpp:616