提问人:Matt Moissat 提问时间:11/17/2023 更新时间:11/17/2023 访问量:94
如何将彩色 ASCII 字符直接写入控制台的缓冲区?
How can I write coloured ASCII characters directly to the console's buffer?
问:
我是一个对C++语言非常感兴趣的学生,我目前正在做一个小项目,涉及在Windows终端上打印大量彩色ASCII字符,这是旧版本的优化版本。我的项目包括通过将视频中的帧转换为 ASCII 图像并按顺序打印它们来创建小型“ASCII 视频”(请参阅或视频链接)。我目前正在使用 ANSI 转义序列,例如手动设置每个 ASCII 字符所需的颜色,如下所示:cmd.exe
\033[38;2;⟨r⟩;⟨g⟩;⟨b⟩m
string char_ramp("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\" ^ `'. ");
int brightness(some_number);
(...)
current_frame += string{} + "\033[38;2;" + to_string(r) + ";" + to_string(g) + ";" + to_string(b) + "m" + char_ramp[(int)(greyscale_index / 255 * (char_ramp.size() - 1)) / brightness] + "\033[0m";
基本上,我通过在灰度字符渐带上添加与当前像素亮度级别相对应的 ASCII 字符来逐行构建帧,然后使用 ANSI 转义序列对其进行着色。
但是这样做似乎对终端的要求很高,因为当我打印彩色 ASCII 帧时,我会出现掉帧。例如,如果原始视频为 30 fps,则彩色 ASCII 视频将下降到大约 15 fps。
我尝试去除颜色,它似乎工作正常,没有掉帧。
一些朋友建议我应该直接设置缓冲区数据,而不是做打印行的所有额外逻辑。但是,我不知道如何在不改变构造 ASCII 帧的方式的情况下做到这一点,因为我确实需要以特定方式存储它们,或者至少以允许我控制输出速率的方式存储它们。我听说该函数允许我做一些事情,但我不明白如何在我的代码中实现它。WriteConsoleOutputCharacter
我在这里并不真正关心内存效率,所以难道没有办法将帧数据设置到缓冲区中,然后循环访问我想要显示的帧吗?
如果这只是一个错误的调用,那么在终端上显示这些 ascii 帧的有效方法是什么?
答:
前段时间,我编写了一些代码,将流式输出直接输出到 Windows 控制台,并具有颜色支持。
// WinBuf.hpp:
#pragma once
#include <ios>
#include <ostream>
#include <windows.h>
//! A C++ streambuf that writes directly to a Windows console
class WinBuf : public std::streambuf
{
HANDLE h;
public:
//! Create a WinBuf from an Windows handle
//! @param h handle to a Windows console
WinBuf(HANDLE h) : h(h) {}
WinBuf(WinBuf const &) = delete;
WinBuf &operator=(WinBuf const &) = delete;
//! Return the handle to which this buffer is attached
HANDLE handle() const { return h; }
protected:
virtual int_type overflow(int_type c) override
{
if (c != EOF)
{
DWORD written;
WriteConsole(h, &c, 1, &written, nullptr);
}
return c;
}
virtual std::streamsize xsputn(char_type const *s, std::streamsize count) override
{
DWORD written;
WriteConsole(h, s, DWORD(count), &written, nullptr);
return written;
}
};
//! A C++ ostream that writes via the preceding WinBuf
class WinStream : public virtual std::ostream
{
WinBuf buf;
public:
//! Create stream for a Windows console, defaulting to standard output
WinStream(HANDLE dest = GetStdHandle(STD_OUTPUT_HANDLE))
: buf(dest), std::ostream(&buf)
{
}
//! return a pointer to the underlying WinBuf
WinBuf *rdbuf() { return &buf; }
};
//! Provide the ability to set attributes (text colors)
class SetAttr
{
WORD attr;
public:
//! Save user's attribute for when this SetAttr object is written out
SetAttr(WORD attr) : attr(attr) {}
//! Support writing the SetAttr object to a WinStream
//! @param w a WinStream object to write to
//! @param c An attribute to set
friend WinStream &operator<<(WinStream &w, SetAttr const &c)
{
WinBuf *buf = w.rdbuf();
auto h = buf->handle();
SetConsoleTextAttribute(h, c.attr);
return w;
}
//! support combining attributes
//! @param r the attribute to combine with this one
SetAttr operator|(SetAttr const &r)
{
return SetAttr(attr | r.attr);
}
};
//! Support setting the position for succeeding output
class gotoxy
{
COORD coord;
public:
//! Save position for when object is written to stream
gotoxy(SHORT x, SHORT y) : coord{ .X = x, .Y = y} {}
//! support writing gotoxy object to stream
friend WinStream &operator<<(WinStream &w, gotoxy const &pos)
{
WinBuf *buf = w.rdbuf();
auto h = buf->handle();
SetConsoleCursorPosition(h, pos.coord);
return w;
}
};
//! Clear the "screen"
class cls
{
char ch;
public:
//! Create screen clearing object
//! @param ch character to use to fill screen
cls(char ch = ' ') : ch(ch) {}
//! Support writing to a stream
//! @param os the WinStream to write to
//! @param c the cls object to write
friend WinStream &operator<<(WinStream &os, cls const &c)
{
COORD tl = {0, 0};
CONSOLE_SCREEN_BUFFER_INFO s;
WinBuf *w = os.rdbuf();
HANDLE console = w->handle();
GetConsoleScreenBufferInfo(console, &s);
DWORD written, cells = s.dwSize.X * s.dwSize.Y;
FillConsoleOutputCharacter(console, c.ch, cells, tl, &written);
FillConsoleOutputAttribute(console, s.wAttributes, cells, tl, &written);
SetConsoleCursorPosition(console, tl);
return os;
}
};
//! Provide some convenience instances of the SetAttr object
//! to (marginally) ease setting colors.
extern SetAttr red;
extern SetAttr green;
extern SetAttr blue;
extern SetAttr intense;
extern SetAttr red_back;
extern SetAttr blue_back;
extern SetAttr green_back;
extern SetAttr intense_back;
属性在匹配的 .cpp 文件中定义:
// Winbuf.cpp:
#include "WinBuf.hpp"
SetAttr red{FOREGROUND_RED};
SetAttr green{FOREGROUND_GREEN};
SetAttr blue{FOREGROUND_BLUE};
SetAttr intense{FOREGROUND_INTENSITY};
SetAttr red_back{BACKGROUND_RED};
SetAttr green_back{BACKGROUND_GREEN};
SetAttr blue_back{BACKGROUND_BLUE};
SetAttr intense_back{BACKGROUND_INTENSITY};
下面是一个快速演示/测试程序,以展示它的使用方式:
// test_Winbuf.cpp
#include "winbuf.hpp"
int main()
{
WinStream w;
// the colors OR together, so red | green | blue gives white:
auto color = red | green | blue | blue_back;
w << color << cls() << gotoxy(10, 4) << "This is a string\n";
for (int i = 0; i < 10; i++)
w << "Line: " << i << "\n";
w << (green | blue_back);
for (int i = 0; i < 10; i++)
w << "Line: " << i + 10 << "\n";
w << gotoxy(20, 10) << "Stuck in the middle with you!\n";
w << gotoxy(0, 30) << color << "The end\n";
}
根据我的经验,这比使用 ANSI 转义序列要快得多(而且,尽管它的价值很小,但也适用于旧版本的 Windows)。
评论
auto
评论
WriteConsoleOutput