【问题标题】:Display a Image in a console application在控制台应用程序中显示图像
【发布时间】:2016-02-05 22:21:36
【问题描述】:

我有一个管理图像的控制台应用程序。现在我需要在控制台应用程序中预览图像。有没有办法在控制台中显示它们?

这是当前基于字符的答案的比较:

输入:

输出:

【问题讨论】:

  • 在控制台中,就像在控制台窗口中一样?不可以。不过,您可以启动一个单独的对话框/窗口。
  • 控制台应用程序主要用于纯文本应用程序。没有办法显示图像。您可以启动另一个显示图像的应用程序。这个其他应用程序很可能需要支持命令行选项才能将图像传递给它。
  • 为什么要使用控制台应用程序?它在哪里运行?您始终可以启动一个进程来调出默认图像查看器或您自己的简单的 winforms 等应用程序..
  • 我需要改进 Antonín Lejsek 的代码(这很棒)。有一些颜色不匹配,通过改进的性能,我也可以显示动画 gif

标签: c# image console


【解决方案1】:

没有直接的方法。但是你可以尝试使用像this one这样的image-to-ascii-art转换器

【讨论】:

  • :-) 但是,请注意控制台(窗口)的颜色功能也非常有限。所以“褪色”效果等甚至是不可能的。
  • 嗯,它匹配分辨率:P
  • @Christian.K Antonín Lejsek 的回答使褪色成为可能
【解决方案2】:

是的,如果您通过在控制台应用程序中打开 Form 稍微延长问题,您可以做到。

以下是让控制台应用程序打开表单并显示图像的方法:

  • 在您的项目中包含这两个引用:System.DrawingSystem.Windows.Forms
  • 也包括两个命名空间:

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

this post on how to do that

现在你只需要添加这样的东西:

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

当然你也可以使用PictureBox..

您可以使用form1.Show(); 在预览显示时保持控制台处于活动状态..

原帖:当然,您不能在内部 25x80 窗口中正确显示图像;即使您使用更大的窗口并阻止图形,它也不会是预览而是一团糟!

更新:看起来您毕竟可以在控制台表单上使用 GDI 绘制图像;看taffer的回答!

【讨论】:

    【解决方案3】:

    如果你使用 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"));
    

    样本输入:

    样本输出:

    【讨论】:

    • @willywonka_dailyblah - 这是触手之日的紫色触手。不是末日
    • @Blaatz0r 我的意思是类似 Doom 的图形......我是这个街区的新手,我只知道 Doom
    • 一切都被原谅了:-p。如果你有机会试试 Day if the tentacle 它是一款很棒的游戏,虽然古老但很棒。
    【解决方案4】:

    虽然在控制台中显示图像不是控制台的预期用途,但您肯定可以破解这些东西,因为控制台窗口只是一个窗口,就像任何其他窗口一样。

    实际上,一旦我开始为具有图形支持的控制台应用程序开发文本控件库。虽然我有一个有效的概念验证演示,但我从未完成过:

    如果你获得了控制台字体大小,你可以非常精确地放置图像。

    你可以这样做:

    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;
    

    结果:

    [

    【讨论】:

    • 哇,这真的很有趣!您能否详细说明一下您所说的我从未完成过,尽管我有一个工作的概念验证演示?有什么缺点还是只是缺少润色..?
    • 表示项目很不完整。我做了基本的架构,基本的事件驱动的 OO 环境,鼠标支持,消息泵等。但即使是最基础的控件,如ButtonTextBox 等仍然缺失。我的梦想是通过数据绑定和类似 WPF 的“将任何东西嵌入任何东西”的理念来提供相当完整的 XAML 支持。但我离那个还很远......好吧,就在这一刻:)
    • 好的,我明白了,但是代码看起来可以用于任何不太完整、不太雄心勃勃的项目,是吗?
    • 嗯,以目前的形式......不是真的。但我计划在让它变得稳定和连贯后将其发布到 GitHub 上。
    • 这看起来很酷,除了必须使用非托管 DLL。另外,我们如何跟踪图书馆的发展?
    【解决方案5】:

    这很有趣。 感谢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;更改。

    taffer 的解决方案也很酷。 你们两个有我的支持...

    【讨论】:

      【解决方案6】:

      我进一步使用了来自@DieterMeemken 的代码。我将垂直分辨率减半并通过░▒▓添加抖动。左边是 Dieter Meemken 结果,右边是 my。底部是调整大小以大致匹配输出的原始图片。 虽然 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);
      

      编辑

      颜色距离是一个复杂的主题(herehere 以及这些页面上的链接...)。我试图在 YUV 中计算距离,结果比在 RGB 中更差。使用 Lab 和 DeltaE 可能会更好,但我没有尝试过。 RGB 中的距离似乎足够好。事实上,欧几里得距离和曼哈顿距离在 RGB 颜色空间中的结果非常相似,所以我怀疑可供选择的颜色太少了。

      其余的只是将颜色与颜色和图案的所有组合(=符号)进行强力比较。我将 ░▒▓█ 的填充率指定为 1/4、2/4、3/4 和 4/4。在这种情况下,第三个符号实际上对第一个符号是多余的。但是如果比率不是那么统一(取决于字体),结果可能会改变,所以我把它留在那里以备将来改进。符号的平均颜色根据填充率计算为前景色和背景色的加权平均值。它采用线性颜色,这也是很大的简化。所以还有改进的余地。

      【讨论】:

      • 谢谢@fubo。顺便说一句,我尝试了伽马校正的 RGB 和 Lab,两者都得到了改进。但是填充率必须设置为与使用的字体匹配,并且对于 truetype 字体根本不起作用。所以它不再是一种适合所有解决方案的尺寸。
      【解决方案7】:

      我正在阅读有关 色彩空间LAB 空间似乎对您来说是一个不错的选择(请参阅此问题:Finding an accurate “distance” between colorsAlgorithm to check similarity of colors

      引用维基百科CIELAB页面,这个色彩空间的优点是:

      与 RGB 和 CMYK 颜色模型不同,Lab 颜色旨在接近人类视觉。它渴望感知均匀性,其 L 分量与人类对亮度的感知非常匹配。因此,它可以通过修改a和b分量的输出曲线来进行准确的色彩平衡校正。

      要测量颜色之间的距离,您可以使用Delta E distance。

      有了这个,你可以更好地从ColorConsoleColor

      首先,您可以定义一个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)。

      对于从RGBLAB 的转换,您可以使用以下方法(从here):

      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这样的阴影字符做('█','▓','▒','░'),这可以让您获得超过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);
      
      }
      

      结果图片:



      以下解决方案不是基于字符,而是提供完整详细的图像


      您可以使用其处理程序在任何窗口上绘制以创建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);
      }
      

      这看起来不错,但是如果调整控制台大小或滚动,图像会因为窗口被刷新而消失(在您的情况下,可能实现某种机制来重绘图像)。


      另一种解决方案是在控制台应用程序中嵌入一个窗口 (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 属性设置为所需的图像(在ThreadTask 上执行此操作以避免阻塞控制台):

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

      当然你可以设置FormBorderStyle = FormBorderStyle.None隐藏窗口边框(右图)

      在这种情况下,您可以调整控制台的大小,并且图像/窗口仍然存在。

      这种方法的一个好处是,您可以通过更改BackgroundImage 属性随时找到所需的窗口并更改图像。

      【讨论】:

      • 感谢您的努力,但您的方法比 Antonín Lejsek 的解决方案慢 6 倍。无论如何,非常有趣的圈速结果。
      猜你喜欢
      • 1970-01-01
      • 2013-03-08
      • 2017-05-13
      • 2010-12-12
      • 2011-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多