Skip to content

Instantly share code, notes, and snippets.

@Hotrian
Last active March 16, 2016 16:47
Show Gist options
  • Save Hotrian/7a864af47064de9e0738 to your computer and use it in GitHub Desktop.
Save Hotrian/7a864af47064de9e0738 to your computer and use it in GitHub Desktop.
Outputs colored text to System.Console. Colorful code borrowed from https://github.com/tomakita/Colorful.Console See bottom of page for usage.
using System;
using System.Drawing;
using System.Runtime.InteropServices;
namespace Colorful
{
/// <summary>
/// Exposes methods used for mapping System.Drawing.Colors to System.ConsoleColors.
/// Based on code that was originally written by Alex Shvedov, and that was then modified by MercuryP.
/// </summary>
public sealed class ColorMapper
{
[StructLayout(LayoutKind.Sequential)]
private struct COORD
{
internal short X;
internal short Y;
}
[StructLayout(LayoutKind.Sequential)]
private struct SMALL_RECT
{
internal short Left;
internal short Top;
internal short Right;
internal short Bottom;
}
[StructLayout(LayoutKind.Sequential)]
private struct COLORREF
{
private uint ColorDWORD;
internal COLORREF(Color color)
{
ColorDWORD = (uint)color.R + (((uint)color.G) << 8) + (((uint)color.B) << 16);
}
internal COLORREF(uint r, uint g, uint b)
{
ColorDWORD = r + (g << 8) + (b << 16);
}
}
[StructLayout(LayoutKind.Sequential)]
private struct CONSOLE_SCREEN_BUFFER_INFO_EX
{
internal int cbSize;
internal COORD dwSize;
internal COORD dwCursorPosition;
internal ushort wAttributes;
internal SMALL_RECT srWindow;
internal COORD dwMaximumWindowSize;
internal ushort wPopupAttributes;
internal bool bFullscreenSupported;
internal COLORREF black;
internal COLORREF darkBlue;
internal COLORREF darkGreen;
internal COLORREF darkCyan;
internal COLORREF darkRed;
internal COLORREF darkMagenta;
internal COLORREF darkYellow;
internal COLORREF gray;
internal COLORREF darkGray;
internal COLORREF blue;
internal COLORREF green;
internal COLORREF cyan;
internal COLORREF red;
internal COLORREF magenta;
internal COLORREF yellow;
internal COLORREF white;
}
const int STD_OUTPUT_HANDLE = -11; // per WinBase.h
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); // per WinBase.h
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetStdHandle(int nStdHandle);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);
/// <summary>
/// Maps a System.Drawing.Color to a System.ConsoleColor.
/// </summary>
/// <param name="oldColor">The color to be replaced.</param>
/// <param name="newColor">The color to be mapped.</param>
public static void MapColor(ConsoleColor oldColor, Color newColor)
{
MapColor(oldColor, newColor.R, newColor.G, newColor.B);
}
private static void MapColor(ConsoleColor color, uint r, uint g, uint b)
{
CONSOLE_SCREEN_BUFFER_INFO_EX csbe = new CONSOLE_SCREEN_BUFFER_INFO_EX();
csbe.cbSize = (int)Marshal.SizeOf(csbe); // 96 = 0x60
IntPtr hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE); // 7
if (hConsoleOutput == INVALID_HANDLE_VALUE)
{
throw new ColorMappingException(Marshal.GetLastWin32Error());
}
bool brc = GetConsoleScreenBufferInfoEx(hConsoleOutput, ref csbe);
if (!brc)
{
throw new ColorMappingException(Marshal.GetLastWin32Error());
}
switch (color)
{
case ConsoleColor.Black:
csbe.black = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkBlue:
csbe.darkBlue = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkGreen:
csbe.darkGreen = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkCyan:
csbe.darkCyan = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkRed:
csbe.darkRed = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkMagenta:
csbe.darkMagenta = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkYellow:
csbe.darkYellow = new COLORREF(r, g, b);
break;
case ConsoleColor.Gray:
csbe.gray = new COLORREF(r, g, b);
break;
case ConsoleColor.DarkGray:
csbe.darkGray = new COLORREF(r, g, b);
break;
case ConsoleColor.Blue:
csbe.blue = new COLORREF(r, g, b);
break;
case ConsoleColor.Green:
csbe.green = new COLORREF(r, g, b);
break;
case ConsoleColor.Cyan:
csbe.cyan = new COLORREF(r, g, b);
break;
case ConsoleColor.Red:
csbe.red = new COLORREF(r, g, b);
break;
case ConsoleColor.Magenta:
csbe.magenta = new COLORREF(r, g, b);
break;
case ConsoleColor.Yellow:
csbe.yellow = new COLORREF(r, g, b);
break;
case ConsoleColor.White:
csbe.white = new COLORREF(r, g, b);
break;
}
csbe.srWindow.Bottom++;
csbe.srWindow.Right++;
brc = SetConsoleScreenBufferInfoEx(hConsoleOutput, ref csbe);
if (!brc)
{
throw new ColorMappingException(Marshal.GetLastWin32Error());
}
}
}
}
using System;
namespace Colorful
{
/// <summary>
/// Encapsulates information relating to exceptions thrown during color mapping.
/// </summary>
public sealed class ColorMappingException : Exception
{
/// <summary>
/// Encapsulates information relating to exceptions thrown during color mapping.
/// </summary>
/// <param name="errorCode">The underlying Win32 error code associated with the exception that
/// has been trapped.</param>
public ColorMappingException(int errorCode) : base($"Color conversion failed with system error code {errorCode}!")
{
}
}
}
namespace Logger
{
public interface ILogArgs
{
}
}
using System;
using System.Linq;
using Colorful;
namespace Logger
{
public static class Log
{
// These don't actually control the color of the console since the user can change them Instead,
// these map to the Enum for White & Black, which should be the default Foreground and Background
// palette entries. These are here in case that changes in the future and they need to be remapped.
public const ConsoleColor DefaultForeground = ConsoleColor.White;
public const ConsoleColor DefaultBackground = ConsoleColor.Black;
// This controls which tokens can be used for arguments in WriteMultiColorLine
public static readonly string[] MultiColorBreak = { "{x}" };
public static readonly string[] FiveCharKeys = { "!!!!!", "?????", ".....", ",,,,," };
public static readonly string[] FourCharKeys = { "!!!!", "????", "....", ",,,," };
public static readonly string[] ThreeCharKeys = { "!!!", "???", "...", ",,,", "\\\\\\", "///"};
public static readonly string[] TwoCharKeys = { "!!", "??", "..", ",,", "\\\\", "//"};
public static readonly string[] OneCharKeys = { "!", "?", ".", ",", "\\", "/"};
public static void WriteLine(string msg, System.Drawing.Color foregroundColor)
{
Console.BackgroundColor = DefaultBackground;
Console.ForegroundColor = RollingColorMapper.GetFromColor(foregroundColor);
Console.WriteLine(msg);
Console.ForegroundColor = DefaultForeground;
}
public static void WriteLine(string msg, System.Drawing.Color foregroundColor, System.Drawing.Color backgroundColor)
{
Console.BackgroundColor = RollingColorMapper.GetFromColor(backgroundColor);
Console.ForegroundColor = RollingColorMapper.GetFromColor(foregroundColor);
Console.WriteLine(msg);
Console.ForegroundColor = DefaultForeground;
Console.BackgroundColor = DefaultBackground;
}
public static void WriteMultiColorLine(string msg, ILogArgs[] args, System.Drawing.Color? foregroundColor = null, System.Drawing.Color? backgroundColor = null)
{
ConsoleColor? fg = foregroundColor != null ? RollingColorMapper.GetFromColor(foregroundColor.Value) : DefaultForeground;
ConsoleColor? bg = backgroundColor != null ? RollingColorMapper.GetFromColor(backgroundColor.Value) : DefaultBackground;
WriteMultiColorLine(msg, args, fg, bg);
}
private static void WriteMultiColorLine(string msg, ILogArgs[] args, ConsoleColor? foregroundColor = null, ConsoleColor? backgroundColor = null)
{
if (!MultiColorBreak.Any(msg.Contains))
{
// Just write the line if we don't have any args
Console.BackgroundColor = backgroundColor ?? DefaultBackground;
Console.ForegroundColor = foregroundColor ?? DefaultForeground;
Console.WriteLine(msg);
Console.ForegroundColor = DefaultForeground;
Console.BackgroundColor = DefaultBackground;
return;
}
var parts = msg.Split(MultiColorBreak, StringSplitOptions.None);
for (var i = 0; i < parts.Length; i ++)
{
WriteNoResetColor(parts[i], foregroundColor ?? DefaultForeground, backgroundColor ?? DefaultBackground); // Write string piece
if (i == parts.Length - 1) continue;
if (args == null) continue;
var argPos = i;
if (i < parts.Length - 1 && parts[i + 1].StartsWith(":"))
{
var pieces = parts[i + 1].Split(new[] { " " }, StringSplitOptions.None);
int a;
if (FiveCharKeys.Any(pieces[0].EndsWith))
{
pieces[0] = pieces[0].Substring(0, pieces[0].Length - 5);
}
else if (FourCharKeys.Any(pieces[0].EndsWith))
{
pieces[0] = pieces[0].Substring(0, pieces[0].Length - 4);
}
else if (ThreeCharKeys.Any(pieces[0].EndsWith))
{
pieces[0] = pieces[0].Substring(0, pieces[0].Length - 3);
}
else if (TwoCharKeys.Any(pieces[0].EndsWith))
{
pieces[0] = pieces[0].Substring(0, pieces[0].Length - 2);
}
else if (OneCharKeys.Any(pieces[0].EndsWith))
{
pieces[0] = pieces[0].Substring(0, pieces[0].Length - 1);
}
if (int.TryParse(pieces[0].Substring(1), out a))
{
argPos = a;
parts[i + 1] = parts[i + 1].Substring(pieces[0].Length);
}
}
if (argPos < 0 || argPos >= args.Length)
{
argPos = i % args.Length;
}
var logArg = args[argPos] as LogArg;
if (logArg != null)
{
WriteNoResetColor(logArg.Argument.ArgText, logArg.Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArg.Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
else
{
var logArgs = args[argPos] as LogArgCollection;
if (logArgs?.Arguments == null) continue;
for(var j = 0; j < logArgs.Arguments.Length; j++)
{
switch (logArgs.Style)
{
case LogArgConcatStyle.Space:
switch (logArgs.Mode)
{
case LogArgConcatMode.Postfix:
if (j < logArgs.Arguments.Length - 1)
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText + " ", logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
else
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
break;
case LogArgConcatMode.Prefix:
if (j > 0)
{
WriteNoResetColor(" " + logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
else
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
break;
case LogArgConcatMode.Normal:
default:
if (j < logArgs.Arguments.Length - 1)
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
WriteNoResetColor(" ", foregroundColor ?? DefaultForeground, backgroundColor ?? DefaultBackground);
}
else
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
break;
}
break;
case LogArgConcatStyle.CommaSpace:
switch (logArgs.Mode)
{
case LogArgConcatMode.Postfix:
if (j < logArgs.Arguments.Length - 1)
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText + ", ", logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
else
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
break;
case LogArgConcatMode.Prefix:
if (j > 0)
{
WriteNoResetColor(", " + logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
else
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
break;
case LogArgConcatMode.Normal:
default:
if (j < logArgs.Arguments.Length - 1)
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
WriteNoResetColor(", ", foregroundColor ?? DefaultForeground, backgroundColor ?? DefaultBackground);
}
else
{
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
}
break;
}
break;
case LogArgConcatStyle.None:
default:
WriteNoResetColor(logArgs.Arguments[j].Argument.ArgText, logArgs.Arguments[j].Argument.ForegroundColor ?? foregroundColor ?? DefaultForeground, logArgs.Arguments[j].Argument.BackgroundColor ?? backgroundColor ?? DefaultBackground);
break;
}
}
}
}
Console.ForegroundColor = DefaultForeground;
Console.BackgroundColor = DefaultBackground;
Console.WriteLine();
}
private static void WriteNoResetColor(string msg, ConsoleColor foregroundColor, ConsoleColor backgroundColor)
{
Console.BackgroundColor = backgroundColor;
Console.ForegroundColor = foregroundColor;
Console.Write(msg);
}
}
}
using System;
using Colorful;
namespace Logger
{
public class LogArg : ILogArgs
{
public LogArgument Argument;
public LogArg(string argText, System.Drawing.Color foregroundColor, System.Drawing.Color? backgroundColor = null)
{
ConsoleColor? fg = RollingColorMapper.GetFromColor(foregroundColor);
ConsoleColor? bg = null;
if (backgroundColor != null)
{
bg = RollingColorMapper.GetFromColor(backgroundColor.Value);
}
Argument = new LogArgument(argText, fg, bg);
}
public LogArg(string argText, ConsoleColor? foregroundColor = null, ConsoleColor? backgroundColor = null)
{
Argument = new LogArgument(argText, foregroundColor, backgroundColor);
}
public LogArg(LogArgument argument)
{
Argument = argument;
}
}
}
namespace Logger
{
public class LogArgCollection : ILogArgs
{
public LogArg[] Arguments;
public LogArgConcatStyle Style;
public LogArgConcatMode Mode;
public LogArgCollection(LogArg[] arguments, LogArgConcatStyle style = LogArgConcatStyle.Space, LogArgConcatMode mode = LogArgConcatMode.Normal)
{
Arguments = arguments;
Style = style;
Mode = mode;
}
}
}
namespace Logger
{
public enum LogArgConcatMode
{
Normal,
Postfix,
Prefix
}
}
namespace Logger
{
public enum LogArgConcatStyle
{
None,
Space,
CommaSpace
}
}
using System;
namespace Logger
{
public struct LogArgument
{
public readonly string ArgText;
public readonly ConsoleColor? ForegroundColor;
public readonly ConsoleColor? BackgroundColor;
public LogArgument(string argText, ConsoleColor? foregroundColor = null, ConsoleColor? backgroundColor = null)
{
ArgText = argText;
ForegroundColor = foregroundColor;
BackgroundColor = backgroundColor;
}
}
}
using Color = System.Drawing.Color;
// Some of these have a leading zero because ReSharper colors the block they're in, and it looks nice this way :)
namespace Logger
{
public static class LoggerColors
{
public static Color DarkRed = Color.FromArgb(160, 40, 40);
public static Color Red = Color.FromArgb(255, 50, 50);
public static Color Orange = Color.FromArgb(255, 100, 20);
public static Color Yellow = Color.FromArgb(255, 255, 20);
public static Color DarkYellow = Color.FromArgb(170, 160, 10);
public static Color DarkGreen = Color.FromArgb(020, 120, 20);
public static Color Green = Color.FromArgb(020, 255, 20);
public static Color Cyan = Color.FromArgb(020, 255, 240);
public static Color Blue = Color.FromArgb(020, 150, 255);
public static Color DarkBlue = Color.FromArgb(020, 100, 255);
public static Color Purple = Color.FromArgb(140, 20, 200);
public static Color Magenta = Color.FromArgb(255, 20, 255);
public static Color White = Color.FromArgb(255, 255, 255);
public static Color LighterGray = Color.FromArgb(200, 200, 200);
public static Color LightGray = Color.FromArgb(150, 150, 150);
public static Color Gray = Color.FromArgb(100, 100, 100);
public static Color DarkGray = Color.FromArgb(050, 50, 50);
public static Color DarkerGray = Color.FromArgb(025, 25, 25);
public static Color Black = Color.FromArgb(000, 0, 0);
}
}
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using Logger;
namespace Colorful
{
public static class RollingColorMapper
{
private static bool loadedForegroundBackground = false;
private static readonly ConsoleColor[] IgnoredColors = {Log.DefaultForeground, Log.DefaultBackground}; // Don't remap the default colors
private static readonly Color[] CurrentColors = new Color[16];
private static List<ConsoleColor> _colorList;
// ReSharper disable once InconsistentNaming
public static ConsoleColor GetFromRGB(int r, int g, int b)
{
return GetFromColor(Color.FromArgb(r, g, b));
}
public static ConsoleColor GetFromColor(Color color)
{
// Check if one of our CurrentColors
for (var i = 0; i < 16; i++)
{
var cc = (ConsoleColor)i;
if (IgnoredColors.Contains(cc) || CurrentColors[i] != color) continue;
_colorList.Remove(cc); // Remove from current spot
_colorList.Add(cc); // Add to end of list
return cc; // Return ConsoleColor
}
// On first launch, add all ConsoleColors to list except ignored colors
if (_colorList == null)
{
_colorList = new List<ConsoleColor>();
var colorEnums = (ConsoleColor[]) Enum.GetValues(typeof (ConsoleColor));
foreach (var colorEnum in colorEnums.Where(colorEnum => !IgnoredColors.Contains(colorEnum)))
{
_colorList.Add(colorEnum);
}
if (!loadedForegroundBackground)
{
ColorMapper.MapColor(Log.DefaultForeground, LoggerColors.White);
ColorMapper.MapColor(Log.DefaultBackground, LoggerColors.Black);
}
}
// Grab first color on list and remap, adding to end of list
var c = _colorList[0];
_colorList.Remove(c); // Remove from current spot (at start)
_colorList.Add(c); // Add to end of list
ColorMapper.MapColor(c, color); // Remap ConsoleColor
CurrentColors[(int) c] = color; // Map Color for reuse
return c; // Return ConsoleColor
}
}
}
@Hotrian
Copy link
Author

Hotrian commented Mar 16, 2016

As far as I can tell, Colorful.Console only supports up to 16 colors in the lifetime of the System.Console. I'm not sure what happens when you've used all 16 colors, but I don't see any rolling color systems, so I implemented the RollingColorMapper which allows easy access to any color, the only limitation is that only the last 14 colors (+foreground+background) will be used. The 15th colored object will have it's color changed so the new object can be colored the desired color. When a color is reused it is pushed to the top of the stack so it will always be the 15th last used color that will be recycled for the new color. This shouldn't be noticeable in most logs and output, but might not work for certain projects like Text Based RPGs where it might be desirable to have more than 16 colors visible at a time.

To reduce on garbage, the LoggerColors class was created. It is not required to use the LoggerColors as any RGB color can be used.

Usage is similar to String.Format, using {x} to represent arguments, and {x}:# to choose a specific argument.

Examples:

Log.WriteMultiColorLine("-- {x} ran out of air! --", new ILogArgs[] { new LogArg("Bob", LoggerColors.DarkGray) }, LoggerColors.Red);
Log.WriteMultiColorLine("-- {x} ran out of air! --", new ILogArgs[] { new LogArg("Bob", LoggerColors.DarkGray) }, System.Drawing.Color.FromArgb(255, 0, 0));
Log.WriteMultiColorLine("-- {x} attacks {x} with the {x} and {x}:1 dies! --", new ILogArgs[] { new LogArg("Bob", LoggerColors.DarkGray), new LogArg("Jane", LoggerColors.Blue), new LogArg("Axe", LoggerColors.White) }, LoggerColors.Red);

1

So how is this useful? Well those examples aren't very useful, but you don't have to use direct strings like "Bob" and "Jane", you can use variables!

Log.WriteMultiColorLine("-- {x} ran out of air! --", new ILogArgs[] { new LogArg(character.Name, LoggerColors.DarkGray) }, LoggerColors.Red);

2

@Hotrian
Copy link
Author

Hotrian commented Mar 16, 2016

In addition, LogArgCollection was created to facilitate multi parameter arguments. It supports any number of arguments, and supports different concatenation strings and modes.

Log.WriteMultiColorLine("-- {x} ran out of air! --", new ILogArgs[] { new LogArgCollection(new [] { new LogArg("Bob", LoggerColors.Orange) , new LogArg("(Human)", LoggerColors.DarkGray) }, LogArgConcatStyle.Space, LogArgConcatMode.Normal) }, LoggerColors.Red);

3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment