Ich habe über Farbräume gelesen und LAB- Räume scheinen eine gute Option für Sie zu sein (siehe folgende Fragen: Finden eines genauen „Abstands“ zwischen Farben und Algorithmus, um die Ähnlichkeit von Farben zu überprüfen )
Wenn Sie die Wikipedia CIELAB- Seite zitieren , sind die Vorteile dieses Farbraums:
Im Gegensatz zu den RGB- und CMYK-Farbmodellen ist die Lab-Farbe so konzipiert, dass sie sich dem menschlichen Sehen annähert. Es strebt nach Wahrnehmungsgleichmäßigkeit und seine L-Komponente entspricht genau der menschlichen Wahrnehmung von Leichtigkeit. Somit kann es verwendet werden, um genaue Farbbalancekorrekturen vorzunehmen, indem Ausgangskurven in den a- und b-Komponenten modifiziert werden.
Um den Abstand zwischen Farben zu messen, können Sie den Delta E- Abstand verwenden.
Mit diesem können Sie besser von Colorbis annähernConsoleColor :
Zunächst können Sie eine CieLabKlasse definieren , um Farben in diesem Bereich darzustellen:
public class CieLab
{
public double L { get; set; }
public double A { get; set; }
public double B { get; set; }
public static double DeltaE(CieLab l1, CieLab l2)
{
return Math.Pow(l1.L - l2.L, 2) + Math.Pow(l1.A - l2.A, 2) + Math.Pow(l1.B - l2.B, 2);
}
public static CieLab Combine(CieLab l1, CieLab l2, double amount)
{
var l = l1.L * amount + l2.L * (1 - amount);
var a = l1.A * amount + l2.A * (1 - amount);
var b = l1.B * amount + l2.B * (1 - amount);
return new CieLab { L = l, A = a, B = b };
}
}
Es gibt zwei statische Methoden, eine zum Messen des Abstands mit Delta E ( DeltaE) und eine zum Kombinieren von zwei Farben, die angeben, wie viel von jeder Farbe ( Combine).
Und für die Transformation von RGBnach können LABSie die folgende Methode verwenden (von hier aus ):
public static CieLab RGBtoLab(int red, int green, int blue)
{
var rLinear = red / 255.0;
var gLinear = green / 255.0;
var bLinear = blue / 255.0;
double r = rLinear > 0.04045 ? Math.Pow((rLinear + 0.055) / (1 + 0.055), 2.2) : (rLinear / 12.92);
double g = gLinear > 0.04045 ? Math.Pow((gLinear + 0.055) / (1 + 0.055), 2.2) : (gLinear / 12.92);
double b = bLinear > 0.04045 ? Math.Pow((bLinear + 0.055) / (1 + 0.055), 2.2) : (bLinear / 12.92);
var x = r * 0.4124 + g * 0.3576 + b * 0.1805;
var y = r * 0.2126 + g * 0.7152 + b * 0.0722;
var z = r * 0.0193 + g * 0.1192 + b * 0.9505;
Func<double, double> Fxyz = t => ((t > 0.008856) ? Math.Pow(t, (1.0 / 3.0)) : (7.787 * t + 16.0 / 116.0));
return new CieLab
{
L = 116.0 * Fxyz(y / 1.0) - 16,
A = 500.0 * (Fxyz(x / 0.9505) - Fxyz(y / 1.0)),
B = 200.0 * (Fxyz(y / 1.0) - Fxyz(z / 1.0890))
};
}
Die Idee ist, Schattenzeichen wie @AntoninLejsek do ('█', '▓', '▒', '░') zu verwenden. Auf diese Weise können Sie mehr als 16 Farben erhalten, indem Sie die Konsolenfarben kombinieren (mithilfe der CombineMethode).
Hier können wir einige Verbesserungen vornehmen, indem wir die zu verwendenden Farben vorberechnen:
class ConsolePixel
{
public char Char { get; set; }
public ConsoleColor Forecolor { get; set; }
public ConsoleColor Backcolor { get; set; }
public CieLab Lab { get; set; }
}
static List<ConsolePixel> pixels;
private static void ComputeColors()
{
pixels = new List<ConsolePixel>();
char[] chars = { '█', '▓', '▒', '░' };
int[] rs = { 0, 0, 0, 0, 128, 128, 128, 192, 128, 0, 0, 0, 255, 255, 255, 255 };
int[] gs = { 0, 0, 128, 128, 0, 0, 128, 192, 128, 0, 255, 255, 0, 0, 255, 255 };
int[] bs = { 0, 128, 0, 128, 0, 128, 0, 192, 128, 255, 0, 255, 0, 255, 0, 255 };
for (int i = 0; i < 16; i++)
for (int j = i + 1; j < 16; j++)
{
var l1 = RGBtoLab(rs[i], gs[i], bs[i]);
var l2 = RGBtoLab(rs[j], gs[j], bs[j]);
for (int k = 0; k < 4; k++)
{
var l = CieLab.Combine(l1, l2, (4 - k) / 4.0);
pixels.Add(new ConsolePixel
{
Char = chars[k],
Forecolor = (ConsoleColor)i,
Backcolor = (ConsoleColor)j,
Lab = l
});
}
}
}
Eine weitere Verbesserung könnte darin bestehen, direkt auf die Bilddaten zuzugreifen, LockBitsanstatt sie zu verwendenGetPixel .
UPDATE : Wenn das Bild Teile mit derselben Farbe enthält, können Sie den Prozess des Zeichnens von Zeichen mit denselben Farben anstelle einzelner Zeichen erheblich beschleunigen:
public static void DrawImage(Bitmap source)
{
int width = Console.WindowWidth - 1;
int height = (int)(width * source.Height / 2.0 / source.Width);
using (var bmp = new Bitmap(source, width, height))
{
var unit = GraphicsUnit.Pixel;
using (var src = bmp.Clone(bmp.GetBounds(ref unit), PixelFormat.Format24bppRgb))
{
var bits = src.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, src.PixelFormat);
byte[] data = new byte[bits.Stride * bits.Height];
Marshal.Copy(bits.Scan0, data, 0, data.Length);
for (int j = 0; j < height; j++)
{
StringBuilder builder = new StringBuilder();
var fore = ConsoleColor.White;
var back = ConsoleColor.Black;
for (int i = 0; i < width; i++)
{
int idx = j * bits.Stride + i * 3;
var pixel = DrawPixel(data[idx + 2], data[idx + 1], data[idx + 0]);
if (pixel.Forecolor != fore || pixel.Backcolor != back)
{
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.Write(builder);
builder.Clear();
}
fore = pixel.Forecolor;
back = pixel.Backcolor;
builder.Append(pixel.Char);
}
Console.ForegroundColor = fore;
Console.BackgroundColor = back;
Console.WriteLine(builder);
}
Console.ResetColor();
}
}
}
private static ConsolePixel DrawPixel(int r, int g, int b)
{
var l = RGBtoLab(r, g, b);
double diff = double.MaxValue;
var pixel = pixels[0];
foreach (var item in pixels)
{
var delta = CieLab.DeltaE(l, item.Lab);
if (delta < diff)
{
diff = delta;
pixel = item;
}
}
return pixel;
}
Rufen Sie zum Schluss DrawImageso an:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Ergebnis Bilder:


Die folgenden Lösungen basieren nicht auf Zeichen, sondern bieten detaillierte Bilder
Sie können mit dem Handler über jedes Fenster zeichnen , um ein GraphicsObjekt zu erstellen . Um den Handler einer Konsolenanwendung zu erhalten, können Sie Folgendes importieren GetConsoleWindow:
[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();
Erstellen Sie dann mit dem Handler (using Graphics.FromHwnd) eine Grafik und zeichnen Sie das Bild mit den Methoden im GraphicsObjekt, zum Beispiel:
static void Main(string[] args)
{
var handler = GetConsoleHandle();
using (var graphics = Graphics.FromHwnd(handler))
using (var image = Image.FromFile("img101.png"))
graphics.DrawImage(image, 50, 50, 250, 200);
}

Dies sieht gut aus, aber wenn die Größe der Konsole geändert oder ein Bildlauf durchgeführt wird, verschwindet das Bild, weil das Fenster aktualisiert wird (in Ihrem Fall ist möglicherweise die Implementierung eines Mechanismus zum Neuzeichnen des Bildes möglich).
Eine andere Lösung ist das Einbetten eines Fensters ( Form) in die Konsolenanwendung. Dazu müssen Sie importieren SetParent(und MoveWindowdas Fenster in der Konsole verschieben):
[DllImport("user32.dll")]
public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
Dann müssen Sie nur noch eine Eigenschaft erstellen Formund BackgroundImageauf das gewünschte Image setzen (tun Sie dies auf einem Threadoder um Taskzu vermeiden, dass die Konsole blockiert wird):
static void Main(string[] args)
{
Task.Factory.StartNew(ShowImage);
Console.ReadLine();
}
static void ShowImage()
{
var form = new Form
{
BackgroundImage = Image.FromFile("img101.png"),
BackgroundImageLayout = ImageLayout.Stretch
};
var parent = GetConsoleHandle();
var child = form.Handle;
SetParent(child, parent);
MoveWindow(child, 50, 50, 250, 200, true);
Application.Run(form);
}

Natürlich können Sie festlegen FormBorderStyle = FormBorderStyle.None, dass Fensterränder ausgeblendet werden (rechtes Bild).
In diesem Fall können Sie die Größe der Konsole ändern und das Bild / Fenster ist noch vorhanden.
Ein Vorteil dieses Ansatzes besteht darin, dass Sie das gewünschte Fenster jederzeit lokalisieren und das Bild jederzeit ändern können, indem Sie einfach die BackgroundImageEigenschaft ändern .