Je lisais sur les espaces colorimétriques et l' espace LAB semble être une bonne option pour vous (voir ces questions: Trouver une «distance» précise entre les couleurs et l' algorithme pour vérifier la similitude des couleurs )
Citant la page Wikipedia CIELAB , les avantages de cet espace colorimétrique sont:
Contrairement aux modèles de couleurs RVB et CMJN, la couleur Lab est conçue pour se rapprocher de la vision humaine. Il aspire à l'uniformité perceptuelle et sa composante L correspond étroitement à la perception humaine de la légèreté. Ainsi, il peut être utilisé pour effectuer des corrections précises de l'équilibre des couleurs en modifiant les courbes de sortie dans les composantes a et b.
Pour mesurer la distance entre les couleurs, vous pouvez utiliser la distance Delta E.
Avec cela, vous pouvez mieux vous rapprocher de Colorà ConsoleColor:
Tout d'abord, vous pouvez définir une CieLabclasse pour représenter les couleurs dans cet espace:
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 };
}
}
Il existe deux méthodes statiques, l'une pour mesurer la distance en utilisant Delta E ( DeltaE) et l'autre pour combiner deux couleurs en spécifiant la quantité de chaque couleur ( Combine).
Et pour transformer de RGBen, LABvous pouvez utiliser la méthode suivante (à partir d' ici ):
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))
};
}
L'idée est d'utiliser des caractères d'ombre comme @AntoninLejsek do ('█', '▓', '▒', ''), cela vous permet d'obtenir plus de 16 couleurs combinant les couleurs de la console (en utilisant la Combineméthode).
Ici, nous pouvons faire quelques améliorations en pré-calculant les couleurs à utiliser:
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
});
}
}
}
Une autre amélioration pourrait être d'accéder directement aux données d'image en utilisant LockBitsau lieu d'utiliserGetPixel .
MISE À JOUR : Si l'image comporte des parties de la même couleur, vous pouvez accélérer considérablement le processus de dessin d'un morceau de caractères ayant les mêmes couleurs, au lieu de caractères individuels:
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;
}
Enfin, appelez DrawImagecomme ceci:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Images de résultat:


Les solutions suivantes ne sont pas basées sur des caractères mais fournissent des images détaillées complètes
Vous pouvez dessiner sur n'importe quelle fenêtre en utilisant son gestionnaire pour créer un Graphicsobjet. Pour obtenir le gestionnaire d'une application console, vous pouvez le faire en important GetConsoleWindow:
[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();
Ensuite, créez un graphique avec le gestionnaire (en utilisant Graphics.FromHwnd) et dessinez l'image en utilisant les méthodes de l' Graphicsobjet, par exemple:
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);
}

Cela semble correct mais si la console est redimensionnée ou défilée, l'image disparaît car la fenêtre est rafraîchie (peut-être que la mise en œuvre d'une sorte de mécanisme pour redessiner l'image est possible dans votre cas).
Une autre solution consiste à intégrer une fenêtre ( Form) dans l'application console. Pour ce faire, vous devez importer SetParent(et MoveWindowdéplacer la fenêtre à l'intérieur de la console):
[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);
Ensuite, il vous suffit de créer une propriété Formet de définir BackgroundImagel'image souhaitée (faites-le sur un Threadou Taskpour éviter de bloquer la console):
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);
}

Bien sûr, vous pouvez définir FormBorderStyle = FormBorderStyle.Nonepour masquer les bordures des fenêtres (image de droite)
Dans ce cas, vous pouvez redimensionner la console et l'image / la fenêtre est toujours là.
L'un des avantages de cette approche est que vous pouvez localiser la fenêtre où vous le souhaitez et modifier l'image à tout moment en modifiant simplement la BackgroundImagepropriété.