Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support FullColor (24bit) for Windows #103

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Example/demo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ static class Demo {
class Box10x : View {
public Box10x (int x, int y) : base (new Rect (x, y, 10, 10))
{
ColorScheme = new ColorScheme();
ColorScheme.Focus = new Terminal.Gui.Attribute(new TrueColor(0, 0, 0),
new TrueColor(255, 177, 177));
}

public override void Redraw (Rect region)
Expand Down
120 changes: 119 additions & 1 deletion Terminal.Gui/Drivers/ConsoleDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,104 @@ public enum Color {
/// </summary>
White
}

/// <summary>
/// 24bit color. Support translate it to 4bit(Windows) and 8bit(Linux) color
/// </summary>
public class TrueColor {
public int R { get; private set; }
public int G { get; private set; }
public int B { get; private set; }
public TrueColor(int r, int g, int b) {
R = r;
G = g;
B = b;
}
/// <summary>
/// Get color by code in 256 colors palette
/// </summary>
public static TrueColor Color8(int code) {
if (code == 7)
code = 8;
else if (code == 8)
code = 7;
if (code == 8)
return new TrueColor(192, 192, 192);
if (code <= 15) {
int k = 128;
if (code >= 9)
k = 255;
return new TrueColor(code % 2 * k, code / 2 % 2 * k, code / 4 % 2 * k);
}
if (code <= 231) {
code -= 16;
int b = code % 6 * 40;
if (b > 0)
b += 45;
int g = code / 6 % 6 * 40;
if (g > 0)
g += 45;
int r = code / 36 % 6 * 40;
if (r > 0)
r += 45;
return new TrueColor(r, g, b);
}
{
code -= 231;
return new TrueColor(8 + code * 10, 8 + code * 10, 8 + code * 10);
}
}
/// <summary>
/// Get color by 16 colors palette
/// </summary>
public static TrueColor Color4(int code) {
if (code == 7)
code = 8;
else if (code == 8)
code = 7;
if (code == 8)
return new TrueColor(192, 192, 192);
int k = 128;
if (code >= 9)
k = 255;
return new TrueColor(code / 4 % 2 * k, code / 2 % 2 * k, code % 2 * k);
}
/// <summary>
/// Return color diff
/// </summary>
public static int Diff(TrueColor c1, TrueColor c2) {
//TODO: upgrade to CIEDE2000
return (c1.R - c2.R) * (c1.R - c2.R) + (c1.G - c2.G) * (c1.G - c2.G) + (c1.B - c2.B) * (c1.B - c2.B);
}
/// <summary>
/// Get color code in 256 colors palette (use approximation)
/// </summary>
public static int GetCode8(TrueColor c) {
int ans = 0;
for (int i = 1; i < 255; i++)
if (Diff(Color8(i), c) < Diff(Color8(ans), c))
ans = i;
return ans;
}
/// <summary>
/// Get color code in 16 colors palette (use approximation)
/// </summary>
public static int GetCode4(TrueColor c) {
int ans = 0;
for (int i = 1; i < 16; i++)
if (Diff(Color4(i), c) < Diff(Color4(ans), c))
ans = i;
return ans;
}
/// <summary>
/// Convert code in 16 colors palette to 256 colors palette
/// </summary>
public static int Code4ToCode8(int code) {
if (code == 0 || code == 7 || code == 8 || code == 15)
return code;
return (code & 8) + (code & 2) + 4 * (code & 1) + (code & 4) / 4;
}
}

/// <summary>
/// Attributes are used as elements that contain both a foreground and a background or platform specific features
Expand All @@ -93,14 +191,34 @@ public enum Color {
/// </remarks>
public struct Attribute {
internal int value;

/// <summary>
/// Forground color
/// </summary>
public TrueColor Fg { get; private set; }
/// <summary>
/// Backgorund color
/// </summary>
public TrueColor Bg { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="T:Terminal.Gui.Attribute"/> struct.
/// </summary>
/// <param name="value">Value.</param>
public Attribute (int value)
{
this.value = value;
Fg = Bg = null;
}

/// <summary>
/// Build Attribute by two TrueColor
/// </summary>
/// <param name="fg">Forground color</param>
/// <param name="bg">Background color</param>
public Attribute(TrueColor fg, TrueColor bg)
{
value = TrueColor.GetCode4(fg) + TrueColor.GetCode4(bg) * 16;
Fg = fg;
Bg = bg;
}

public static implicit operator int (Attribute c) => c.value;
Expand Down
104 changes: 92 additions & 12 deletions Terminal.Gui/Drivers/WindowsDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,30 @@
using System.Threading.Tasks;
using Mono.Terminal;
using NStack;
using Microsoft.Win32;
using System.Text;

namespace Terminal.Gui {

internal class WindowsConsole {
public const int STD_OUTPUT_HANDLE = -11;
public const int STD_INPUT_HANDLE = -10;
public const int STD_ERROR_HANDLE = -12;

internal IntPtr InputHandle, OutputHandle;
IntPtr ScreenBuffer;
uint originalConsoleMode;

public bool SupportTrueColor { get; private set; }

void SetSupportTrueColor() {
//work only for CMD
//TODO add supprot windows terminals
string ver = RuntimeInformation.OSDescription;
int num = int.Parse(ver.Substring(ver.LastIndexOf('.') + 1));
SupportTrueColor = num >= 14931;
}

public WindowsConsole ()
{
InputHandle = GetStdHandle (STD_INPUT_HANDLE);
Expand All @@ -52,11 +64,17 @@ public WindowsConsole ()
newConsoleMode |= (uint)(ConsoleModes.EnableMouseInput | ConsoleModes.EnableExtendedFlags);
newConsoleMode &= ~(uint)ConsoleModes.EnableQuickEditMode;
ConsoleMode = newConsoleMode;
uint ocm = OutputConsoleMode;
ocm = ocm | (uint)(ConsoleModes.EnableProcessedOutput | ConsoleModes.EnableVirtualTerminalProcessing
| ConsoleModes.DisableNewlineAutoReturn);
ocm -= ocm & (uint)(ConsoleModes.EnableWrapAtEolOutput);
OutputConsoleMode = ocm;
SetSupportTrueColor();
}

public CharInfo [] OriginalStdOutChars;

public bool WriteToConsole (CharInfo [] charInfoBuffer, Coord coords, SmallRect window)
public bool WriteToConsole (FullCharInfo [] charInfoBuffer, Coord coords, SmallRect window)
{
if (ScreenBuffer == IntPtr.Zero) {
ScreenBuffer = CreateConsoleScreenBuffer (
Expand All @@ -82,8 +100,40 @@ public bool WriteToConsole (CharInfo [] charInfoBuffer, Coord coords, SmallRect

ReadConsoleOutput (OutputHandle, OriginalStdOutChars, coords, new Coord () { X = 0, Y = 0 }, ref window);
}

return WriteConsoleOutput (ScreenBuffer, charInfoBuffer, coords, new Coord () { X = window.Left, Y = window.Top }, ref window);
if (!SupportTrueColor) {
CharInfo[] ci = new CharInfo[charInfoBuffer.Length];
for (int i = 0; i < ci.Length; i++) {
ci[i].Char.UnicodeChar = charInfoBuffer[i].Char;
ci[i].Attributes = (ushort)(charInfoBuffer[i].Attributes);
}
return WriteConsoleOutput(ScreenBuffer, ci, coords, new Coord() { X = window.Left, Y = window.Top }, ref window);
}
return WriteConsoleTrueColor (charInfoBuffer);
}

public bool WriteConsoleTrueColor(FullCharInfo[] Buffer) {
char esc = '\x1b';
StringBuilder sb = new StringBuilder();
//save cursor position (esc7) and move to (0,0)
sb.AppendFormat("{0}7{0}[0;0H", esc);
for (int i = 0; i < Buffer.Length; i++) {
var a = Buffer[i].Attributes;
if (Buffer[i].Attributes.Fg != null) {
sb.AppendFormat("{0}[38;2;{1};{2};{3};48;2;{4};{5};{6}m", esc, a.Fg.R, a.Fg.G, a.Fg.B,
a.Bg.R, a.Bg.G, a.Bg.B);
}
else {
sb.AppendFormat("{0}[38;5;{1};48;5;{2}m", esc, TrueColor.Code4ToCode8((int)a % 16), TrueColor.Code4ToCode8((int)a / 16));
}
if (Buffer[i].Char != esc)
sb.Append(Buffer[i].Char);
else
sb.Append(' ');
}
//restore cursore position
sb.AppendFormat("{0}8", esc);
string s = sb.ToString();
return WriteConsole(ScreenBuffer, s, (uint)(s.Length), out uint _, null);
}

public bool SetCursorPosition (Coord position)
Expand Down Expand Up @@ -115,11 +165,26 @@ public uint ConsoleMode {
}
}

public uint OutputConsoleMode {
get {
uint v;
GetConsoleMode(OutputHandle, out v);
return v;
}
set {
SetConsoleMode(OutputHandle, value);
}
}

[Flags]
public enum ConsoleModes : uint {
EnableMouseInput = 16,
EnableQuickEditMode = 64,
EnableExtendedFlags = 128,
EnableProcessedOutput = 1,
EnableWrapAtEolOutput = 2,
EnableVirtualTerminalProcessing = 4,
DisableNewlineAutoReturn = 8
}

[StructLayout (LayoutKind.Explicit, CharSet = CharSet.Unicode)]
Expand Down Expand Up @@ -348,6 +413,15 @@ public override string ToString ()
}
}

[DllImport("kernel32.dll", EntryPoint = "WriteConsole", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool WriteConsole(
IntPtr hConsoleOutput,
String lpbufer,
UInt32 NumberOfCharsToWriten,
out UInt32 lpNumberOfCharsWritten,
object lpReserved);


[DllImport ("kernel32.dll", SetLastError = true)]
static extern IntPtr GetStdHandle (int nStdHandle);

Expand Down Expand Up @@ -412,20 +486,25 @@ public uint InputEventCount {
}
}

struct FullCharInfo {
public char Char { get; set; }
public Attribute Attributes { get; set; }
}

internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver {
static bool sync;
AutoResetEvent eventReady = new AutoResetEvent (false);
AutoResetEvent waitForProbe = new AutoResetEvent (false);
MainLoop mainLoop;
Action TerminalResized;
WindowsConsole.CharInfo [] OutputBuffer;
FullCharInfo [] OutputBuffer;
int cols, rows;
WindowsConsole winConsole;
WindowsConsole.SmallRect damageRegion;

public override int Cols => cols;
public override int Rows => rows;

public WindowsDriver ()
{
winConsole = new WindowsConsole ();
Expand All @@ -436,6 +515,7 @@ public WindowsDriver ()

ResizeScreen ();
UpdateOffScreen ();
winConsole.ConsoleMode = winConsole.ConsoleMode;

Task.Run ((Action)WindowsInputHandler);
}
Expand Down Expand Up @@ -721,7 +801,7 @@ public override void Init (Action terminalResized)

void ResizeScreen ()
{
OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols];
OutputBuffer = new FullCharInfo [Rows * Cols];
Clip = new Rect (0, 0, Cols, Rows);
damageRegion = new WindowsConsole.SmallRect () {
Top = 0,
Expand All @@ -737,7 +817,7 @@ void UpdateOffScreen ()
for (int col = 0; col < cols; col++) {
int position = row * cols + col;
OutputBuffer [position].Attributes = (ushort)MakeColor (ConsoleColor.White, ConsoleColor.Blue);
OutputBuffer [position].Char.UnicodeChar = ' ';
OutputBuffer [position].Char = ' ';
}
}

Expand All @@ -753,8 +833,8 @@ public override void AddRune (Rune rune)
var position = crow * Cols + ccol;

if (Clip.Contains (ccol, crow)) {
OutputBuffer [position].Attributes = (ushort)currentAttribute;
OutputBuffer [position].Char.UnicodeChar = (char)rune;
OutputBuffer [position].Attributes = currentAttribute;
OutputBuffer [position].Char = (char)rune;
WindowsConsole.SmallRect.Update (ref damageRegion, (short)ccol, (short)crow);
}

Expand All @@ -774,10 +854,10 @@ public override void AddStr (ustring str)
AddRune (rune);
}

int currentAttribute;
Attribute currentAttribute;
public override void SetAttribute (Attribute c)
{
currentAttribute = c.value;
currentAttribute = c;
}

private Attribute MakeColor (ConsoleColor f, ConsoleColor b)
Expand Down