Zeigen Sie ein Bild in einer Konsolenanwendung an


85

Ich habe eine Konsolenanwendung, die Bilder verwaltet. Jetzt brauche ich so etwas wie eine Vorschau der Bilder in der Konsolenanwendung. Gibt es eine Möglichkeit, sie in der Konsole anzuzeigen?

Hier ist ein Vergleich der aktuellen zeichenbasierten Antworten:

Eingang:

Geben Sie hier die Bildbeschreibung ein

Ausgabe:

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein


In der Konsole wie im Konsolenfenster? Sie können jedoch einen separaten Dialog / ein separates Fenster starten.
Christian.K

Konsolenanwendungen werden hauptsächlich für Nur-Text-Anwendungen verwendet. Es gibt keine Möglichkeit, ein Bild anzuzeigen. Sie können eine andere Anwendung starten, die das Bild anzeigt. Diese andere Anwendung müsste höchstwahrscheinlich eine Befehlszeilenoption unterstützen, um ein Image an sie zu übergeben.
Lindos Pechos

Warum verwenden Sie eine Konsolenanwendung? Wo läuft es? Sie können jederzeit einen Prozess starten, um den Standard-Bildbetrachter oder eine eigene Winforms-App usw. aufzurufen.
TaW

1
Ich brauche eine Verbesserung des Codes von Antonín Lejsek (was großartig ist). Es gibt ein paar Farbfehler und mit verbesserter Leistung könnte ich auch animierte
Gifs

Antworten:


54

Ich habe weiter mit Code von @DieterMeemken gespielt. Ich habe die vertikale Auflösung halbiert und Dithering über ░▒▓ hinzugefügt. Links ist Dieter Meemken Ergebnis, rechts mein. Auf der Unterseite befindet sich das Originalbild, dessen Größe der Ausgabe entspricht. Ausgabeergebnis Während Malwyns Konvertierungsfunktion beeindruckend ist, werden nicht alle grauen Farben verwendet, was schade ist.

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();
}

Verwendung:

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

BEARBEITEN

Farbabstand ist ein komplexes Thema ( hier , hier und Links auf diesen Seiten ...). Ich habe versucht, die Entfernung in YUV zu berechnen, und die Ergebnisse waren eher schlechter als in RGB. Mit Lab und DeltaE könnten sie besser sein, aber das habe ich nicht versucht. Die Entfernung in RGB scheint gut genug zu sein. Tatsächlich sind die Ergebnisse sowohl für die euklidische als auch für die Manhattan-Entfernung im RGB-Farbraum sehr ähnlich. Ich vermute, dass es einfach zu wenige Farben gibt, aus denen Sie auswählen können.

Der Rest ist nur ein Brute-Force-Vergleich der Farbe mit allen Kombinationen von Farben und Mustern (= Symbole). Ich habe das Füllverhältnis für ░▒▓█ mit 1/4, 2/4, 3/4 und 4/4 angegeben. In diesem Fall ist das dritte Symbol tatsächlich redundant zum ersten. Wenn die Verhältnisse jedoch nicht so einheitlich wären (abhängig von der Schriftart), könnten sich die Ergebnisse ändern, sodass ich sie für zukünftige Verbesserungen dort belassen habe. Die durchschnittliche Farbe des Symbols wird als gewogener Durchschnitt von foregroudColor und backgroundColor gemäß dem Füllverhältnis berechnet. Es werden lineare Farben angenommen, was auch eine große Vereinfachung darstellt. Es gibt also noch Raum für Verbesserungen.


Danke @fubo. Übrigens habe ich mit gammakorrigiertem RGB und Lab experimentiert und beide waren verbessert. Das Füllverhältnis musste jedoch so eingestellt werden, dass es mit der verwendeten Schriftart übereinstimmt, und es funktionierte überhaupt nicht für TrueType-Schriftarten. Es wäre also nicht mehr eine Einheitsgröße.
Antonín Lejsek

88

Obwohl das Anzeigen eines Bildes in einer Konsole nicht die beabsichtigte Verwendung der Konsole ist, können Sie die Dinge sicher hacken, da das Konsolenfenster wie jedes andere Fenster nur ein Fenster ist.

Eigentlich habe ich einmal angefangen, eine Textsteuerungsbibliothek für Konsolenanwendungen mit Grafikunterstützung zu entwickeln. Ich habe das nie beendet, obwohl ich eine funktionierende Proof-of-Concept-Demo habe:

Textsteuerelemente mit Bild

Und wenn Sie die Schriftgröße der Konsole erhalten, können Sie das Bild sehr genau platzieren.

So können Sie es machen:

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);
        }
    }
}

So erhalten Sie die aktuelle Schriftgröße der Konsole:

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);            
}

Und die erforderlichen zusätzlichen WinApi-Aufrufe, -Konstanten und -Typen:

[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;

Und das Ergebnis:

[Grafiken in der Konsole


2
Wow, das ist wirklich interessant! Könnten Sie bitte etwas näher erläutern, was Sie damit meinen, dass ich das nie fertiggestellt habe, obwohl ich eine funktionierende Proof-of-Concept-Demo habe ? Irgendwelche Nachteile oder nur die fehlende Politur ..?
TaW

Dies bedeutet, dass das Projekt sehr unvollständig ist. Ich habe die grundlegende Architektur, die Basis ereignisgesteuerte OO - Umgebung, Maus - Unterstützung, die Nachricht Pumpe usw. Aber selbst die grundlegendsten Kontrollen wie Button, TextBoxusw. fehlen noch. Mein Traum ist es, eine ziemlich vollständige XAML-Unterstützung mit Datenbindung und mit WPF-ähnlicher Philosophie "Alles in irgendetwas einbetten" zu schaffen. Aber ich bin sehr weit davon entfernt ... nun, in diesem Moment :)
György Kőszeg

3
OK, ich verstehe, aber der Code scheint für jedes weniger vollständige, weniger ehrgeizige Projekt verwendet werden zu können, ja?
TaW

1
Nun, in seiner jetzigen Form ... nicht wirklich. Aber ich habe vor, es auf GitHub zu veröffentlichen, sobald ich es etwas stabiler und kohärenter gemacht habe.
György Kőszeg

Das sieht wirklich cool aus, bis auf die Notwendigkeit, nicht verwaltete DLLs zu verwenden. Wie können wir auch die Entwicklung der Bibliotheken verfolgen?
Ein Ohrwurm

56

Wenn Sie ASCII 219 (█) zweimal verwenden, haben Sie so etwas wie ein Pixel (██). Jetzt sind Sie durch die Anzahl der Pixel und die Anzahl der Farben in Ihrer Konsolenanwendung eingeschränkt.

  • Wenn Sie die Standardeinstellungen beibehalten, haben Sie ungefähr 39 x 39 Pixel. Wenn Sie mehr möchten, können Sie die Größe Ihrer Konsole mit Console.WindowHeight = resSize.Height + 1;und ändern Console.WindowWidth = resultSize.Width * 2;

  • Sie müssen das Seitenverhältnis des Bildes so weit wie möglich beibehalten, damit Sie in den meisten Fällen nicht 39x39 haben

  • Malwyn hat eine völlig unterschätzte Methode zu konvertieren System.Drawing.ColorzuSystem.ConsoleColor

so wäre mein Ansatz

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();
    }
}

also kannst du

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

Beispieleingabe:

Geben Sie hier die Bildbeschreibung ein

Beispielausgabe:

Geben Sie hier die Bildbeschreibung ein


7
@willywonka_dailyblah - Es ist das lila Tentakel vom Tag des Tentakels. Not Doom
Blaatz0r

@ Blaatz0r Ich meine Doom-ähnliche Grafiken ... imma neues Kind auf dem Block Ich kenne nur Doom

3
Alles ist vergeben :-p. Wenn Sie jemals die Chance haben, versuchen Sie es mit Day, wenn das Tentakel ein großartiges Spiel ist, das alt, aber großartig ist.
Blaatz0r

37

das hat Spaß gemacht. Danke fubo , ich habe deine Lösung ausprobiert und konnte die Auflösung der Vorschau um 4 (2x2) erhöhen.

Ich habe festgestellt, dass Sie die Hintergrundfarbe für jedes einzelne Zeichen einstellen können. Anstatt zwei ASCII 219 (█) Zeichen zu verwenden, habe ich ASCII 223 (▀) zweimal mit unterschiedlichen Vordergrund- und Hintergrundfarben verwendet. Das teilt das große Pixel (██) in 4 Subpixel wie dieses (▀▄).

In diesem Beispiel habe ich beide Bilder nebeneinander platziert, damit Sie den Unterschied leicht erkennen können:

Geben Sie hier die Bildbeschreibung ein

Hier ist der 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();
    }
  }
}

Um das Beispiel auszuführen, muss sich die Bitmap "image.bmp" im selben Verzeichnis wie die ausführbare Datei befinden. Ich habe die Konsole vergrößert, die Vorschau ist immer noch 39 und kann unter geändert werdenint sMax = 39; .

Die Lösung von Taffer ist auch sehr cool. Ihr zwei habt meine Gegenstimme ...


23

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 Colorbis annähernConsoleColor :

Zunächst können Sie eine CieLabKlasse 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 RGBnach können LABSie 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 CombineMethode).

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, LockBitsanstatt 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 DrawImageso an:

static void Main(string[] args)
{
    ComputeColors();

    Bitmap image = new Bitmap("image.jpg", true);
    DrawImage(image);

}

Ergebnis Bilder:

Konsole1

Console2



Die folgenden Lösungen basieren nicht auf Zeichen, sondern bieten detaillierte Bilder


Sie können mit dem Handler über jedes Fenster zeichnen , um ein GraphicsObjekt 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 GraphicsObjekt, 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);
}

Version 1

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 MoveWindowdas 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 Formund BackgroundImageauf das gewünschte Image setzen (tun Sie dies auf einem Threadoder um Taskzu 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);
}

Version 2

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 BackgroundImageEigenschaft ändern .


Vielen Dank für Ihre Mühe, aber Ihr Ansatz ist 6x langsamer als die Lösung von Antonín Lejsek. Auf jeden Fall sehr interessantes Rundenergebnis.
Byyo

4

Es gibt keinen direkten Weg. Sie können jedoch versuchen, einen Bild-zu-ASCII-Kunst-Konverter wie diesen zu verwenden


:-) Beachten Sie jedoch, dass die Farbfunktionen der Konsole (des Fensters) ebenfalls ziemlich eingeschränkt sind. "Fading" -Effekte usw. sind also nicht einmal möglich.
Christian.K

1
Nun, es entspricht der Auflösung: P
DarkWanderer

1
@ Christian.K Antonín Lejseks Antwort macht das Ausbleichen möglich
Byyo

1

Ja, Sie können es tun, wenn Sie die Frage ein wenig erweitern, indem Sie a öffnen Form in der Konsolenanwendung .

So können Sie Ihre Konsolenanwendung ein Formular öffnen und ein Bild anzeigen lassen:

  • Nehmen Sie diese beiden Referenzen in Ihr Projekt auf: System.Drawing undSystem.Windows.Forms
  • Fügen Sie auch die beiden Namespaces hinzu:

using System.Windows.Forms;
using System.Drawing;

In diesem Beitrag erfahren Sie, wie das geht !

Jetzt brauchen Sie alles, um so etwas hinzuzufügen:

Form form1 = new Form();
form1.BackgroundImage = bmp;
form1.ShowDialog();

Natürlich können Sie auch eine PictureBox..

Und Sie können verwenden form1.Show();, um die Konsole am Leben zu halten, während die Vorschau zeigt.

Originalbeitrag: Natürlich können Sie ein Bild in einem 25x80-Fenster nicht richtig anzeigen . Selbst wenn Sie ein größeres Fenster verwenden und Grafiken blockieren, wäre dies keine Vorschau, sondern ein Durcheinander!

Update: Sieht so aus, als könnten Sie mit GDI ein Bild auf das Konsolenformular zeichnen. siehe Taffers Antwort!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.