如何将彩色 ASCII 字符直接写入控制台的缓冲区?

问题描述 投票:0回答:1

我是一名对 C++ 语言非常感兴趣的学生,目前正在开发一个小项目,涉及在 Windows Terminal 上打印大量彩色 ASCII 字符,这是旧版

cmd.exe
的优化版本。我的项目包括通过将视频帧转换为 ASCII 图像并按顺序打印它们来创建小型“ASCII 视频”(请参阅 视频链接)。我目前正在使用 ANSI 转义序列(如
\033[38;2;⟨r⟩;⟨g⟩;⟨b⟩m
)来手动设置每个 ASCII 字符所需的颜色,如下所示:

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 帧的有效方法是什么?

c++ performance colors io buffer
1个回答
0
投票

前一段时间,我编写了一些代码,直接向 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;

    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)。

© www.soinside.com 2019 - 2024. All rights reserved.