แสดงภาพในแอปพลิเคชันคอนโซล


86

ฉันมีแอปพลิเคชันคอนโซลที่จัดการรูปภาพ ตอนนี้ฉันต้องการบางอย่างเช่นการดูตัวอย่างรูปภาพภายในแอปพลิเคชันคอนโซล มีวิธีแสดงในคอนโซลหรือไม่?

นี่คือการเปรียบเทียบคำตอบตามอักขระปัจจุบัน:

อินพุต:

ใส่คำอธิบายภาพที่นี่

เอาท์พุต:

ใส่คำอธิบายภาพที่นี่

ใส่คำอธิบายภาพที่นี่

ใส่คำอธิบายภาพที่นี่

ใส่คำอธิบายภาพที่นี่


ในคอนโซลเช่นเดียวกับในหน้าต่างคอนโซล? ไม่ได้อย่างไรก็ตามคุณสามารถเริ่มกล่องโต้ตอบ / หน้าต่างแยกกันได้
Christian.K

แอปพลิเคชันคอนโซลส่วนใหญ่ใช้สำหรับแอปพลิเคชันแบบข้อความเท่านั้น ไม่มีวิธีแสดงภาพ คุณสามารถเปิดแอปพลิเคชั่นอื่นที่แสดงภาพ แอปพลิเคชันอื่นนี้มักจะต้องสนับสนุนตัวเลือกบรรทัดคำสั่งเพื่อส่งภาพไปยังแอปพลิเคชัน
Lindos Pechos

ทำไมคุณถึงใช้แอปพลิเคชันคอนโซล มันวิ่งไปไหน? คุณสามารถเริ่มกระบวนการเพื่อเรียกใช้โปรแกรมดูรูปภาพเริ่มต้นหรือแอป winforms และอื่น ๆ ของคุณเองได้ตลอดเวลา ..
TaW

1
ฉันต้องการการปรับปรุงรหัสของAntonín Lejsek (ซึ่งดีมาก) มีการจับคู่สีไม่กี่สีและด้วยประสิทธิภาพที่ดีขึ้นฉันสามารถแสดง gif แบบเคลื่อนไหวได้ด้วย
Byyo

คำตอบ:


58

ฉันเล่นกับรหัสจาก @DieterMeemken เพิ่มเติม ฉันลดความละเอียดแนวตั้งลงครึ่งหนึ่งและเพิ่มการแยกทางผ่าน░▒▓ ทางด้านซ้ายคือผลลัพธ์ของ Dieter Meemken ทางด้านขวาของฉัน ด้านล่างเป็นภาพต้นฉบับที่ปรับขนาดให้ตรงกับเอาต์พุต ผลลัพธ์ผลลัพธ์ แม้ว่าฟังก์ชั่นการแปลง 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);

แก้ไข

ระยะสีเป็นเรื่องที่ซับซ้อน ( ที่นี่ , ที่นี่และการเชื่อมโยงในหน้าเหล่านั้น ... ) ฉันพยายามคำนวณระยะทางใน YUV และผลลัพธ์ค่อนข้างแย่กว่าใน RGB พวกเขาน่าจะดีกว่าเมื่อใช้ Lab และ DeltaE แต่ฉันไม่ได้ลอง ระยะห่างใน RGB ดูเหมือนจะดีพอ ในความเป็นจริงผลลัพธ์มีความคล้ายคลึงกันมากสำหรับระยะห่างของยุคลิดและแมนฮัตตันในพื้นที่สี RGB ดังนั้นฉันจึงสงสัยว่ามีสีให้เลือกน้อยเกินไป

ส่วนที่เหลือเป็นเพียงการเปรียบเทียบสีกับการผสมสีและรูปแบบทั้งหมด (= สัญลักษณ์) ฉันระบุอัตราส่วนการเติมสำหรับ░▒▓█เป็น 1/4, 2/4, 3/4 และ 4/4 ในกรณีนี้ในความเป็นจริงสัญลักษณ์ที่สามซ้ำซ้อนกับสัญลักษณ์แรก แต่ถ้าอัตราส่วนไม่เท่ากัน (ขึ้นอยู่กับแบบอักษร) ผลลัพธ์อาจเปลี่ยนแปลงได้ดังนั้นฉันจึงปล่อยไว้ที่นั่นเพื่อปรับปรุงในอนาคต สีเฉลี่ยของสัญลักษณ์คำนวณตามน้ำหนักเฉลี่ยของ foregroudColor และ backgroundColor ตามอัตราส่วนการเติม มันถือว่าเป็นสีเชิงเส้นสิ่งที่ทำให้เข้าใจง่ายมาก ดังนั้นยังมีช่องว่างสำหรับการปรับปรุง


ขอบคุณ @fubo Btw ฉันได้ทดลองกับ gamma ที่แก้ไข RGB และ Lab และทั้งสองอย่างได้รับการปรับปรุง แต่ต้องตั้งค่าอัตราส่วนการเติมให้ตรงกับแบบอักษรที่ใช้และไม่ได้ผลเลยสำหรับแบบอักษร truetype ดังนั้นมันจะไม่มีขนาดเดียวที่เหมาะกับการละลายทั้งหมดอีกต่อไป
Antonín Lejsek

90

แม้ว่าการแสดงภาพในคอนโซลไม่ใช่การใช้งานคอนโซลตามที่ตั้งใจไว้ แต่คุณก็สามารถแฮ็กสิ่งต่างๆได้อย่างแน่นอนเนื่องจากหน้าต่างคอนโซลเป็นเพียงหน้าต่างเช่นเดียวกับหน้าต่างอื่น ๆ

จริงๆแล้วเมื่อฉันได้เริ่มพัฒนาไลบรารีควบคุมข้อความสำหรับแอปพลิเคชันคอนโซลที่รองรับกราฟิก ฉันไม่เคยทำสิ่งนั้นสำเร็จแม้ว่าฉันจะมีการสาธิตแนวคิดที่ใช้งานได้:

ตัวควบคุมข้อความพร้อมรูปภาพ

และหากคุณได้รับขนาดตัวอักษรคอนโซลคุณสามารถวางภาพได้อย่างแม่นยำ

นี่คือวิธีที่คุณสามารถทำได้:

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;

และผลลัพธ์:

[กราฟิกในคอนโซล


2
ว้าวน่าสนใจจริงๆ! คุณช่วยกรุณาอธิบายรายละเอียดเล็ก ๆ น้อย ๆ เพียงแค่สิ่งที่คุณหมายถึงฉันไม่เคยเสร็จที่แม้ว่าฉันมีหลักฐานการทำงานของแนวคิดการสาธิต ? ข้อบกพร่องใด ๆ หรือแค่การขัดเงาที่หายไป .. ?
TaW

หมายความว่าโครงการไม่สมบูรณ์มาก ฉันทำสถาปัตยกรรมพื้นฐานฐานเหตุการณ์ที่ขับเคลื่อนด้วยสภาพแวดล้อม OO สนับสนุนเมาส์ปั๊มข้อความ ฯลฯ แต่ถึงแม้การควบคุมพื้นฐานมากที่สุดเช่นButton, TextBoxฯลฯ ยังคงหายไป ความฝันของฉันคือการสนับสนุน XAML แบบเต็มรูปแบบด้วยการผูกข้อมูลและปรัชญา "ฝังอะไรลงในอะไรก็ได้" แบบ WPF แต่ฉันอยู่ไกลจากนั้นมาก ... ดีในขณะนี้ :)
GyörgyKőszeg

3
ตกลงฉันเห็น แต่ดูเหมือนว่าโค้ดสามารถใช้กับโปรเจ็กต์ที่สมบูรณ์น้อยกว่าและมีความทะเยอทะยานน้อยกว่าใช่ไหม
TaW

1
ในรูปแบบปัจจุบัน ... ไม่จริง แต่ฉันวางแผนที่จะเผยแพร่บน GitHub เมื่อฉันทำให้มันค่อนข้างเสถียรและสอดคล้องกัน
GyörgyKőszeg

สิ่งนี้ดูดีมากยกเว้นความจำเป็นในการใช้ DLL ที่ไม่มีการจัดการ นอกจากนี้เราจะติดตามการพัฒนาห้องสมุดได้อย่างไร
earwig

57

หากคุณใช้ 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 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();
    }
}

คุณสามารถ

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

อินพุตตัวอย่าง:

ใส่คำอธิบายภาพที่นี่

ตัวอย่างผลลัพธ์:

ใส่คำอธิบายภาพที่นี่


7
@willywonka_dailyblah - หนวดสีม่วงจาก Day of the Tentacle Not Doom
Blaatz0r

@ Blaatz0r I mean Doom-like graphics ... imma new kid on teh block I only know Doom

3
ทั้งหมดได้รับการอภัย :-p. หากคุณเคยมีโอกาสลอง Day if the tentacle เป็นเกมที่เก่าแก่ แต่ยอดเยี่ยม
Blaatz0r

38

นั้นน่าสนุก. ขอบคุณfuboฉันลองใช้วิธีแก้ปัญหาของคุณแล้วและสามารถเพิ่มความละเอียดของการแสดงตัวอย่างได้ 4 (2x2)

ฉันพบว่าคุณสามารถตั้งค่าสีพื้นหลังสำหรับอักขระแต่ละตัวได้ ดังนั้นแทนที่จะใช้อักขระ ASCII 219 (█) สองตัวฉันใช้ ASCII 223 (▀) สองครั้งโดยมีสีพื้นหน้าและพื้นหลังที่แตกต่างกัน ซึ่งแบ่งพิกเซลขนาดใหญ่ (██) ออกเป็น 4 พิกเซลย่อยเช่นนี้ (▀▄)

ในตัวอย่างนี้ฉันวางทั้งสองภาพไว้ข้างๆกันเพื่อให้คุณเห็นความแตกต่างได้ง่าย:

ใส่คำอธิบายภาพที่นี่

นี่คือรหัส:

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

ในการรันตัวอย่างบิตแมป "image.bmp" จะต้องอยู่ในไดเร็กทอรีเดียวกับไฟล์ปฏิบัติการ ฉันเพิ่มขนาดของคอนโซลขนาดของตัวอย่างยังคงเป็น 39 และสามารถเปลี่ยนแปลงได้ที่int sMax = 39;.

น้ำยาจากทอฟฟี่ก็เด็ดมากเช่นกัน คุณสองคนมีคะแนนโหวตของฉัน ...


24

ฉันกำลังอ่านเกี่ยวกับช่องว่างสีและพื้นที่LABดูเหมือนจะเป็นตัวเลือกที่ดีสำหรับคุณ (ดูคำถามนี้: การค้นหา "ระยะห่าง" ระหว่างสีและอัลกอริทึมที่แม่นยำเพื่อตรวจสอบความคล้ายคลึงกันของสี )

การอ้างถึงหน้าWikipedia CIELABข้อดีของพื้นที่สีนี้คือ:

แตกต่างจากรุ่นสี RGB และ CMYK Lab color ได้รับการออกแบบมาเพื่อประมาณการมองเห็นของมนุษย์ มันต้องการความสม่ำเสมอในการรับรู้และส่วนประกอบ L ของมันตรงกับการรับรู้ความสว่างของมนุษย์อย่างใกล้ชิด ดังนั้นจึงสามารถใช้เพื่อทำการแก้ไขสมดุลสีที่ถูกต้องได้โดยการแก้ไขเส้นโค้งเอาต์พุตในส่วนประกอบ a และ b

ในการวัดระยะห่างระหว่างสีคุณสามารถใช้ระยะเดลต้า E

ด้วยสิ่งนี้คุณสามารถประมาณได้ดีขึ้นจาก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 };
    }
}

มีสองวิธีแบบคงที่วิธีหนึ่งในการวัดระยะทางโดยใช้Delta E ( 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
                });
            }
        }
}

ปรับปรุงอีกอาจจะเข้าถึงโดยตรงไปยังข้อมูลภาพโดยใช้แทนการใช้LockBitsGetPixel

อัปเดต : หากภาพมีส่วนที่มีสีเดียวกันคุณสามารถเพิ่มความเร็วได้มากในขั้นตอนการวาดตัวละครที่มีสีเดียวกันแทนที่จะเป็นอักขระเดี่ยว:

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

}

ภาพผลลัพธ์:

คอนโซล 1

คอนโซล 2



โซลูชันต่อไปนี้ไม่ได้ขึ้นอยู่กับตัวอักษร แต่ให้ภาพที่มีรายละเอียดครบถ้วน


คุณสามารถวาดบนหน้าต่างใดก็ได้โดยใช้ตัวจัดการเพื่อสร้างGraphicsวัตถุ ในการรับตัวจัดการแอปพลิเคชันคอนโซลคุณสามารถนำเข้าได้GetConsoleWindow:

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

จากนั้นสร้างกราฟิกด้วยตัวจัดการ (โดยใช้Graphics.FromHwnd) และวาดภาพโดยใช้วิธีการในGraphicsวัตถุตัวอย่างเช่น:

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

เวอร์ชัน 1

สิ่งนี้ดูดี แต่ถ้าคอนโซลถูกปรับขนาดหรือเลื่อนภาพจะหายไปเนื่องจากมีการรีเฟรชหน้าต่าง (อาจใช้กลไกบางอย่างในการวาดภาพใหม่ในกรณีของคุณ)


อีกวิธีหนึ่งคือการฝังหน้าต่าง ( Form) ลงในแอปพลิเคชันคอนโซล ในการดำเนินการนี้คุณต้องนำเข้าSetParent(และMoveWindowย้ายตำแหน่งหน้าต่างภายในคอนโซล):

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

จากนั้นคุณก็ต้องสร้างFormและตั้งค่าBackgroundImageคุณสมบัติเป็นภาพที่ต้องการ (ทำบนThreadหรือTaskเพื่อหลีกเลี่ยงการปิดกั้นคอนโซล):

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

เวอร์ชัน 2

แน่นอนว่าคุณสามารถตั้งค่าFormBorderStyle = FormBorderStyle.Noneให้ซ่อนขอบหน้าต่างได้ (ภาพขวา)

ในกรณีนี้คุณสามารถปรับขนาดคอนโซลและภาพ / หน้าต่างยังคงอยู่ที่นั่น

ข้อดีอย่างหนึ่งของวิธีนี้คือคุณสามารถค้นหาหน้าต่างที่คุณต้องการและเปลี่ยนภาพได้ตลอดเวลาโดยเพียงแค่เปลี่ยนBackgroundImageคุณสมบัติ


ขอบคุณสำหรับความพยายามของคุณ แต่คุณเข้าใกล้ช้ากว่าโซลูชันของAntonín Lejsek ถึง 6 เท่า ผลลัพธ์ Lap ที่น่าสนใจมาก ๆ
Byyo

4

ไม่มีทางตรง แต่คุณอาจลองใช้ตัวแปลง image-to-ascii-art แบบนี้ก็ได้


:-) อย่างไรก็ตามโปรดทราบว่าความสามารถของสีคอนโซล (ของหน้าต่าง) นั้นค่อนข้าง จำกัด เอฟเฟกต์ "ซีดจาง" ฯลฯ จึงเป็นไปไม่ได้ด้วยซ้ำ
Christian.K

1
มันตรงกับความละเอียด: P
DarkWanderer

1
คำตอบของ @ Christian.K Antonín Lejsek ทำให้เลือนหายไปได้
Byyo

0

ใช่คุณสามารถทำได้หากคุณยืดคำถามเล็กน้อยโดยการเปิด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 ทั้งหมดลงในแบบฟอร์มคอนโซล ดูคำตอบของ Taffer!

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.