98 votes

Afficher une image dans une application console

J'ai une application console qui gère des images. Maintenant j'ai besoin d'avoir une sorte d'aperçu des images dans l'application console.

Y a-t-il un moyen de les afficher dans la console ?

Voici une comparaison des réponses actuelles basées sur les caractères :

Entrée :

enter image description here

Sortie :

enter image description here

enter image description here

enter image description here

enter image description here

0 votes

Dans la console, comme dans la fenêtre de la console? Non. Cependant, vous pourriez démarrer un dialogue/fenêtre séparé(e).

0 votes

Les applications console sont principalement utilisées pour les applications texte uniquement. Il n'y a pas moyen d'afficher une image. Vous pourriez lancer une autre application qui affiche l'image. Cette autre application aurait très probablement besoin de prendre en charge une option de ligne de commande pour lui transmettre une image.

0 votes

Pourquoi utilisez-vous une application console? Où s'exécute-t-elle? vous pouvez toujours démarrer un processus pour ouvrir le visualiseur d'images par défaut ou simplement une application winforms, etc., de votre propre création...

109voto

taffer Points 1433

Bien que montrer une image dans une console ne soit pas l'usage prévu de la console, vous pouvez sûrement pirater les choses, car la fenêtre de la console est juste 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 prise en charge des graphiques. Je n'ai jamais terminé cela, bien que j'aie une démonstration de preuve de concept fonctionnelle:

Contrôles de texte avec image

Et si vous obtenez la taille de la police de la console, vous pouvez placer l'image de manière très précise.

Voici comment vous pouvez le faire:

static void Main(string[] args)
{
    Console.WriteLine("Graphiques dans la fenêtre de la console!");

    Point location = new Point(10, 10);
    Taille imageSize = new Taille(20, 10); // taille d'image souhaitée en caractères

    // dessiner des espaces réservés
    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), @"Images d'exemple\tulips.jpg");
    using (Graphics g = Graphics.FromHwnd(GetConsoleWindow()))
    {
        using (Image image = Image.FromFile(path))
        {
            Taille fontSize = GetConsoleFontSize();

            // traduire les positions de caractères en 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 vous pouvez obtenir la taille de la police de la console actuelle:

private static Taille GetConsoleFontSize()
{
    // obtenir le handle du tampon de sortie de la console
    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("Impossible d'ouvrir CONOUT$", errorCode);
    }

    InfoPoliceConsole cfi = new InfoPoliceConsole();
    if (!GetCurrentConsoleFont(outHandle, false, cfi))
    {
        throw new InvalidOperationException("Impossible d'obtenir les informations sur la police.");
    }

    return new Taille(cfi.dwFontSize.X, cfi.dwFontSize.Y);            
}

Et les appels WinApi supplémentaires, constantes et types 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)]InfoPoliceConsole lpConsoleCurrentFont);

[StructLayout(LayoutKind.Sequential)]
internal class InfoPoliceConsole
{
    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:

[Graphiques dans la console

3 votes

Waouh, c'est vraiment intéressant ! Pourriez-vous élaborer un peu sur ce que vous voulez dire par Je n'ai jamais terminé cela, bien que j'ai une démo de principe fonctionnelle ? Des inconvénients ou juste un manque de finition..?

0 votes

Cela signifie que le projet est très incomplet. J'ai réalisé l'architecture de base, l'environnement orienté objet basé sur les événements, le support de la souris, la pompe à messages, etc. Mais même les contrôles les plus fondamentaux tels que Bouton, `TextBox` etc sont encore manquants. Mon rêve est de créer un support XAML assez complet avec la liaison de données et une philosophie similaire à celle de WPF "intégrez n'importe quoi dans n'importe quoi". Mais j'en suis très loin... enfin, pour le moment :)

4 votes

D'accord, je vois, mais le code semble qu'il peut être utilisé pour n'importe quel projet moins complet, moins ambitieux, oui?

62voto

Antonín Lejsek Points 1288

J'ai joué davantage avec le code de @DieterMeemken. J'ai divisé la résolution verticale par deux et ajouté du tramage via . À gauche se trouve le résultat de Dieter Meemken, à droite le mien. En bas se trouve l'image originale redimensionnée pour correspondre approximativement à la sortie. Résultat de la sortie Bien que la fonction de conversion de Malwyn soit 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();
}

utilisation:

Bitmap bmpSrc = new Bitmap(@"HuwnC.gif", true);    
ConsoleWriteImage(bmpSrc);

MODIFIER

La distance de couleur est un sujet complexe (ici, ici et les liens sur ces pages...). J'ai essayé de calculer la distance en YUV et les résultats étaient plutôt pires qu'en RGB. Ils pourraient être meilleurs avec Lab et DeltaE, mais je n'ai pas essayé. La distance en RGB semble être assez bonne. En fait, les résultats sont très similaires pour la distance euclidienne et de Manhattan dans l'espace des couleurs RGB, donc je soupçonne qu'il y a juste trop peu de couleurs à choisir.

Le reste n'est qu'une comparaison brutale des couleurs avec toutes les combinaisons de couleurs et de motifs (=symboles). J'ai défini un ratio de remplissage 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 (cela dépend de la police), les résultats pourraient changer, donc je l'ai laissé là pour de futures améliorations. La couleur moyenne du symbole est calculée comme une moyenne pondérée de la couleur de premier plan et de la couleur de fond selon le ratio de remplissage. Cela suppose des couleurs linéaires, ce qui est également une grande simplification. Il reste donc encore de la place pour des améliorations.

0 votes

Merci @fubo. Au fait, j'ai expérimenté avec les RGB corrigés gamma et Lab et les deux étaient une amélioration. Cependant, le taux de remplissage devait être ajusté pour correspondre à la police utilisée et cela n'a pas du tout fonctionné pour les polices TrueType. Donc, cela ne serait plus une solution taille unique pour tous.

58voto

fubo Points 647

Si vous utilisez le code ASCII 219 (░) deux fois, vous obtenez quelque chose de semblable à un pixel (▓). Maintenant, vous êtes limité par le nombre de pixels et le nombre de couleurs dans votre application console.

  • si vous gardez les paramètres par défaut, vous avez environ 39x39 pixels, si vous voulez en avoir plus, vous pouvez redimensionner votre console avec Console.WindowHeight = tailleRésultat.Height + 1; et Console.WindowWidth = tailleRésultat.Width * 2;

  • vous devez conserver le rapport d'aspect de l'image autant que possible, il est donc peu probable que vous ayez 39x39 pixels dans la plupart des cas

  • Malwyn a posté une méthode totalement sous-estimée pour convertir System.Drawing.Color en System.ConsoleColor

donc, ma méthode 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();
    }
}

donc vous pouvez

ConsoleWriteImage(new Bitmap(@"C:\image.gif"));

entrée d'exemple:

description de l'image

sortie d'exemple:

description de l'image

7 votes

@willywonka_dailyblah - C'est le tentacule violet de Day of the Tentacle. Pas Doom

0 votes

@Blaatz0r je veux dire des graphismes à la Doom... je suis nouveau dans le domaine, je ne connais que Doom.

3 votes

Tout est pardonné :-p. Si jamais tu as l'occasion, essaie Day of the Tentacle, c'est un super jeu, vieux mais génial.

38voto

Dieter Meemken Points 830

Ce fut 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 découvert que vous pouvez définir la couleur d'arrière-plan pour chaque caractère individuel. Ainsi, au lieu d'utiliser deux caractères ASCII 219 ( ), j'ai utilisé deux fois ASCII 223 ( ) avec des couleurs de premier plan et d'arrière-plan différentes. Cela divise le gros pixel ( ) en 4 sous-pixels comme ceci ( ).

Dans cet exemple, j'ai mis les deux images côte à côte, donc vous pouvez facilement voir la différence :

entrer la description de l'image ici

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 pourcentage = Math.Min(decimal.Divide(sMax, bmpSrc.Width), decimal.Divide(sMax, bmpSrc.Height));
        Size resSize = new Size((int)(bmpSrc.Width * pourcentage), (int)(bmpSrc.Height * pourcentage));
        Func 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 être 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...

4voto

DarkWanderer Points 3559

Il n'y a pas de méthode directe. Mais vous pouvez essayer d'utiliser un convertisseur d'image en art ASCII comme celui-ci

0 votes

:-) Cependant, veuillez noter que les capacités de couleur de la console (fenêtre) sont également assez limitées. Donc les effets de "fondu", etc. ne sont même pas possibles.

1 votes

Eh bien, cela correspond à la résolution :P

1 votes

@Christian.K La réponse d'Antonín Lejsek rend le fondu possible

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X