Отображение изображения в консольном приложении



У меня есть консольное приложение, которое управляет изображений. Теперь мне нужно что-то вроде предварительного просмотра изображений в консольном приложении. Есть ли способ отобразить их в консоли?



вот сравнение текущих ответов на основе символов:



вход:



enter image description here



выход:



enter image description here



enter image description here



enter image description here



enter image description here

1164   7  

7 ответов:

Я дальше играл с кодом от @DieterMeemken. Я уменьшил вдвое вертикальное разрешение и добавил сглаживание через░ ▒ ▓. Слева-Дитер результате Meemken, справа мой. В нижней части находится исходное изображение, измененное до грубого соответствия выходу. Output result Хотя функция преобразования Malwyns впечатляет, она не использует все серые цвета, что жаль.

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

использование:

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

EDIT

расстояние цвета сложная тема (здесь,здесь и ссылки на этих страницах...). Я попытался рассчитать расстояние в YUV, и результаты были гораздо хуже, чем в RGB. Они могли бы быть лучше с Lab и DeltaE, но я этого не пробовал. Расстояние в RGB, кажется, достаточно хорошо. На самом деле результаты очень похожи как для евклидова, так и для Манхэттенского расстояния в цветовом пространстве RGB, поэтому я подозреваю, что слишком мало цветов на выбор.

остальное просто грубая сила сравнить цвета против всех сочетания цветов и узоров (=символы). Я заявил, коэффициент заполнения ░▒▓█ будет 1/4, 2/4, 3/4 и 4/4. В этом случае третий символ по сути повторяет первый. Но если бы соотношения не были такими однородными (зависит от шрифта), результаты могли бы измениться, поэтому я оставил его там для будущих улучшений. Средний цвет символа вычисляется как взвешенное среднее значение foregroudColor и backgroundColor в соответствии с коэффициентом заполнения. Он предполагает линейные цвета, что также является большим упрощением. Так что есть еще место для улучшение.

хотя отображение изображения в консоли не является предполагаемым использованием консоли, вы можете, конечно, взломать вещи, так как окно консоли-это просто окно, как и любые другие окна.

на самом деле, как только я начал разрабатывать библиотеку текстовых элементов управления для консольных приложений с поддержкой графики. Я никогда не заканчивал это, хотя у меня есть рабочее доказательство концепции демо:

Text controls with image

и если вы получите размер шрифта консоли, вы можете поместите изображение очень точно.

вот как вы можете это сделать:

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

вот как вы можете получить текущий размер шрифта консоли:

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

и необходимые дополнительные вызовы 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;

и в итоге:

[Graphics in Console

если вы используете ASCII 219 ( █ ) дважды, у вас есть что-то вроде пикселя ( █ █ ). Теперь вы ограничены количеством пикселей и количеством цветов в вашем консольном приложении.

  • если вы сохраняете настройки по умолчанию у вас есть около 39x39 пикселей, если вы хотите больше, вы можете изменить размер консоли с Console.WindowHeight = resSize.Height + 1;и Console.WindowWidth = resultSize.Width * 2;

  • вы должны сохранить соотношение сторон изображения, насколько это возможно, так что вы не будете иметь 39x39 в большинстве дела

  • Malwyn опубликовал полностью недооцененный метод преобразования System.Drawing.Color до System.ConsoleColor

так что мой подход будет

using System.Drawing;

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);
    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("██");
        }
        System.Console.WriteLine();
    }
}

вы можете

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

пример ввода:

enter image description here

пример вывода:

enter image description here

это было весело. Спасибо фубошань, я попробовал ваше решение и смог увеличить разрешение предварительного просмотра на 4 (2x2).

я обнаружил, что вы можете установить цвет фона для каждого отдельного символа. Таким образом, вместо использования двух символов ASCII 219 ( █ ) я использовал ASCII 223 ( ▀ ) два раза с разными цветами переднего плана и фона. Это делит большой пиксель ( █ █ ) на 4 субпикселя, как это ( ▀ ▄ ).

в этом примере я поставил оба изображения рядом друг с другие, так что вы можете легко увидеть разницу:

enter image description here

вот код:

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

чтобы запустить пример, растровое " изображение.БМП" должно быть в том же каталоге, что и исполняемый файл. Я увеличил размер консоли, размер предварительного просмотра по-прежнему 39 и может быть изменен на int sMax = 39;.

решение от таффер тоже очень здорово. У вас двоих есть мой голос...

я читал о цветовые пространства и лаборатории пространство кажется хорошим вариантом для вас (см. Эти вопросы:найти точное "расстояние" между цветами и алгоритм проверки сходства цветов)

Цитирую Википедию CIELAB страница, преимущества этого цветового пространства:

в отличие от цветовых моделей RGB и CMYK, цвет лаборатории предназначен для приближения человека видение. Он стремится к перцептивной однородности, и его L-компонент близко соответствует человеческому восприятию легкости. Таким образом, он может быть использован для точной коррекции цветового баланса путем изменения выходных кривых в компонентах a и B.

для измерения расстояния между цветами вы можете использовать Дельта Е расстояние.

С этим вы можете приблизиться лучше от Color до ConsoleColor:

во-первых, вы можете определить CieLab класс представьте цвета в этом пространстве:

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

есть два статических метода, один для измерения расстояния с помощью Дельта Е (DeltaE) и другие, чтобы объединить два цвета, указав, сколько каждого цвета (Combine).

и для преобразования из RGB до LAB вы можете использовать следующий метод (от здесь):

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

идея заключается в использовании теневых символов, таких как @AntoninLejsek do ('█', '▓', '▒', '░'), это позволяет вам получите более 16 цветов, сочетающих цвета консоли (используя Combine метод).

здесь мы можем сделать некоторые улучшения, предварительно вычисляя цвета для использования:

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

еще одним улучшением может быть доступ непосредственно к данным изображения с помощью LockBits вместо GetPixel.

обновление: если изображение имеет части с таким же цветом, то вы можете быстро пройти вверх значительно отростчатый рисуя кусок характеров имея такие же цвета, вместо отдельные символы:

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

и, наконец, вызвать DrawImage вот так:

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

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

}

Результате Изображения:

Console1

Console2



следующие решения не основаны на символах, но предоставляют полные подробные изображения


вы можете нарисовать над любым окном, используя его проводник создать

прямого пути нет. Но вы можете попробовать использовать конвертер изображений в ascii-art, например этот

Да, вы можете это сделать, если немного растянете вопрос, открыв Form из консольного приложения.

вот как вы можете иметь вас консольное приложение открыть форму и отобразить изображение:

  • включите эти две ссылки в свой проект:System.Drawing и System.Windows.Forms
  • включите также два пространства имен:

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

посмотреть этот пост о том, как это сделать!

теперь все, что вам нужно, чтобы добавить что-то вроде этого:

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

конечно, вы также можете использовать PictureBox..

и вы можете использовать form1.Show(); чтобы сохранить консоль живой во время предварительного просмотра..

исходный пост: конечно, вы не можете правильно отображать изображение внутри окно 25x80; даже если вы используете более крупное окно и блокируете графику, это будет не предварительный просмотр, а беспорядок!

обновление: похоже, что вы можете после все GDI-нарисуйте изображение на форме консоли; см. ответ таффера!

Comments

    Ничего не найдено.