Convenient, high-performance RGB color and position control for console output

Overview

Oof (omnipotent output friend)

It's common for C++ programs to write output to the console. But consoles are far more capable than what they are usually used for. The magic lies in the so-called Virtual Terminal sequences (sometimes also confusingly called "escape codes"): These cryptic character sequences allow complete control over position, color and other properties of written characters. Oof is a single C++20 header that wraps these in a convenient way.

for (int i = 0; i < 10; ++i){
   std::cout << oof::fg_color(oof::color{255 - i * 25});
   std::cout << oof::position(i, 2 * i) << std::to_string(i);
}

123_example

constexpr double values[]{0.54, 0.88, 0.42, 0.21, 0.33, 0.68, 0.91};
for(int i=0; i<std::size(values); ++i){
   std::cout << oof::underline(true) << std::format("value {}", i) << oof::reset_formatting() << ": ";
   const oof::color color{ 255, static_cast<int>(255 - values[i] * 255), 0 };
   std::cout << oof::fg_color(color) << std::format("{:.2f}\n", values[i]) << oof::reset_formatting();
}

values_example

On top of that, oof provides two special interfaces that heavily optimize the resulting stream of VT sequences, so that real-time outputs like those below are possible. The following videos are showcases - everything in them are letters in a console window:

radar_demo.mp4
snow_demo.mp4
cursor_demo.mp4
fireworks_demo.mp4

Usage

To use the library, include oof.h. As with most header-only libraries, that include must be preceeded with #define OOF_IMPL in one cpp file. That way, the function implementations are only compiled once.

The simple interface consists of the functions below.

// Sets the foreground RGB color
auto fg_color(const color& col) -> fg_rgb_color_sequence;

// Sets the background RGB color
auto bg_color(const color& col) -> bg_rgb_color_sequence;

// Sets the foreground indexed color. Index must be in [1, 255]
auto fg_color(int index) -> fg_index_color_sequence;

// Sets the background indexed color. Index must be in [1, 255]
auto bg_color(int index) -> bg_index_color_sequence;

// Sets the indexed color. Index must be in [1, 255]
auto set_index_color(int index, const color& col) -> set_index_color_sequence;

// Sets the underline state
auto underline(bool new_value = true) -> underline_sequence;

// Sets the bold state. Warning: Bold is not supported by all console, see readme
auto bold(bool new_value = true) -> bold_sequence;

// Sets cursor visibility state. Recommended to turn off before doing real-time displays
auto cursor_visibility(bool new_value) -> cursor_visibility_sequence;

// Resets foreground- and background color, underline and bold state
auto reset_formatting() -> reset_sequence;

// Clears the screen
auto clear_screen() -> clear_screen_sequence;

// Sets the cursor position. Zero-based ie 0, 0 is first line, first column
auto position(int line, int column) -> position_sequence;
auto vposition(int line) -> vposition_sequence;
auto hposition(int column) -> hposition_sequence;

// Moves the cursor a certain amount
auto move_left (int amount) -> move_left_sequence;
auto move_right(int amount) -> move_right_sequence;
auto move_up   (int amount) -> move_up_sequence;
auto move_down (int amount) -> move_down_sequence;

Index colors are simply colors referred to by an index. The colors behind the indices can be set with set_index_color().

All these functions return a magic type that can operator<< into std::cout and std::wcout. Example:

std::cout << oof::fg_color({ 255, 100, 100 }) << "This is red\n";
std::cout << "Still the same - state was changed!\n";
std::cout << oof::reset_formatting() << oof::hposition(10) << "All back to normal\n";

example

They also implicitly convert into std::string and std::wstring so you can build up your own strings with them.

The type oof::color is mostly just a struct color { uint8_t red{}, green{}, blue{}; }. It does have convenience constructors for integer component parameters that get automatically static_casted into uint8_t. And it can be constructed with a single value, which will result in a grayscale color. You're encouraged to std::bit_cast, reinterpret_cast or memcpy your favorite 3-byte RGB color type into this.

Performance and screen interfaces

Each printing command (regardless of wether it's printf, std::cout or something OS-specific) is pretty expensive. If performance is a priority, then consider building up your string first, and printing it in one go.

If you want real-time output, ie continuously changing what's on the screen, there's even more potential: By keeping track of the current screen state, oof avoids writing to cells that haven't changed. And: Changing the console cursor state (even without printing anything) is expensive. Avoiding unnecessary state changes is key. Both of these optimizations are implemented in the screen and pixel_screen classes.

With oof::screen you define a rectangle in your console window and set the state of every single cell. Its get_string() and write_string(string_type&) methods then output an optimized string to achieve the desired state. This assumes that the user didn't interfere - so don't. The difference between get_string() and write_string(string_type&) is that the passed string will be reused to avoid allocating a new string. Almost always, the time to build up the string is tiny vs the time it takes to print, so don't worry about this too much.

Example for oof::screen usage:

oof::screen scr(10, 3, 0, 0, ' ');
for(uint64_t i=0; ; ++i){
   int j = 0;
   for (auto& cell : scr) {
      cell.m_letter = 'A' + (j + i) % 26;
      cell.m_format.m_bg_color.red = j * 8;
      ++j;
   }
   std::cout << scr.get_string();
}

screen_example

The API in general is pretty low level compared to other libraries, focused on high performance and modularity. You're encouraged to use it to build your own components. A good example for this is the horizontal bars demo:

bars_demo

Consoles always write text, ie letters. With most fonts, a single letter or cell is much taller than wide. By using a very special character that exactly fills the upper half of a cell, the visible area gets effectively transformed into (almost) square pixels. Exactly that's done by the pixel_screen class. There you only set colors and give up control of the letters themselves. Note that that type often has halfline type parameters. That's due to the fact that a "pixel" is now just half a line high.

oof::pixel_screen

Example for oof::pixel_screen usage:

oof::pixel_screen screen(10, 10);
const auto t0 = std::chrono::high_resolution_clock::now();
while(true){
   const auto t1 = std::chrono::high_resolution_clock::now();
   const double seconds = std::chrono::duration<double>(t1-t0).count();

   for (oof::color& pixel : screen) {
      pixel.red   = 127.5 + 127.5 * std::sin(1.0 * seconds);
      pixel.green = 127.5 + 127.5 * std::sin(2.0 * seconds);
      pixel.blue  = 127.5 + 127.5 * std::sin(3.0 * seconds);
   }
   fast_print(screen.get_string());
}

pixel_screen_example

The source code from the demo videos at the beginning is in this repo under demos/. That code uses a not-included and yet unreleased helper library (s9w::) for colors and math. But those aren't crucial if you just want to have a look.

Notes

Consoles display text. Text is displayed via fonts. If you use letters that aren't included in your console font, that will result in visual artifacts - duh. This especially important for the pixel_display type, as it uses the mildly special Block element '▀'. Some fonts may not have them included. Others do, but have them poorly aligned or sized - breaking up the even pixel grid.

This is a short overview of common monospace fonts and how well they are suited for "pixel" displays. Note that many are great in some sizes but ugly in others.

Font name
Great fonts Fira Mono, Anka/Coder, Cascadia Code, Courier New, Hack, Lucida Console, Source Code pro, Pragmata Pro, Consolas
Mediocre Inconsolata, Iosevka
Broken or awful Input mono, Menlo, Office Code pro

Bold

The bold sequence is special. Some consoles ignore it entirely, some implement is as actual bold and others implement it as "bright" - slightly altering the colors of affected letters. Check yours before you use it. cmd.exe interprets it as bright. The Windows Terminal however correctly implements it as bold since v1.11, if you set "intenseTextStyle": "bold". See this article.

Errors

You can provide an error function that gets called when something goes wrong. This mostly catches obvious usage errors (negative sizes, out of bounds indices etc), so feel free to ignore this. If you want, this is how:

auto my_error_function(const std::string& msg) -> void
{
   std::cerr << std::format("ERROR! msg: {}\n", msg);
   std::terminate();
}

oof::error_callback = my_error_function;

Performance & OS-Specific details

Under windows, printf and std::cout are very slow. They're completely fine to use for static outputs but they're unsuitable real-time displays. It's much faster to use Windows own WriteConsole() directly. A ready-to use wrapper that works for std::string and std::wstring would be:

template<typename char_type>
auto fast_print(const std::basic_string<char_type>& sss) -> void
{
   HANDLE const output_handle = GetStdHandle(STD_OUTPUT_HANDLE);
   const auto char_count = static_cast<DWORD>(sss.length());
   if constexpr (std::is_same_v<char_type, char>)
      WriteConsoleA(output_handle, sss.c_str(), char_count, nullptr, nullptr);
   else
      WriteConsoleW(output_handle, sss.c_str(), char_count, nullptr, nullptr);
}

If oof doesn't produce the output expected, it may be that the VT mode is not enabled. To enable it, this function can be used on windows:

auto enable_vt_mode() -> void
{
   HANDLE const handle = GetStdHandle(STD_OUTPUT_HANDLE);
   if (handle == INVALID_HANDLE_VALUE)
   {
      std::terminate(); // error handling
   }

   DWORD dwMode = 0;
   if (!GetConsoleMode(handle, &dwMode))
   {
      std::terminate(); // error handling
   }

   if (dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING)
   {
      // VT mode is already enabled
      return;
   }

   dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
   if (!SetConsoleMode(handle, dwMode))
   {
      std::terminate(); // error handling
   }
}
  • If you use pixel_screen or screen<std::wstring> in combination with std::wcout, you might not see the output. That's because unicode output might need some magic to enable. Either google that, or use the recommended fast_print above as it's faster and doesn't suffer from these problems.
  • While the VT sequences are universal, not all consoles programs and operating systems may support them. I only have access to a windows machine so I can't make any claims on other operating systems.
  • The new Windows Terminal has some problems with irregular frame pacing. It will report high FPS but "feel" much choppier than good old cmd.exe.
You might also like...
PUBG ESP Hack for Emulator using C++ code. Player Position, Bones, Loots, Weapons, Vehicles, Boxes ... etc.
PUBG ESP Hack for Emulator using C++ code. Player Position, Bones, Loots, Weapons, Vehicles, Boxes ... etc.

PUBG 1.7 ESP Hack for Emulator (C++ Source Code) PUBG ESP Hack for Emulator using C++ code. Player Position, Bones, Loots, Weapons, Vehicles, Boxes ..

Scroll pos - Provides some additional functions to ScrollController to define item position relative to the screen.
Scroll pos - Provides some additional functions to ScrollController to define item position relative to the screen.

Scroll Position Provides some additional functions to ScrollController to define item position relative to the screen. A live version is available her

A CUDA-accelerated cloth simulation engine based on Extended Position Based Dynamics (XPBD).
A CUDA-accelerated cloth simulation engine based on Extended Position Based Dynamics (XPBD).

Velvet Velvet is a CUDA-accelerated cloth simulation engine based on Extended Position Based Dynamics (XPBD). Why another cloth simulator? There are a

Control Heidelberg Wallbox Energy Control over WiFi using ESP8266 and configure your own local load management
Control Heidelberg Wallbox Energy Control over WiFi using ESP8266 and configure your own local load management

scroll down for English version and additional information wbec WLAN-Anbindung der Heidelberg WallBox Energy Control über ESP8266 Die Heidelberg W

Azure Outlook Command & Control (C2) - Remotely control a compromised Windows Device from your Outlook mailbox. Threat Emulation Tool for North Korean APT InkySquid / ScarCruft / APT37. TTP: Use Microsoft Graph API for C2 Operations.
Budgie Control Center is a fork of GNOME Control Center for the Budgie 10 Series.

Budgie Control Center Budgie Control Center is a fork of GNOME Settings / GNOME Control Center with the intent of providing a simplified list of setti

Small and convenient C2 tool for Windows targets
Small and convenient C2 tool for Windows targets

Micro Backdoor for Windows Micro Backdoor is C2 tool for Windows targets with easy customizable code base and small footprint. Micro Backdoor consists

Memory Process File System (MemProcFS) is an easy and convenient way of viewing physical memory as files in a virtual file system
Memory Process File System (MemProcFS) is an easy and convenient way of viewing physical memory as files in a virtual file system

The Memory Process File System (MemProcFS) is an easy and convenient way of viewing physical memory as files in a virtual file system.

Just a repository convenient for reviewing and retrieval practice.

The-art-of-multiprocessor-programming Purpose Just a repository convenient for reviewing and retrieval practice. The architecture of this repository(H

Comments
  • Query sequences

    Query sequences

    There is a special VT sequence that "will emit their responses into the console input stream immediately after being recognized on the output stream". In particular this is about ESC [ 6 n - the "report cursor position" sequence. To utilize this, it's required to read input from the console, without the user pressing enter. Unfortunately, that is not part of the C++ standard.

    Therefore I'd like to explore if it's possible to write this code for the three (?) major platforms. I've written the code below, which works for windows. If other devs could join in to make something happen for Linux/Mac, that would be awesome.

    Some relevant SO threads:

    • https://stackoverflow.com/questions/421860/capture-characters-from-standard-input-without-waiting-for-enter-to-be-pressed
    • https://stackoverflow.com/questions/9547868/is-there-a-way-to-get-user-input-without-pressing-the-enter-key
    #ifdef _WIN32
    #include <conio.h>
    #define WIN32_LEAN_AND_MEAN
    #define NOMINMAX
    #include <windows.h>
    #endif
    
    #include <tuple>
    #include <iostream>
    #include <string>
    
    #ifdef _WIN32
    inline auto enable_windows_vt_mode() -> void{
       HANDLE const handle = GetStdHandle(STD_OUTPUT_HANDLE);
       DWORD dwMode = 0;
       GetConsoleMode(handle, &dwMode);
       dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
       SetConsoleMode(handle, dwMode);
    }
    #endif
    
    auto immediately_read_one_char() -> char {
    #ifdef _WIN32
       return static_cast<char>(_getch());
    #endif
    }
    
    auto get_pos() -> std::tuple<int, int> {
       const std::string cursor_position_sequence = "\x1b[6n";
       std::cout << cursor_position_sequence;
       // This will "emit their responses into the console input stream immediately
       // after being recognized on the output stream".
       // So that input will need to be read without a return press.
       // Example response: "\x1b[11;22R"
    
       std::string input{};
       do{
          input.resize(input.size() + 1);
          input.back() = immediately_read_one_char();
       } while (input.back() != 'R');
       const size_t sep_pos = input.find_first_of(';');
       constexpr size_t row_substr_start = 2; // After the ESC and [
       const std::string row_str = input.substr(row_substr_start, sep_pos - row_substr_start);
       const std::string column_str = input.substr(sep_pos+1, input.size() - sep_pos - 2);
    
       return {
          std::stoi(row_str) - 1,
          std::stoi(column_str) - 1
       };
    }
    
    int main() {
    #ifdef _WIN32
       enable_windows_vt_mode();
    #endif
       
       std::cout << "test123\ntest";
       const auto pos = get_pos();
    
       return 0;
    }
    
    opened by s9w 0
  • Added cmake file and partial testing support.

    Added cmake file and partial testing support.

    Hi,

    I love the idea of your library and since I would love adding it as a vendor I would like to suggest adding a CMake file. I also attempted to add doctest, but was unable to due to some private libraries which I couldn't find in your public repos.

    Sincerely, FrozenSource

    opened by FrozenSource 7
  • Can't build demos due to missing s9w_geom_types.h file.

    Can't build demos due to missing s9w_geom_types.h file.

    ~/Development/oof/demos master g++-11 main.cpp 
    In file included from main.cpp:3:
    tools.h:10:10: fatal error: s9w/s9w_geom_types.h: No such file or directory
       10 | #include <s9w/s9w_geom_types.h>
          |          ^~~~~~~~~~~~~~~~~~~~~~
    compilation terminated.
    
    opened by azawadzki 8
Owner
Sebastian Werhausen
Sebastian Werhausen
Library with useful output stream tools like: color and style manipulators, progress bars and terminal graphics.

Library with useful output stream tools like: color and style manipulators, progress bars and terminal graphics Table of contents Introduction Documen

Gianluca Bianco 168 Dec 20, 2022
Console game in which you are required to say the color of the word, not what the word says.

ENG УКР (ENG)Stroop - Train reaction and play at one time Desctiption Stroop - is a console game in which the user has to say color of the word, not t

Yura Shymon 2 Sep 22, 2022
Read file to console, automatically recognize file encoding, include ansi, utf16le, utf16be, utf8. Currently output ansi as gbk for chinese text search.

rgpre A tool for rg --pre. Read file to console, automatically recognize file encoding, include ansi, utf16le, utf16be, utf8. Currently output ansi as

null 3 Mar 18, 2022
A wireless control for an RGB LED based on emotions found in discord messages.

Sample project (See the README.md file in the upper level 'examples' directory for more information about examples.) This is the simplest buildable ex

Fernando Mendoza 2 Dec 1, 2021
An eventing framework for building high performance and high scalability systems in C.

NOTE: THIS PROJECT HAS BEEN DEPRECATED AND IS NO LONGER ACTIVELY MAINTAINED As of 2019-03-08, this project will no longer be maintained and will be ar

Meta Archive 1.7k Dec 14, 2022
Building and Executing Position Independent Shellcode from Object Files in Memory

PIC-Privileges Building and Executing Position Independent Shellcode from Object Files in Memory. This is a pingback to the blogpost I wrote at https:

Paranoid Ninja 108 Dec 26, 2022
imGuIZMO.quat is a ImGui widget: like a trackball it provides a way to rotate models, lights, or objects with mouse, and graphically visualize their position in space, also around any single axis (Shift/Ctrl/Alt/Super)

imGuIZMO.quat v3.0 imGuIZMO.quat is a ImGui widget: like a trackball it provides a way to rotate models, lights, or objects with mouse, and graphicall

Michele Morrone 276 Dec 28, 2022
Plot airfield from a file containing the list of airfield of Germany and their position (latitude/longitude)

Plot aerodromes from a file containing the list of aerodromes of Germany and their position (latitude/longitude)

Nanfack Steve Ulrich 2 Feb 6, 2022
Harsh Badwaik 1 Dec 19, 2021
I modified the colmap,when it reconstructs from known pose ,only let it optimize rotation ,fixing position!

Mapping-base-lidar-pose-or-vslam-pose I simply modified the colmap,when it reconstructs from known pose ,only let it optimize rotation ,fixing positio

李城(大橙子) 23 Dec 16, 2022