Wyświetl obraz w aplikacji konsoli

85

Mam aplikację konsoli, która zarządza obrazami. Teraz potrzebuję czegoś w rodzaju podglądu obrazów w aplikacji konsoli. Czy jest sposób, aby wyświetlić je w konsoli?

Oto porównanie aktualnych odpowiedzi opartych na znakach:

Wejście:

wprowadź opis obrazu tutaj

Wynik:

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

wprowadź opis obrazu tutaj

Byyo
źródło
W konsoli, jak w oknie konsoli? Nie. Możesz jednak uruchomić osobne okno / okno.
Christian.K
Aplikacje konsolowe są używane głównie w aplikacjach tekstowych. Nie ma możliwości wyświetlenia obrazu. Możesz uruchomić inną aplikację, która wyświetla obraz. Ta inna aplikacja najprawdopodobniej musiałaby obsługiwać opcję wiersza poleceń, aby przekazać do niej obraz.
Lindos Pechos
Dlaczego używasz aplikacji konsolowej? Gdzie to biegnie? zawsze możesz rozpocząć proces, aby wywołać domyślną przeglądarkę zdjęć lub po prostu własną aplikację winforms itp.
TaW
1
Potrzebuję ulepszenia kodu Antonína Lejska (co jest świetne). Jest kilka niedopasowań kolorów i przy lepszej wydajności mogę również wyświetlać animowane gify
Byyo

Odpowiedzi:

55

Dalej bawiłem się kodem z @DieterMeemken. Zmniejszyłem o połowę rozdzielczość pionową i dodałem dithering przez ░▒▓. Po lewej wynik Dietera Meemkena, po prawej mój. U dołu znajduje się oryginalny obraz, którego rozmiar został zmieniony w celu zgrubnego dopasowania do obrazu wyjściowego. Wynik wyjściowy O ile funkcja konwersji Malwynsa robi wrażenie, to nie wykorzystuje wszystkich szarych kolorów, szkoda.

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

stosowanie:

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

EDYTOWAĆ

Odległość kolorów to złożony temat ( tutaj , tutaj i linki na tych stronach ...). Próbowałem obliczyć odległość w YUV i wyniki były raczej gorsze niż w RGB. Mogłyby być lepsze z Lab i DeltaE, ale nie próbowałem tego. Odległość w RGB wydaje się wystarczająca. W rzeczywistości wyniki są bardzo podobne dla odległości euklidesowej i manhattanu w przestrzeni kolorów RGB, więc podejrzewam, że jest zbyt mało kolorów do wyboru.

Reszta to po prostu brutalne porównanie koloru ze wszystkimi kombinacjami kolorów i wzorów (= symboli). Podałem, że współczynnik wypełnienia ░▒▓█ wynosi 1/4, 2/4, 3/4 i 4/4. W takim przypadku trzeci symbol jest w rzeczywistości zbędny w stosunku do pierwszego. Ale gdyby proporcje nie były tak jednolite (w zależności od czcionki), wyniki mogłyby się zmienić, więc zostawiłem to na przyszłe ulepszenia. Średni kolor symbolu jest obliczany jako średnia ważona z foregroudColor i backgroundColor zgodnie ze współczynnikiem wypełnienia. Przyjmuje liniowe kolory, co jest również dużym uproszczeniem. Jest więc jeszcze miejsce na ulepszenia.

Antonín Lejsek
źródło
Dziękuję @fubo. Przy okazji eksperymentowałem z RGB i Lab z korekcją gamma i oba były ulepszone. Ale współczynnik wypełnienia musiał być ustawiony tak, aby pasował do używanej czcionki, a to w ogóle nie działało w przypadku czcionek TrueType. Więc nie byłby to już jeden rozmiar dla wszystkich.
Antonín Lejsek
88

Chociaż wyświetlanie obrazu w konsoli nie jest zamierzonym użyciem konsoli, z pewnością możesz zhakować rzeczy, ponieważ okno konsoli jest tylko oknem, jak każde inne okno.

Właściwie to kiedyś zacząłem tworzyć bibliotekę kontrolek tekstu dla aplikacji konsolowych z obsługą grafiki. Nigdy tego nie skończyłem, chociaż mam działające demo typu proof-of-concept:

Kontrolki tekstu z obrazem

A jeśli uzyskasz rozmiar czcionki konsoli, możesz bardzo precyzyjnie umieścić obraz.

Oto jak możesz to zrobić:

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

Oto jak możesz uzyskać aktualny rozmiar czcionki konsoli:

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

Oraz wymagane dodatkowe wywołania, stałe i typy WinApi:

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

A wynik:

[Grafika w konsoli

György Kőszeg
źródło
2
Wow, to naprawdę interesujące! Czy mógłbyś wyjaśnić trochę, co masz na myśli, mówiąc, że nigdy tego nie skończyłem, chociaż mam działające demo potwierdzające koncepcję ? Jakieś wady czy po prostu brakujący lakier ..?
Dzień
Oznacza to, że projekt jest bardzo niekompletny. Zrobiłem podstawową architekturę, podstawa zdarzeniami środowiska OO, obsługę myszy, pompę wiadomość, itp ale nawet najbardziej podstawowe elementy sterujące, takie jak Button, TextBoxetc nadal brakuje. Moim marzeniem jest stworzenie w miarę pełnej obsługi języka XAML z wiązaniem danych i podobną do WPF filozofią „osadzania wszystkiego w czymkolwiek”. Ale jestem od tego bardzo daleki ... cóż, w tej chwili :)
György Kőszeg
3
OK, rozumiem, ale wygląda na to, że kod może być użyty do mniej kompletnego, mniej ambitnego projektu, tak?
Dzień
1
Cóż, w obecnej formie ... nie do końca. Ale planuję opublikować go na GitHubie, gdy tylko sprawię, że będzie stabilny i spójny.
György Kőszeg
Wygląda to naprawdę fajnie, z wyjątkiem konieczności używania niezarządzanych bibliotek DLL. Ponadto, jak możemy śledzić rozwój bibliotek?
skórka
56

Jeśli użyjesz ASCII 219 (█) dwa razy, otrzymasz coś w rodzaju piksela (██). Teraz ogranicza Cię liczba pikseli i liczba kolorów w aplikacji konsolowej.

  • jeśli zachowasz ustawienia domyślne, masz około 39x39 pikseli, jeśli chcesz więcej, możesz zmienić rozmiar konsoli za pomocą Console.WindowHeight = resSize.Height + 1;iConsole.WindowWidth = resultSize.Width * 2;

  • musisz zachować jak największy współczynnik proporcji obrazu, więc w większości przypadków nie będziesz mieć 39 x 39

  • Malwyn opublikował całkowicie niedocenianą metodę konwersji System.Drawing.ColornaSystem.ConsoleColor

więc moje podejście byłoby

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

więc możesz

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

przykładowe dane wejściowe:

wprowadź opis obrazu tutaj

przykładowe wyjście:

wprowadź opis obrazu tutaj

fubo
źródło
7
@willywonka_dailyblah - To fioletowa macka z Day of the Tentacle. Not Doom
Blaatz0r
@ Blaatz0r mam na myśli grafikę w stylu Dooma ... imma nowy dzieciak na tym bloku Znam tylko Dooma
3
Wszystko jest wybaczone :-p. Jeśli kiedykolwiek będziesz miał okazję spróbować Day, jeśli macka to świetna gra, stara, ale świetna.
Blaatz0r
37

to było zabawne. Dzięki fubo , wypróbowałem Twoje rozwiązanie i udało mi się zwiększyć rozdzielczość podglądu o 4 (2x2).

Odkryłem, że możesz ustawić kolor tła dla każdego pojedynczego znaku. Tak więc, zamiast używać dwóch znaków ASCII 219 (█), użyłem dwa razy ASCII 223 (▀) z różnymi kolorami pierwszego planu i tła. To dzieli duży piksel (██) na 4 takie subpiksele (▀▄).

W tym przykładzie umieściłem oba obrazy obok siebie, aby łatwo było zobaczyć różnicę:

wprowadź opis obrazu tutaj

Oto kod:

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

Aby uruchomić przykład, mapa bitowa „image.bmp” musi znajdować się w tym samym katalogu, co plik wykonywalny. Zwiększyłem rozmiar konsoli, rozmiar podglądu nadal wynosi 39 i można go zmienić na int sMax = 39;.

Rozwiązanie od taffer jest również bardzo fajne. Wy dwoje macie moje głosy za ...

Dieter Meemken
źródło
23

Czytałem o przestrzeniach kolorów, a przestrzeń LAB wydaje się być dla Ciebie dobrym rozwiązaniem (zobacz to pytanie: Znajdowanie dokładnej „odległości” między kolorami i Algorytm, aby sprawdzić podobieństwo kolorów )

Cytując stronę Wikipedii CIELAB , zalety tej przestrzeni kolorów to:

W przeciwieństwie do modeli kolorów RGB i CMYK, kolory Lab są zaprojektowane tak, aby przybliżać ludzkie widzenie. Dąży do jednorodności percepcji, a jej składnik L ściśle pasuje do ludzkiego postrzegania lekkości. W związku z tym może być używany do dokładnych korekt balansu kolorów poprzez modyfikację krzywych wyjściowych w składowych a i b.

Aby zmierzyć odległość między kolorami, możesz użyć odległości Delta E.

Dzięki temu możesz lepiej przybliżać od Colordo ConsoleColor:

Po pierwsze, możesz zdefiniować CieLabklasę reprezentującą kolory w tej przestrzeni:

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

Istnieją dwie statyczne metody, jedna do pomiaru odległości za pomocą Delta E ( DeltaE), a druga do łączenia dwóch kolorów, określając ilość każdego koloru ( Combine).

A do przekształcenia z RGBdo LABmożesz użyć następującej metody ( stąd ):

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

Pomysł polega na użyciu znaków cieni, takich jak @AntoninLejsek do ('█', '▓', '▒', '░'), co pozwala uzyskać więcej niż 16 kolorów łączących kolory konsoli ( Combinemetodą).

Tutaj możemy wprowadzić pewne ulepszenia, wstępnie obliczając kolory do użycia:

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

Innym ulepszeniem może być bezpośredni dostęp do danych obrazu przy użyciu LockBitszamiast używania GetPixel.

AKTUALIZACJA : Jeśli obraz ma części o tym samym kolorze, możesz znacznie przyspieszyć proces rysowania fragmentów znaków o tych samych kolorach zamiast pojedynczych znaków:

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

Na koniec zadzwoń w ten DrawImagesposób:

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

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

}

Obrazy wyników:

Konsola 1

Konsola 2



Poniższe rozwiązania nie są oparte na znakach, ale zapewniają pełne szczegółowe obrazy


Możesz rysować po dowolnym oknie, używając jego obsługi, aby utworzyć Graphicsobiekt. Aby uzyskać obsługę aplikacji konsolowej, możesz to zrobić importując GetConsoleWindow:

[DllImport("kernel32.dll", EntryPoint = "GetConsoleWindow", SetLastError = true)]
private static extern IntPtr GetConsoleHandle();

Następnie utwórz grafikę za pomocą handlera (używając Graphics.FromHwnd) i narysuj obraz używając metod w Graphicsobiekcie, na przykład:

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

Wersja 1

Wygląda dobrze, ale jeśli rozmiar konsoli zostanie zmieniony lub przewinięty, obraz znika, ponieważ okna są odświeżane (być może w twoim przypadku możliwe jest zaimplementowanie jakiegoś mechanizmu do przerysowania obrazu).


Innym rozwiązaniem jest osadzenie Formmetody window ( ) w aplikacji konsoli. Aby to zrobić, musisz zaimportować SetParent(i MoveWindowprzenieść okno wewnątrz konsoli):

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

Następnie wystarczy utworzyć Formi ustawić BackgroundImagewłaściwość na żądany obraz (zrób to na Threadlub, Taskaby uniknąć blokowania konsoli):

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

Wersja 2

Oczywiście możesz ustawić FormBorderStyle = FormBorderStyle.Noneukrywanie granic okien (prawy obraz)

W takim przypadku możesz zmienić rozmiar konsoli, a obraz / okno nadal tam będzie.

Jedną z korzyści tego podejścia jest to, że możesz zlokalizować okno w wybranym miejscu i zmienić obraz w dowolnym momencie, po prostu zmieniając BackgroundImagewłaściwość.

Arturo Menchaca
źródło
Dziękuję za wysiłek, ale podejście jest 6x wolniejsze niż rozwiązanie Antonína Lejska. W każdym razie bardzo ciekawy wynik na okrążeniu.
Byyo
4

Nie ma bezpośredniego sposobu. Ale możesz spróbować użyć konwertera obrazu na ascii-art, takiego jak ten

DarkWanderer
źródło
:-) jednak zwróć uwagę, że możliwości kolorów konsoli (okna) są również dość ograniczone. Zatem efekty „zanikania” itp. Nie są nawet możliwe.
Christian.K
1
Cóż, pasuje do rozdzielczości: P
DarkWanderer
1
@ Christian.K Odpowiedź Antonína Lejseka umożliwia zanikanie
Byyo
1

Tak, możesz to zrobić, jeśli trochę rozciągniesz pytanie, otwierając Formz poziomu aplikacji Console.

Oto, jak możesz otworzyć formularz w aplikacji konsolowej i wyświetlić obraz:

  • uwzględnij w swoim projekcie te dwa odniesienia: System.DrawingiSystem.Windows.Forms
  • uwzględnij również dwie przestrzenie nazw:

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

Zobacz ten post, jak to zrobić !

Teraz wszystko, czego potrzebujesz, aby dodać coś takiego:

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

Oczywiście możesz także użyć PictureBox...

Możesz też użyć, form1.Show();aby utrzymać konsolę przy życiu podczas wyświetlania podglądu.

Oryginalny post: Oczywiście nie możesz poprawnie wyświetlić obrazu w oknie 25x80; nawet jeśli używasz większego okna i blokowej grafiki, nie byłby to podgląd, ale bałagan!

Aktualizacja: Wygląda na to, że możesz mimo wszystko GDI narysować obraz na formularzu konsoli; zobacz odpowiedź Taffera!

Wyprawiać
źródło