J'ai une application console qui gère les images. Maintenant, j'ai besoin de quelque chose comme un aperçu des images dans l'application console. Existe-t-il un moyen de les afficher dans la console?
Voici une comparaison des réponses actuelles basées sur les caractères:
Contribution:
Production:
Réponses:
J'ai continué à jouer avec le code de @DieterMeemken. J'ai réduit de moitié la résolution verticale et ajouté du tramage via °. Sur la gauche se trouve le résultat de Dieter Meemken, sur la droite mon. En bas, l'image originale est redimensionnée pour correspondre approximativement à la sortie. Si la fonction de conversion Malwyns est impressionnante, elle n'utilise pas toutes les couleurs grises, ce qui est dommage.
static int[] cColors = { 0x000000, 0x000080, 0x008000, 0x008080, 0x800000, 0x800080, 0x808000, 0xC0C0C0, 0x808080, 0x0000FF, 0x00FF00, 0x00FFFF, 0xFF0000, 0xFF00FF, 0xFFFF00, 0xFFFFFF }; public static void ConsoleWritePixel(Color cValue) { Color[] cTable = cColors.Select(x => Color.FromArgb(x)).ToArray(); char[] rList = new char[] { (char)9617, (char)9618, (char)9619, (char)9608 }; // 1/4, 2/4, 3/4, 4/4 int[] bestHit = new int[] { 0, 0, 4, int.MaxValue }; //ForeColor, BackColor, Symbol, Score for (int rChar = rList.Length; rChar > 0; rChar--) { for (int cFore = 0; cFore < cTable.Length; cFore++) { for (int cBack = 0; cBack < cTable.Length; cBack++) { int R = (cTable[cFore].R * rChar + cTable[cBack].R * (rList.Length - rChar)) / rList.Length; int G = (cTable[cFore].G * rChar + cTable[cBack].G * (rList.Length - rChar)) / rList.Length; int B = (cTable[cFore].B * rChar + cTable[cBack].B * (rList.Length - rChar)) / rList.Length; int iScore = (cValue.R - R) * (cValue.R - R) + (cValue.G - G) * (cValue.G - G) + (cValue.B - B) * (cValue.B - B); if (!(rChar > 1 && rChar < 4 && iScore > 50000)) // rule out too weird combinations { if (iScore < bestHit[3]) { bestHit[3] = iScore; //Score bestHit[0] = cFore; //ForeColor bestHit[1] = cBack; //BackColor bestHit[2] = rChar; //Symbol } } } } } Console.ForegroundColor = (ConsoleColor)bestHit[0]; Console.BackgroundColor = (ConsoleColor)bestHit[1]; Console.Write(rList[bestHit[2] - 1]); } public static void ConsoleWriteImage(Bitmap source) { int sMax = 39; decimal percent = Math.Min(decimal.Divide(sMax, source.Width), decimal.Divide(sMax, source.Height)); Size dSize = new Size((int)(source.Width * percent), (int)(source.Height * percent)); Bitmap bmpMax = new Bitmap(source, dSize.Width * 2, dSize.Height); for (int i = 0; i < dSize.Height; i++) { for (int j = 0; j < dSize.Width; j++) { ConsoleWritePixel(bmpMax.GetPixel(j * 2, i)); ConsoleWritePixel(bmpMax.GetPixel(j * 2 + 1, i)); } System.Console.WriteLine(); } Console.ResetColor(); }
usage:
Bitmap bmpSrc = new Bitmap(@"HuwnC.gif", true); ConsoleWriteImage(bmpSrc);
ÉDITER
La distance de couleur est un sujet complexe ( ici , ici et des liens sur ces pages ...). J'ai essayé de calculer la distance en YUV et les résultats étaient plutôt pires qu'en RVB. Ils pourraient être meilleurs avec Lab et DeltaE, mais je n'ai pas essayé cela. La distance en RVB semble être suffisante. En fait, les résultats sont très similaires pour les distances euclidienne et de Manhattan dans l'espace colorimétrique RVB, donc je suppose qu'il y a trop peu de couleurs parmi lesquelles choisir.
Le reste n'est que la force brute de comparer la couleur à toutes les combinaisons de couleurs et de motifs (= symboles). J'ai indiqué que le taux de remplissage pour ° █ était de 1/4, 2/4, 3/4 et 4/4. Dans ce cas, le troisième symbole est en fait redondant par rapport au premier. Mais si les ratios n'étaient pas aussi uniformes (dépend de la police), les résultats pourraient changer, alors je l'ai laissé là pour de futures améliorations. La couleur moyenne du symbole est calculée en tant que moyenne pondérée de ForegroudColor et backgroundColor en fonction du taux de remplissage. Il suppose des couleurs linéaires, ce qui est également une grande simplification. Il y a donc encore place à l'amélioration.
la source
Bien que montrer une image dans une console ne soit pas l'utilisation prévue de la console, vous pouvez sûrement pirater les choses, car la fenêtre de la console n'est qu'une fenêtre, comme toutes les autres fenêtres.
En fait, une fois que j'ai commencé à développer une bibliothèque de contrôles de texte pour les applications console avec support graphique. Je n'ai jamais fini cela, même si j'ai une démonstration de démonstration de principe:
Et si vous obtenez la taille de police de la console, vous pouvez placer l'image très précisément.
Voici comment vous pouvez le faire:
static void Main(string[] args) { Console.WriteLine("Graphics in console window!"); Point location = new Point(10, 10); Size imageSize = new Size(20, 10); // desired image size in characters // draw some placeholders Console.SetCursorPosition(location.X - 1, location.Y); Console.Write(">"); Console.SetCursorPosition(location.X + imageSize.Width, location.Y); Console.Write("<"); Console.SetCursorPosition(location.X - 1, location.Y + imageSize.Height - 1); Console.Write(">"); Console.SetCursorPosition(location.X + imageSize.Width, location.Y + imageSize.Height - 1); Console.WriteLine("<"); string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonPictures), @"Sample Pictures\tulips.jpg"); using (Graphics g = Graphics.FromHwnd(GetConsoleWindow())) { using (Image image = Image.FromFile(path)) { Size fontSize = GetConsoleFontSize(); // translating the character positions to pixels Rectangle imageRect = new Rectangle( location.X * fontSize.Width, location.Y * fontSize.Height, imageSize.Width * fontSize.Width, imageSize.Height * fontSize.Height); g.DrawImage(image, imageRect); } } }
Voici comment obtenir la taille de police actuelle de la console:
private static Size GetConsoleFontSize() { // getting the console out buffer handle IntPtr outHandle = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero); int errorCode = Marshal.GetLastWin32Error(); if (outHandle.ToInt32() == INVALID_HANDLE_VALUE) { throw new IOException("Unable to open CONOUT$", errorCode); } ConsoleFontInfo cfi = new ConsoleFontInfo(); if (!GetCurrentConsoleFont(outHandle, false, cfi)) { throw new InvalidOperationException("Unable to get font information."); } return new Size(cfi.dwFontSize.X, cfi.dwFontSize.Y); }
Et les appels, constantes et types WinApi supplémentaires requis:
[DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetConsoleWindow(); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr CreateFile( string lpFileName, int dwDesiredAccess, int dwShareMode, IntPtr lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetCurrentConsoleFont( IntPtr hConsoleOutput, bool bMaximumWindow, [Out][MarshalAs(UnmanagedType.LPStruct)]ConsoleFontInfo lpConsoleCurrentFont); [StructLayout(LayoutKind.Sequential)] internal class ConsoleFontInfo { internal int nFont; internal Coord dwFontSize; } [StructLayout(LayoutKind.Explicit)] internal struct Coord { [FieldOffset(0)] internal short X; [FieldOffset(2)] internal short Y; } private const int GENERIC_READ = unchecked((int)0x80000000); private const int GENERIC_WRITE = 0x40000000; private const int FILE_SHARE_READ = 1; private const int FILE_SHARE_WRITE = 2; private const int INVALID_HANDLE_VALUE = -1; private const int OPEN_EXISTING = 3;
Et le résultat:
[
la source
Button
,TextBox
etc. sont toujours manquants. Mon rêve est de faire un support XAML assez complet avec liaison de données et avec une philosophie de type WPF "incorporer n'importe quoi dans n'importe quoi". Mais je suis très loin de ça ... enfin, en ce moment :)Si vous utilisez ASCII 219 (█) deux fois, vous avez quelque chose comme un pixel (██). Vous êtes désormais limité par la quantité de pixels et le nombre de couleurs dans votre application console.
si vous conservez les paramètres par défaut, vous avez environ 39x39 pixels, si vous en voulez plus, vous pouvez redimensionner votre console avec
Console.WindowHeight = resSize.Height + 1;
etConsole.WindowWidth = resultSize.Width * 2;
vous devez garder le rapport hauteur / largeur de l'image autant que possible, vous n'aurez donc pas 39x39 dans la plupart des cas
Malwyn a affiché une méthode totalement sous - estimés pour convertir
System.Drawing.Color
àSystem.ConsoleColor
donc mon approche serait
using System.Drawing; public static int ToConsoleColor(System.Drawing.Color c) { int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0; index |= (c.R > 64) ? 4 : 0; index |= (c.G > 64) ? 2 : 0; index |= (c.B > 64) ? 1 : 0; return index; } public static void ConsoleWriteImage(Bitmap src) { int min = 39; decimal pct = Math.Min(decimal.Divide(min, src.Width), decimal.Divide(min, src.Height)); Size res = new Size((int)(src.Width * pct), (int)(src.Height * pct)); Bitmap bmpMin = new Bitmap(src, res); for (int i = 0; i < res.Height; i++) { for (int j = 0; j < res.Width; j++) { Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i)); Console.Write("██"); } System.Console.WriteLine(); } }
afin que vous puissiez
ConsoleWriteImage(new Bitmap(@"C:\image.gif"));
exemple d'entrée:
exemple de sortie:
la source
c'était amusant. Merci fubo , j'ai essayé votre solution et j'ai pu augmenter la résolution de l'aperçu de 4 (2x2).
J'ai trouvé que vous pouvez définir la couleur d'arrière-plan pour chaque caractère individuel. Donc, au lieu d'utiliser deux caractères ASCII 219 (█), j'ai utilisé ASCII 223 (▀) deux fois avec des couleurs de premier plan et d'arrière-plan différentes. Cela divise le gros Pixel (██) en 4 sous-pixels comme celui-ci (▀▄).
Dans cet exemple, je place les deux images l'une à côté de l'autre pour que vous puissiez voir facilement la différence:
Voici le code:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; namespace ConsoleWithImage { class Program { public static void ConsoleWriteImage(Bitmap bmpSrc) { int sMax = 39; decimal percent = Math.Min(decimal.Divide(sMax, bmpSrc.Width), decimal.Divide(sMax, bmpSrc.Height)); Size resSize = new Size((int)(bmpSrc.Width * percent), (int)(bmpSrc.Height * percent)); Func<System.Drawing.Color, int> ToConsoleColor = c => { int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0; index |= (c.R > 64) ? 4 : 0; index |= (c.G > 64) ? 2 : 0; index |= (c.B > 64) ? 1 : 0; return index; }; Bitmap bmpMin = new Bitmap(bmpSrc, resSize.Width, resSize.Height); Bitmap bmpMax = new Bitmap(bmpSrc, resSize.Width * 2, resSize.Height * 2); for (int i = 0; i < resSize.Height; i++) { for (int j = 0; j < resSize.Width; j++) { Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMin.GetPixel(j, i)); Console.Write("██"); } Console.BackgroundColor = ConsoleColor.Black; Console.Write(" "); for (int j = 0; j < resSize.Width; j++) { Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2)); Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2, i * 2 + 1)); Console.Write("▀"); Console.ForegroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2)); Console.BackgroundColor = (ConsoleColor)ToConsoleColor(bmpMax.GetPixel(j * 2 + 1, i * 2 + 1)); Console.Write("▀"); } System.Console.WriteLine(); } } static void Main(string[] args) { System.Console.WindowWidth = 170; System.Console.WindowHeight = 40; Bitmap bmpSrc = new Bitmap(@"image.bmp", true); ConsoleWriteImage(bmpSrc); System.Console.ReadLine(); } } }
Pour exécuter l'exemple, le bitmap "image.bmp" doit se trouver dans le même répertoire que l'exécutable. J'ai augmenté la taille de la console, la taille de l'aperçu est toujours de 39 et peut être modifiée à
int sMax = 39;
.La solution de taffer est également très cool. Vous avez tous les deux mon vote favorable ...
la source
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:
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:
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 importantGetConsoleWindow
:[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); }
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 importerSetParent
(etMoveWindow
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éfinirBackgroundImage
l'image souhaitée (faites-le sur unThread
ouTask
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); }
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é.la source
Il n'y a pas de moyen direct. Mais vous pouvez essayer d'utiliser un convertisseur image-ascii-art comme celui-ci
la source
Oui, vous pouvez le faire, si vous étirez un peu la question en ouvrant un à
Form
partir de l'application Console.Voici comment vous pouvez demander à votre application console d'ouvrir un formulaire et d'afficher une image:
System.Drawing
etSystem.Windows.Forms
using System.Windows.Forms; using System.Drawing;
Voir cet article pour savoir comment faire cela !
Maintenant, tout ce dont vous avez besoin pour ajouter quelque chose comme ceci:
Form form1 = new Form(); form1.BackgroundImage = bmp; form1.ShowDialog();
Bien sûr, vous pouvez également utiliser un
PictureBox
..Et vous pouvez utiliser
form1.Show();
pour maintenir la console active pendant que l'aperçu s'affiche.Message original: Bien sûr, vous ne pouvez pas afficher correctement une image dans une fenêtre 25x80; même si vous utilisez une fenêtre plus grande et bloquez les graphiques, ce ne serait pas un aperçu mais un désordre!
Mise à jour: il semble que vous puissiez après tout dessiner une image par GDI sur le formulaire de console; voir la réponse de Taffer!
la source