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 Color
bis annähernConsoleColor
:
Zunächst können Sie eine CieLab
Klasse 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 RGB
nach können LAB
Sie 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 Combine
Methode).
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, LockBits
anstatt 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 DrawImage
so 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 Graphics
Objekt 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 Graphics
Objekt, 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 MoveWindow
das 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 Form
und BackgroundImage
auf das gewünschte Image setzen (tun Sie dies auf einem Thread
oder um Task
zu 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 BackgroundImage
Eigenschaft ändern .