Nodachisoft Nodachi Sword Icon
  
@あまじ✎ 2021年3月30日に更新

第3章5 エスケープシーケンスで文字の色、背景の色を変更

イチからゲーム作りで覚えるC言語
第3章4 文字の色、背景の色を変更 : PREV
NEXT : 第3章6 リアルタイムなキーボード入力制御 :

概要

Windows 上のコンソールで文字色や背景色を変更するために、 Windows API で直接色などの制御をするのではなく、 C言語の printf 関数などを通じた文字列の出力で、エスケープシーケンスを使用する方法を 確認していきます!

エスケープシーケンスを使うことで、文字を表示する位置や色を文字列で制御できるようになります。

メリット

Windows API の SetConsoleTextAttribute 関数を使えば、現在の文字の色を変更できますが、 エスケープシーケンスを使うことで、より良いメリットがあります。

  • 画面出力バッファにエスケープシーケンスをため込んで一度に出力できる。SetConsoleTextAttribute 関数などを使っていると画面出力のバッファにカラフルな文字をため込んで一度に出力!という処理ができないので、アニメーションするカラフルな画面のように動きが激しいものだとチラつきが多くなってしまう。
  • Windows だけでなく、Linux 系や MacOS などにも移植しやすい。エスケープシーケンスは様々な OS のコンソール上で対応していることが多い。

シンプルな色の変更例

Windows10 のコンソール画面(cmd.exe)上では、 エスケープシーケンスの機能が標準では OFF となっています。

これを ON にするためには、まず Windows API の ConsoleMode 関数 を呼び出して機能を ON にしてあげます。

エスケープシーケンスを使った簡単な例をみてみましょう!

consolemode_ex1.c
#include <stdio.h>
#include <windows.h>

int main() {
    HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD consoleMode = 0;
    GetConsoleMode(stdOut, &consoleMode);
    consoleMode = consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
    SetConsoleMode(stdOut, consoleMode);
    // 色を赤色に変更
    printf("\x1B[31;1mここは赤色!\x1B[37;m");
    printf("通常の白色");
}

これを実行すると下のような結果になります。

エスケープシーケンスでの色付け実行結果

ちゃんと「ここは赤色!」という文字が赤い色になってますね。

ConsoleMode 関数

Windows 上でコンソールアプリを作成するとき、 出力する先のコンソールはコンソールモードを持っています。

このコンソールモードを変更することで文字の出力についてのいくつかの基本的動作の制御ができます。

コンソールモードで代表的なものとして、以下のようなフラグを持っています

モード 定数 概要 デフォルト
ENABLEPROCESSEDOUTPUT 0x0001 ASCII制御シーケンスを有効にする。例:「\n」なら改行する、など 有効
ENABLEWRAPATEOLOUTPUT 0x0002 行の最後まで文字を表示した時、次の行の頭に移動する 有効
ENABLEVIRTUALTERMINAL_PROCESSING 0x0004 VT100ターミナル等の制御コードを有効にする 無効
DISABLENEWLINEAUTO_RETURN 0x0008 "\n" での改行時に行頭への移動("\r")は行わない 無効

デフォルト値については、MSVC(Microsoft Visual Community)2019 での例です。

エスケープシーケンスは ENABLEVIRTUALTERMINAL_PROCESSING のフラグを ON にすることで利用できるようになります。

コード確認

main 関数から確認していきます。

consolemode_ex1.c
int main() {
    HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD consoleMode = 0;
    GetConsoleMode(stdOut, &consoleMode);

5 行目の中で、Windows API の GetStdHandle 関数を使って、標準出力先(コンソール画面)への ハンドルを取得しています。 ここで取得したコンソール画面へのハンドルに対して、エスケープシーケンス機能をONにします。

6 行目で DWORD 型の変数 consoleMode を宣言・初期化しています。 この変数 consoleMode に現在のコンソールモードの値を取得します。

まずは現在のコンソールモードのフラグを取得します。

consolemode_ex1.c
    GetConsoleMode(stdOut, &consoleMode);

この時点で consoleMode には数値の 3 が入っています。 3 は 1 + 2 ですので、ENABLEPROCESSEDOUTPUT と ENABLEWRAPATEOLOUTPUT のフラグが有効ということです。

続いて、このフラグに対して、エスケープシーケンス機能を使うための ENABLEVIRTUALTERMINALPROCESSING フラグを有効にしますので、 OR 演算で ENABLEVIRTUALTERMINALPROCESSING を有効にします。

consolemode_ex1.c
    consoleMode = consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;

この consoleMode を現在のコンソールに設定します。

consolemode_ex1.c
    SetConsoleMode(stdOut, consoleMode);

これで色を変更するなどのエスケープシーケンスが使用できるようになりました。

次の行で実際に色を変更してみます。

consolemode_ex1.c
    // 色を赤色に変更
    printf("\x1B[31;1mここは赤色!\x1B[37;m");

この文字列の中に「文字の色を赤に変更」、「文字の色を白に戻す」というエスケープシーケンス が含まれています。まずは「文字の色を赤に変更」する箇所から確認していきましょう。

printf に渡す文字列の先頭に、文字列が含まれています。 これがエスケープシーケンスです。

\x1B[31;1m

この文字列がコンソールに出力される時に、コンソール側の機能で出力する文字が赤色に変更されます。 以降、printf や puts 関数などで出力する文字は赤色に変更されます。

続いて、\x1B[37;m という文字列がきています。

エスケープシーケンスの文法

エスケープシーケンスの文法について確認しておきます。

エスケープシーケンスの開始

エスケープシーケンスは「\x1b」で始まります。最初の ”\x” は、16進数で後の 2 文字を扱うという意味となります。 つまり、「\x1b」は、1バイトで、16進数の 1b という意味です。1b は 10 進数に直すと、数字の 27 です。

ASCII コード表を参照してみると、 27 は特殊コードのエスケープです。

つまり、エスケープシーケンスは ASCII コードでいう、エスケープ(27 = '\x1b')から始まってます。

エスケープシーケンスの制御シーケンス

どのような機能を実行したいかをエスケープ('\x1b')の後に続いて書いていきます。 どのような機能を実行したいかの指示を制御シーケンス(Control Sequence)と呼びます。

制御シーケンスは通常、"[" の記号でスタートします。

たとえば赤色に文字を変更したい!という制御シーケンスの場合、

\x1B[31;1m

という記載ですが、この「赤色に変えたい!」という命令は「31;1m」という部分で指示しています。

制御シーケンスの内側ですが、下のような記載となっています。

カンマ(";")区切りのパラメータ + 実行したい命令

具体的に、赤色に変えたい!という命令は下のような制御シーケンスです。

  • 「31;1」の「31」はパラメータの一つ。 31 はカラーパレットの 1 番目を表しており、31 は赤色を表す。
  • 「31;1」の「1」はパラメータの一つ。 1 は太字、もしくは明るめの色指定の意味。明るめの赤色になる。
  • 「m」が色を変更する命令
制御シーケンス
リセット 0m
30m
31m
32m
33m
34m
マゼンダ 35m
シアン 36m
37m

上記の8種類(30~37)は予めコンソールの基本色としてパレットという情報で決まっています。 パレットの色は変更もできますが、8色のみを扱うのは使いづらいため、 RGB を直接指定して変更する方法を確認していきます。

RGBをエスケープシーケンスで指定する

RGB の色合いを直接指定して、色を変更する方法を サンプルコードで確認していきましょう!

consolemode_ex2.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>

// Virtual Terminal機能を有効にする
void enableVT() {
    HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD consoleMode = 0;
    GetConsoleMode(stdOut, &consoleMode);
    SetConsoleMode(stdOut, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}

// エスケープシーケンスを標準出力する
void escSeq(char const *controlSequence, char const function ) {
    printf("\x1B[");
    printf(controlSequence);
    putchar(function);
}

// RGB を指定して文字の色を変更する
void setFontColorRGB( const unsigned char r
                     ,const unsigned char g
                     ,const unsigned char b ) {
    char buf[64];
    sprintf(buf, "38;2;%d;%d;%d", r, g, b);
    escSeq(buf, 'm');
}

// 色情報をリセットする
void resetColor() {
    escSeq("0", 'm');
}

int main() {
    enableVT();
    for (int r = 0; r < 255; r += 8) {
        for (int g = 0 ; g < 255; g += 8 * (r+1)) {
            for (int b = 0 ; b < 255; b += 8 * (g+1)) {
                setFontColorRGB(r, g, b);
                printf("■");
            }
        }
    }
}

実行すると下のような結果となります。

実行結果2

様々な色の ■ が表示されました!

今回作成した関数を確認していきます。

consolemode_ex2.c
// Virtual Terminal機能を有効にする
void enableVT() {
    HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD consoleMode = 0;
    GetConsoleMode(stdOut, &consoleMode);
    SetConsoleMode(stdOut, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}

5行目からの enableVT 関数は、呼び出すと エスケープシーケンスを実行できるようになります。 これをプログラムの最初に呼び出せば、様々な色を表示できるようになります。

続いて、エスケープシーケンスを標準出力するための escSeq 関数です。

consolemode_ex2.c
// エスケープシーケンスを標準出力する
void escSeq(char const *controlSequence, char const function ) {
    printf("\x1B[");
    printf(controlSequence);
    putchar(function);
}

escSeq( 制御シーケンス文字列, 制御シーケンスの命令文 ); という形で呼び出して使います。

続いて、表示する文字の色を RGB 形式(赤色、緑色、青色)で指定できる関数を確認します。

consolemode_ex2.c
// RGB を指定して文字の色を変更する
void setFontColorRGB( const unsigned char r
                     ,const unsigned char g
                     ,const unsigned char b ) {
    char buf[64];
    sprintf(buf, "38;2;%d;%d;%d", r, g, b);
    escSeq(buf, 'm');
}

この setFontColorRGB は下のように呼び出します。

setFontColorRGB( 赤, 緑, 青);

赤(red)、緑(green)、青(blue)は 0~255 の間で色の強さを指定します。 例えば、setFontColorRGB(0,0,0) と呼び出しをすると、表示する文字は RGB(0,0,0) つまり黒色となります。

RGB を指定するエスケープコードを一つの printf で書くと下のようになります。

 
rgbの指定
printf("\x1B[38;2;100;77;111m色が変わりました。");

この例では、R=100, G=77, B=111 を指定しています。

続いて、色の変更などをすべてリセットする関数を確認します。

consolemode_ex2.c
// 色情報をリセットする
void resetColor() {
    escSeq("0", 'm');
}

この resetColor を呼び出すと、変更した色などのフォント設定や背景色の設定が リセットされます。

最後に main 関数の中です。

consolemode_ex2.c
int main() {
    enableVT();
    for (int r = 0; r < 255; r += 8) {
        for (int g = 0 ; g < 255; g += 8 * (r+1)) {
            for (int b = 0 ; b < 255; b += 8 * (g+1)) {
                setFontColorRGB(r, g, b);
                printf("■");
            }
        }
    }
}

35行目で enableVT 関数を呼び出し、エスケープシーケンスを実行できるように コンソールの設定を変更しています。

36~43 行で、setFontColorRGB の R,G,B のそれぞれの値を 調整しながらループで呼び出ししています。 これによリカラフルな結果が出力されました。

その他、画面制御でよく使用するエスケープシーケンス

エスケープシーケンスは画面の文字色を変更するだけではなく、 文字を出力する位置や、カーソル(画面上で点滅している、文字の出力位置)の表示を変更することが出来ます。

代表的なものを下に記載します。

制御シーケンス例 効果
nA カーソルを n 文字ぶん上に移動
nB カーソルを n 文字ぶん下に移動
nC カーソルを n 文字ぶん右に移動
nD カーソルを n 文字ぶん左に移動
nE カーソルを n 個ぶん下の最も左に移動
n;mH カーソルを (m,n) の位置に移動
1J コンソールに表示されている内容を消去する
48;2;r;g;b 背景色を r,g,b に変更する。
rgb はそれぞれ 0~255
?25h カーソルの点滅を表示
?25l カーソルの点滅を非表示
4m 文字にアンダーラインを描画する

使いやすく関数化

ご参考に、コンソールアプリやコンソールゲームで、上記の色変更、カーソル移動の機能を使いやすくするために、 関数化しておきます。

 
escape_sequence.c
// Virtual Terminal機能を有効にする
void enableVT() {
    HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    DWORD consoleMode = 0;
    GetConsoleMode(stdOut, &consoleMode);
    SetConsoleMode(stdOut, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}

// エスケープシーケンスを標準出力する
void escSeq(char const *controlSequence, char const function ) {
    printf("\x1B[");
    printf(controlSequence);
    putchar(function);
}

// RGB を指定して文字の色を変更する
void setFontColorRGB( const unsigned char r
                     ,const unsigned char g
                     ,const unsigned char b ) {
    char buf[64];
    sprintf(buf, "38;2;%d;%d;%d", r, g, b);
    escSeq(buf, 'm');
}

void resetColor() {  // 色情報をリセット
    escSeq("0", 'm');
}

void setFontUnderline() {  // アンダーライン
    escSeq("4", 'm');
}

void moveCursorUp(const unsigned char movecount) { // 上に移動
    char buf[4];
    sprintf(buf, "%d", movecount);
    escSeq(buf, 'A');
}

void moveCursorDown(const unsigned char movecount) { // 下に移動
    char buf[4];
    sprintf(buf, "%d", movecount);
    escSeq(buf, 'B');
}

void moveCursorLeft(const unsigned char movecount) { // 左に移動
    char buf[4];
    sprintf(buf, "%d", movecount);
    escSeq(buf, 'D');
}

void moveCursorRight(const unsigned char movecount) { // 右に移動
    char buf[4];
    sprintf(buf, "%d", movecount);
    escSeq(buf, 'C');
}

void setCursorPosition( const unsigned char x
                       ,const unsigned char y ) { // 指定位置に移動
    char buf[4];
    sprintf(buf, "%d;%d", y, x);
    escSeq(buf, 'H');
}

void setCursorReturn() {  // 改行同様に移動
    escSeq("1", 'E');
}

void clearScreen() {  // 画面表示をクリア
    escSeq("1", 'J');
}

void showCursor() {   // カーソル位置の点滅を表示
    escSeq("?25", 'h');
}

void hideCursor() {   // カーソル位置の点滅を非表示
    escSeq("?25", 'l');
}

補足(VT100)

今回の使用したエスケープシーケンスは、元々DEC社(ディジタル・イクイップメント・コーポレーション)で 開発されたカラフルな文字表示ができる画面表示用の機器である VT100 で使われていた仕様です。

非常に使い勝手が良かったため、この VT100 のエスケープシーケンス機能を使った、エミュレータが Windows や Linux 上のコンソール上でも使えるようになっています。

参考

-docs.microsoft.com - GetConsoleMode function

イチからゲーム作りで覚えるC言語
第3章4 文字の色、背景の色を変更 : PREV
NEXT : 第3章6 リアルタイムなキーボード入力制御 :
 
 
送信しました!

コメント、ありがとうございます。

なんかエラーでした

ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。

Twitter:@NodachiSoft_jp
お名前:
 
連絡先:
 
メッセージ:
 
戻る
内容の確認!

以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください

お名前:
 
連絡先:
 
メッセージ:
 
Roboto からの操作ではないという確認のため確認キーを入れてください。
確認キー=95
戻る
 / 
送信確認へ
コメント欄
コメント送信確認へ

関連ありそうな記事(5件)です!

第1章01 Visual Studio Community 2019 のインストール手順

#C11仕様#C言語#ゲームプログラミング✎ 2021-08-08
C言語でプログラミングをするために、無料で使える Visual Studio Community を使った開発環境を揃えていく手順や注意点をお話しています。
目次
第3章5 エスケープシーケンスで文字の色、背景の色を変更
第3章5 エスケープシーケンスで文字の色、背景の色を変更
概要
概要
メリット
メリット
シンプルな色の変更例
シンプルな色の変更例
ConsoleMode 関数
ConsoleMode 関数
コード確認
コード確認
エスケープシーケンスの文法
エスケープシーケンスの文法
エスケープシーケンスの開始
エスケープシーケンスの開始
エスケープシーケンスの制御シーケンス
エスケープシーケンスの制御シーケンス
RGBをエスケープシーケンスで指定する
RGBをエスケープシーケンスで指定する
その他、画面制御でよく使用するエスケープシーケンス
その他、画面制御でよく使用するエスケープシーケンス
使いやすく関数化
使いやすく関数化
補足(VT100)
補足(VT100)
参考
参考
Nodachisoft © 2021