How to Print to the Console in Color in a Cross-Platform Manner

How can I print to the console in color in a cross-platform manner?

You can use the ANSI colour codes. Here's an example program:

#include <stdio.h>

int main(int argc, char *argv[])
{
printf("%c[1;31mHello, world!\n", 27); // red
printf("%c[1;32mHello, world!\n", 27); // green
printf("%c[1;33mHello, world!\n", 27); // yellow
printf("%c[1;34mHello, world!\n", 27); // blue
return 0;
}

The 27 is the escape character. You can use \e if you prefer.

There are lists of all the codes all over the web. Here is one.

Using C/C++, how do you print to stdout in color, but only if the terminal supports it?

Probably the easiest way to check is simply:

isatty(fileno(STDOUT))

This will return 1 if your standard output is being sent to any sort of terminal. In practice, any terminal will either support or ignore VT100 color codes; examining terminfo is unnecessary unless you expect to be outputting to certain really unusual hardware terminals. (Most of which haven't been made in decades.)

To output colors, use the (extended) SGR sequence:

"\x1b[%dm"

where %d is one of the following values for commonly supported colors:

0: reset colors/style
1: bold
4: underline
30 - 37: black, red, green, yellow, blue, magenta, cyan, and white text
40 - 47: black, red, green, yellow, blue, magenta, cyan, and white background

There are more values, but these are the most widely supported ones. Again, examining terminfo is largely unnecessary for these control codes, as every software terminal worth its salt will support (or ignore) them.

If you need to change multiple attributes at once, you can specify them all at once, separated by semicolons. For instance, the following sequence will sear your eyeballs with bold magenta text on a green background:

"\x1b[1;35;42m"

Color console in ANSI C?


just plain ANSI C?

No. The C standard doesn't assume the stdout is a console or has color.

Can this be done in Windows, Linux, or Mac OS X?

Yes. See How can I print to the console in color on Mac OS X in a cross-platform manner? for Linux and Mac OS X.

For Windows, you may need to directly access the Console Functions if you want to avoid external libraries.

Multithreaded console I/O

Wellp, I solved it using pdcurses. In case someone else wants to do something similar, here's how I did it. First, I initialize the console thusly:

Console::Console(bool makeConsole)
{
if (makeConsole == false)
return;

if (self)
throw ("You only need one console - do not make another!\n");
self = this;

#ifdef WIN32
AllocConsole();
#endif
initscr();

inputLine = newwin(1, COLS, LINES - 1, 0);
outputLines = newwin(LINES - 1, COLS, 0, 0);

if (has_colors())
{
start_color();
for (int i = 1; i <= COLOR_WHITE; ++i)
{
init_pair(i, i, COLOR_BLACK);
}
}
else
wprintw(outputLines, "Terminal cannot print colors.\n");

scrollok(outputLines, TRUE);
scrollok(inputLine, TRUE);

leaveok(inputLine, TRUE);
nodelay(inputLine, TRUE);
cbreak();
noecho();
keypad(inputLine, TRUE);

initCommands();

hello("Starting %s.\n", APP_NAME);
hellomore("Version %i.%i.%i.\n\n", APP_MAJORVER, APP_MINORVER, APP_REVISION);
}

Next, This is the function responsible for handling output. It's actually very simple, I don't need to do anything special to keep it thread-safe. I might simply not have encountered any issues with it, but an easy fix would be to slap a mutex on it.

void Console::sendFormattedMsg(short prefixColor, const char* prefix, short color, const char* format, ...)
{
if (!self)
return;

va_list args;
va_start(args, format);

if (has_colors())
{
if (prefix)
{
wattron(outputLines, A_BOLD | COLOR_PAIR(prefixColor));
wprintw(outputLines, prefix);
}

if (color == COLOR_WHITE)
wattroff(outputLines, A_BOLD);
wattron(outputLines, COLOR_PAIR(color));
vwprintw(outputLines, format, args);

wattroff(outputLines, A_BOLD | COLOR_PAIR(color));
}
else
{
wprintw(outputLines, prefix);
vwprintw(outputLines, format, args);
}

wrefresh(outputLines);
va_end(args);
}

And finally, input. This one required quite a bit of fine-tuning.

void Console::inputLoop(void)
{
static string input;

wattron(inputLine, A_BOLD | COLOR_PAIR(COLOR_WHITE));
wprintw(inputLine, "\n> ");
wattroff(inputLine, A_BOLD | COLOR_PAIR(COLOR_WHITE));

wprintw(inputLine, input.c_str());
wrefresh(inputLine);

char c = wgetch(inputLine);
if (c == ERR)
return;

switch (c)
{
case '\n':
if (input.size() > 0)
{
sendFormattedMsg(COLOR_WHITE, "> ", COLOR_WHITE, input.c_str());
cprint("\n");

executeCommand(&input[0]);
input.clear();
}
break;

case 8:
case 127:
if (input.size() > 0) input.pop_back();
break;

default:
input += c;
break;
}
}

This is run every frame from the same thread that handles window messages. I disabled wgetch()'s blocking behavior using nodelay(), eliminating the need to have console input running in it's own thread. I also disable echoing and echo the input manually. Enabling scrolling on the input window allows me to clear it's contents using a simple "\n", replacing it with updated contents if the user has typed anything. It supports everything one would expect from a simple, multi-threaded terminal capable to typing input as well as receiving output from multiple threads.

Getting the amount of available lines in a terminal

Determining by that screen shot, you are on Windows

This is from http://code.activestate.com/recipes/440694-determine-size-of-console-window-on-windows/

from ctypes import windll, create_string_buffer

# stdin handle is -10
# stdout handle is -11
# stderr handle is -12

h = windll.kernel32.GetStdHandle(-12)
csbi = create_string_buffer(22)
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)

if res:
import struct
(bufx, bufy, curx, cury, wattr,
left, top, right, bottom, maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
sizex = right - left + 1
sizey = bottom - top + 1
else:
sizex, sizey = 80, 25 # can't determine actual size - return default values

print sizex, sizey, curx, cury

That will give you screen size, and the cursor position.

cury is the line, so you can calculate the number of lines left.

However, you may want to re-check the console window size as you progress, as the user may resize the window at any time.

How to Output Unicode Strings on the Windows Console

The general strategy I/we use in most (cross platform) applications/projects is: We just use UTF-8 (I mean the real standard) everywhere. We use std::string as the container and we just interpret everything as UTF8. And we also handle all file IO this way, i.e. we expect UTF8 and save UTF8. In the case when we get a string from somewhere and we know that it is not UTF8, we will convert it to UTF8.

The most common case where we stumble upon WinUTF16 is for filenames. So for every filename handling, we will always convert the UTF8 string to WinUTF16. And also the other way if we search through a directory for files.

The console isn't really used in our Windows build (in the Windows build, all console output is wrapped into a file). As we have UTF8 everywhere, also our console output is UTF8 which is fine for most modern systems. And also the Windows console log file has its content in UTF8 and most text-editors on Windows can read that without problems.

If we would use the WinConsole more and if we would care a lot that all special chars are displayed correctly, we maybe would write some automatic pipe handler which we install in between fileno=0 and the real stdout which will use WriteConsoleW as you have suggested (if there is really no easier way).

If you wonder about how to realize such automatic pipe handler: We have implemented such thing already for all POSIX-like systems. The code probably doesn't work on Windows as it is but I think it should be possible to port it. Our current pipe handler is similar to what tee does. I.e. if you do a cout << "Hello" << endl, it will both be printed on stdout and in some log-file. Look at the code if you are interested how this is done.

Display an array of color in C

Graphics rendering:

I am used to win32 and Borland C++ environments, so I stick to it, but the differences on other environments are mostly only in class names. First some approaches:

  1. console/text modes

You can use text graphics (ASCII art I think in English). Where point is represented by character. Intensity is made by more or less filled chars. Usually have a table of characters sorted by intensity like " ..:+*#" and use that instead of colors. For printing out something, you can use iostream, like cout << "text" << endl; or printf from stdio I think (I am not been using old-style console output for more than a decade).

Text modes video RAM (VRAM) starts at 0B000:0000 if you have the privileges for it you can do direct access like this:

    char far *scr = (char far*)0x0B0000000;
scr[0] = 'A'; // Print A to left upper corner

But on Windows you can forget about direct access.

  1. VGA graphics mode

(DOS only, not Windows; this is doing direct direct access to the VGA hardware). Here is a small example:

 // Turbo C++ for 16-bit real mode DOS
//==============================================================================
char far* scr; // VGA screen
const _sx= 320; // Physical screen size
const _sy= 200;
//==============================================================================
void gfxinit();
void cls();
void pnt(int x,int y,char c);
//==============================================================================
void gfxinit()
{
asm { mov ax,19 // This switches VGA to 320*200*256 color mode (fits inside a single 64 KB segment so no funny stuff is needed)
int 16
}
for (int i=0;i<256;i++) asm { // This overwrites 256 color palette with some BW gradients
mov dx,0x3C8
mov ax,i
out dx,al // Overwrite color al = i
inc dx
shr al,2 // al=al>>2
out dx,al // r,g,b or b,g,r not sure now
out dx,al // All values are 6-bit long, therefore the shr al,2
out dx,al
}
scr=(char far*)0xA0000000; // VRAM start address
}
//==============================================================================
void cls() // This clears the screen with zeros
{
asm { push es
mov ax,0xA000
mov es,ax
mov di,0x0000
sub ax,ax
mov cx,32000
rep stosw
pop es
}
}
//==============================================================================
void pnt(int x,int y,char c) // This draws a single point of color c
{
unsigned int adr;
if (x<_sx)
if (x>=0)
if (y<_sy)
if (y>=0)
{
y=y*_sx;
adr=x+y;
scr[adr]=c;
}
}
//==============================================================================

VESA access is similar, but you have to deal with segment crossing and paging. Here is a small Turbo C++ example:

VESA.h

// Turbo C++, still 16-bit DOS, 
// but using VESA calls to set modes instead of VGA registers
//==============================================================================
//=== Globals: =================================================================
//==============================================================================
char far* scr=(char far*)0xA0000000; // VGA/VESA memory pointer
int VESA_page,VESA_pages; // Actual page and total pages
int VESA_xs,VESA_ys,VESA_bpp; // Video mode properties
int VESA_page_xy[64]={-1,-1}; // Starting x,y for each page
const int VESAmodes[]= // Usable video modes table
{
320, 200, 8,0x150,
640, 480, 8,0x101,
800, 600, 8,0x103,
1024, 768, 8,0x105,
1280,1024, 8,0x107,

320, 200,16,0x10E,
640, 480,16,0x111,
800, 600,16,0x114,
1024, 768,16,0x117,

320, 200,32,0x10F,
640, 480,32,0x112,
800, 600,32,0x115,

0,0,0,0
};
//==============================================================================
//=== Headers: =================================================================
//==============================================================================
int VESAmode(int xs,int ys,int bpp); // Set video mode
void VESApage(int page); // Set page
void VESAexit(); // Return to VGA text mode
void VESAcls(); // Clear with 0
void VESApnt(int x,int y,unsigned int c); // Render 8/16 bpp point
void VESApnt32(int x,int y,int r,int g ,int b); // render 32bpp point
//==============================================================================
//=== Graphic: =================================================================
//==============================================================================
int VESAmode(int xs,int ys,int bpp)
{
int i,mode,x,y;
unsigned int adr0,adr,dx,dy;
// find video mode
for (i=0;VESAmodes[i];i+=4)
if (VESAmodes[i+0]==xs)
if (VESAmodes[i+1]==ys)
if (VESAmodes[i+2]==bpp)
break;
if (!VESAmodes[i]) return 0;
mode=VESAmodes[i+3];
VESA_xs=xs;
VESA_ys=ys;
VESA_bpp=bpp;

// Compute start x,y for each page>0
dx=bpp>>3;
dy=xs*dx;
VESA_pages=1;
for (adr=i=x=y=0;y<VESA_ys;y++)
{
adr0=adr;
adr+=dy;
if (adr0>adr)
{
while (adr>0) { adr-=dx; x--; }
while (x<0) { x+=VESA_xs; y--; }
VESA_page_xy[i]=x; i++;
VESA_page_xy[i]=y+1; i++;
VESA_pages++;
}
}
VESA_page_xy[i]=-1; i++;
VESA_page_xy[i]=-1; i++;

// Set video mode
asm {
mov bx,mode
mov ax,0x4F02
int 16
}
VESApage(0);
/*
// Set palette to grayscale
if (VESAbpp==8)
for (int i=0;i<256;i++) asm {
mov dx,0x3C8
mov ax,i
out dx,al
inc dx
shr al,2
out dx,al
out dx,al
out dx,al
}
*/
return 1;
}
//==============================================================================
void VESApage(int page)
{
int p=page;
asm {
mov dx,p
mov bx,0
mov ax,0x4f05
int 16
}
VESA_page=page;
}
//==============================================================================
void VESAexit()
{
asm {
// Wait for key press
mov ax,0
int 0x16
// VGA 80x25 text mode
mov ax,3
int 16
}
}
//==============================================================================
void VESAcls()
{
int i;
for (i=0;i<VESA_pages;i++)
{
VESApage(i);
asm {
push es
mov ax,0xA000
mov es,ax
mov di,0x0000
mov ax,0
mov cx,32000
rep stosw
pop es
}
}
}
//==============================================================================
void VESApnt(int x,int y,unsigned int c)
{
unsigned int adr;
int p;
// inside screen?
if ((x>=0)&&(x<VESA_xs))
if ((y>=0)&&(y<VESA_ys))
{
// Low 16 bit of address
adr=y;
adr*=VESA_xs;
adr+=x;
adr*=(VESA_bpp>>3);
// Page
for (p=0;VESA_page_xy[p+p+0]>=0;p++)
{
if (VESA_page_xy[p+p+1]>y) break;
if (VESA_page_xy[p+p+1]<y) continue;
if (VESA_page_xy[p+p+0]>x) break;
}
if (p!=VESA_page) VESApage(p);
// Render
scr[adr]=c;
if (VESA_bpp==16)
{
adr++; if (adr==0) VESApage(p+1);
scr[adr]=(c>>8);
}
}
}
//==============================================================================
void VESApnt32(int x,int y,int r,int g ,int b)
{
unsigned int adr;
int p;
// inside screen?
if ((x>=0)&&(x<VESA_xs))
if ((y>=0)&&(y<VESA_ys))
{
// Low 16 bit of address
adr=y;
adr*=VESA_xs;
adr+=x;
adr*=(VESA_bpp>>3);
// Page
for (p=0;VESA_page_xy[p+p+0]>=0;p++)
{
if (VESA_page_xy[p+p+1]>y) break;
if (VESA_page_xy[p+p+1]<y) continue;
if (VESA_page_xy[p+p+0]>x) break;
}
if (p!=VESA_page) VESApage(p);
// Render
scr[adr]=b; adr++; if (adr==0) VESApage(p+1);
scr[adr]=g; adr++; if (adr==0) VESApage(p+1);
scr[adr]=r;
}
}
//==============================================================================
//=== End. =====================================================================
//==============================================================================

main.cpp

    //==============================================================================
//=== Includes: ================================================================
//==============================================================================
#include "vesa.h"
//==============================================================================
//=== Main: ====================================================================
//==============================================================================
void main()
{
if (!VESAmode(800,600,32)) return;
VESAcls();
int x,y;
unsigned int c;
for (y=0;y<VESA_ys;y++)
for (x=0;x<VESA_xs;x++)
{
if (VESA_bpp== 8)
{
c=x+y;
VESApnt(x,y,c);
}
if (VESA_bpp==16)
{
c=(x&31)+((y&63)<<5);
VESApnt(x,y,c);
}
if (VESA_bpp==32) VESApnt32(x,y,x,x+y,y);
}

VESAexit();
}
//==============================================================================
//=== End. =====================================================================
//==============================================================================

  1. GDI - usable on Windows

Canvas is graphic subcomponent of visual components on Windows. In Borland is the class TCanvas named Canvas. All windows has it also, PaintBoxes, Bitmaps, .... It is the GDI interface between Windows and your application. It has subcomponents like Pen, Brush, and Font for lines, fills or text paper, texts ink.

    Form1->Canvas->Pen->Color=clYellow;
Form1->Canvas->MoveTo(10,10);
Form1->Canvas->LineTo(100,150);

where Form1 is my VCL window. This code draws a yellow line.

GDI has many functions like Arc, Ellipse, Pixels[][],.... See the built-in help of your IDE for more information.


  1. GDI Bitmap

This is a special object. It is a bitmap with an OS graphic handle (DC device context). This allows a bitmap to be something like a window and have access to GDI:

    Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->Width=100;
bmp->Height=100;
bmp->HandleType=bmDIB; // Allows use of ScanLine
bmp->PixelFormat=pf32bit; // 32-bit - the same as int so we can use int* for pixels pointer

This creates a VCL bitmap and sets it to 100x100x32 bit with direct access. Now you can access the ScanLine property. Also bmp->Canvas is present, so you can do all GDI stuff too.

    int *p=bmp->ScanLine[10]; // p = pointer to y=10 line of bitmap
p[20]=0; // Draw dot on x=20,y=10 color=0x00000000 which is black
int c = p[15]; // Read pixel x=15,y=10 from bitmap to c

Be careful to stay with x,y inside a bitmap or an exception will be thrown. The color coding depends on pixelformat, and usually it is 0x00RRGGBB or 0x00BBGGRR. I think this approach is the best option for you. Also, you can draw any GDI object to any other GDI object:

    Form1->Canvas->Draw(0, 0, bmp);

This draws your bitmap to the window, so you can see it actually.


  1. Graphics library

There are many, but the most used are OpenGL and DirectX. I prefer OpenGL, because it is simpler to implement (at least for starters) and also OpenGL is cross-platform and DirectX is Windows only. Also when I started coding there wasn't any DirecX. When I started using OpenGL all vendors had it included in the drivers. Now the only vendors which are still up to date are Nvidia and ATI (AMD). There is almost always some driver issue between them, but in general Nvidia is better for OpenGL (has bugs in the DirectX implementation) and ATI (AMD versions only) is better for DirectX (it has bugs in the OpenGL implementation). But for basic operations you are fine (problems gets on more advanced functions).

Vendors like Intel, SiS, etc. have stopped their implementations on newer OpenGL versions. At least, I do not know of any driver better than OpenGL 3.3 for them.

To get started with OpenGL, see OpenGL get Device Context.

I strongly recommend to start with GDI + Bitmap first. You can do a lot with them. I am still using it for non-complex rendering.

As mentioned before, I am Borland (VCL style) friendly, so if you use different compiler/IDE then change the GDI object names to correspond your environment. I think Canvas is the same and bitmap is HBitmap, but better check your help/documentation. At least you know what to search for.

Other platforms and stuff

  • Simple graphics in Linux is here: X11/Xlib.h example
  • VGA modes in assembly 8086


Related Topics



Leave a reply



Submit