constixel
Loading...
Searching...
No Matches
constixel.hpp

CMake on multiple platforms CodeQL Advanced

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)});
}
}
image.sixel_to_cout();
}
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

constixel

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);
image.draw_string_mono<large_font>({.x = 16, .y = 48 * static_cast<int32_t>(i) + 16,
.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

constixel

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();
image.sixel([&ptr](char ch) mutable {
*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);
}
}
return image;
}
int main() {
static const auto image = gen_image_1bit();
printf("image width x height: %d %d x 1bit depth\n", int(image.width()), int(image.height()));
printf("image instance byte size: %d\n", int(image.size()));
size_t count = 0;
image.sixel([&count](char ch) mutable {
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

constixel

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{};
image.png([&out](char ch) mutable {
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:

// Colors in the fixed internal palette
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
};
class image {
// Size in bytes of the image buffer
int32_t size();
// Width in pixel of the image buffer
int32_t width();
// Height in pixels of the image buffer
int32_t height();
// Return a reference to the internal pixel buffer
std::array<uint8_t, T<W, H, S>::image_size> &data_ref();
// Return a clone of this image, data will be copied.
// Clear the image, i.e. set everything to color 0
void clear();
// Copy another image into this instance. Overwrites the contents, no compositing occurs.
void copy(const image<T, W, H, S, GR> &src);
// Draw monochrome utf8 text. #include a monochrome font and specify the included struct as the template parameter.
// Rotation can be 0, 90, 180 or 270 degrees
// Returns the current x caret position in pixels.
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);
// Draw smoothed utf8 text. #include a antialiased font and specify the included struct as the template parameter.
// Rotation can be 0, 90, 180 or 270 degrees
// Returns the current x caret position in pixels.
// NOTE: This only works with constixel::format_8bit/constixel::format_32bit images
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);
// Get the width of a utf8 string in pixels. #include a monochrome or antialiased font and specify the included struct as the template parameter
template <typename FONT, bool KERNING = false>
constexpr int32_t string_width(const char *str);
// Draw a line
void draw_line(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col, uint32_t stroke_width = 1);
// Draw an 1-pixel wide antialiased line with the specified color and thickness.
// NOTE: This only works with constixel::format_8bit/constixel::format_32bit images
void draw_line_aa(int32_t x0, int32_t y0, int32_t x1, int32_t y1, uint8_t col);
// Draw a filled rectangle
void fill_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col);
// Draw a stroked rectangle
void stroke_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint8_t col, int32_t stroke_width = 1);
// Draw a rounded rectangle
void fill_round_rect(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col);
// Draw a stroked rounded rectangle
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);
// Draw a rounded rectangle with antialiasing
// NOTE: This only works with constixel::format_8bit/constixel::format_32bit images
void fill_round_rect_aa(int32_t x, int32_t y, int32_t w, int32_t h, int32_t radius, uint8_t col);
// Draw a stroked rounded rectangle
// NOTE: This only works with constixel::format_8bit/constixel::format_32bit images
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);
// Draw a filled circle
void fill_circle(int32_t x, int32_t y, int32_t radius, uint8_t col);
// Draw a filled circle with antialiasing
// NOTE: This only works with constixel::format_8bit images
void fill_circle_aa(int32_t x, int32_t y, int32_t radius, uint8_t col);
// Get closest matching color in the palette for given rgb values
uint8_t get_nearest_color(uint8_t r, uint8_t g, uint8_t b);
// Get a populated RGBA buffer with the contents of this instance.
// Color 0 is special and will be set to 0x0000000 in the returned buffer,
// while all the other colors will be converted to a 0xffBBGGRR format.
std::array<uint32_t, W * H> RGBA_uint32();
std::array<uint8_t, W * H * 4> RGBA_uint8();
// Flip the contents of this image horizontally
void flip_h();
// Flip the contents of this image vertically
void flip_v();
// Flip the contents of this image horizontally&vertically
void flip_hv();
// Return a transposed version of this image
template <bool FLIP_H = false, bool FLIP_V = false>
image<T, H, W, S, GR> transpose();
// transpose this image into another
template <bool FLIP_H = false, bool FLIP_V = false>
transpose(image<T, H, W, S, GR> &dst);
// Blit an RGBA (little endian) buffer into this instance. Colors are quantizied to the internal palette.
// NOTE: This is a slow operation due to the brute force color quantization and will likely not consteval.
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);
// Blit an RGBA (little endian) buffer into this instance using line diffusion for better quality.
// NOTE: This is a slow operation due to the brute force color quantization and will likely not consteval.
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);
// Blit an RGBA (little endian) buffer into this instance using line diffusion in linear color space for best quality.
// NOTE: This is a very slow operation due to the brute force color quantization and will likely not consteval.
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);
// Generate an uncompressed png file. Provide a lambda function in the form of:
//
// image.png([](char ch) {
// [do something with the character]
// });
//
void png(F &&charOut);
// Convert the current instance into a sixel stream. Provide a lambda function in the form of:
//
// image.sixel<[scale factor]>([](char ch) {
// [do something with the character]
// });
//
// Optionally you can provide a rectangle to get a portion of the image only.
//
template<SCALE_FACTOR = 1>
void sixel(F &&charOut);
template<SCALE_FACTOR = 1>
void sixel(F &&charOut, const constixel::rect<int32_t> &r);
// Convert the current instance into a sixel stream and output it to std::cout
template<SCALE_FACTOR = 1>
void sixel_to_cout();
// Convert the current instance into a png and display it in iTerm
void png_to_iterm();
// Convert the current instance into a png and display it in a terminal with kitty graphics support
void png_to_kitty();
// Send a escape command to std::cout to clear the screen of a vt100 compatible terminal.
void vt100_clear();
// Send a escape command to std::cout to home the cursor of a vt100 compatible terminal.
void vt100_home();
enum device_format {
STRAIGHT_THROUGH,
RGB565_8BIT_SERIAL,
RGB666_8BIT_SERIAL_1,
RGB666_8BIT_SERIAL_2
};
// Convert the current instance into a byte stream formatted for embedded displays.
void convert<device_format>(F &&char_out);
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