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

jgovgodb  于 2023-11-19  发布在  其他
关注(0)|答案(1)|浏览(144)

我是一个对 C++ 非常感兴趣的学生,目前我正在做一个小项目,涉及在Windows Terminal上打印大量彩色ASCII字符,这是旧cmd.exe的优化版本。我的项目包括通过将视频帧转换为ASCII图像并按顺序打印来创建小的“ASCII视频”(请参阅

video link)。我目前使用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帧的有效方法是什么?

m4pnthwp

m4pnthwp1#

不久前,我写了一些代码,直接向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)。

相关问题