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 CieLab
classe 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 RGB
en, LAB
vous 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 Combine
mé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 LockBits
au 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 DrawImage
comme ceci:
static void Main(string[] args)
{
ComputeColors();
Bitmap image = new Bitmap("image.jpg", true);
DrawImage(image);
}
Images de résultat:
![Console1](https://i.stack.imgur.com/qDA2A.png)
![Console2](https://i.stack.imgur.com/VQeFo.png)
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 Graphics
objet. 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' Graphics
objet, 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);
}
![Version 1](https://i.stack.imgur.com/q15XI.png)
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 MoveWindow
dé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é Form
et de définir BackgroundImage
l'image souhaitée (faites-le sur un Thread
ou Task
pour é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);
}
![Version 2](https://i.stack.imgur.com/HXQ3K.png)
Bien sûr, vous pouvez définir FormBorderStyle = FormBorderStyle.None
pour 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 BackgroundImage
propriété.