【问题标题】:Capture screenshot of program using indexed color table使用索引颜色表捕获程序的屏幕截图
【发布时间】:2010-12-31 14:12:02
【问题描述】:

网络上有许多关于如何使用 C# 捕获和保存屏幕截图的教程。 比如我用this website获取我的解决方案:

        using (var screenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb))
        using (var g = Graphics.FromImage(screenshot))
        {
            g.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size);
            screenshot.Save("screenshot.png", ImageFormat.Png);
        }

它在大多数程序中都能正常工作,但我要捕获的程序使用 8 位索引颜色表。使用此代码拍摄的该程序的屏幕截图很奇怪。我的问题是,是否有人可以指出我在 C# 中使用索引颜色表捕获程序屏幕截图的正确方向?

为了帮助您帮助我,我将在下面描述我的发现和尝试的解决方案。

使用 8 位索引颜色表的全屏程序代码捕获的屏幕截图大部分是黑色(约 88 %),其中只有 17 种其他颜色。我没有看到这 17 种颜色的图案。在程序本身中,几乎所有 256 种颜色都被使用。我希望在黑色屏幕截图中也能找到 256 种颜色,这可能表示简单的一对一关系,但事实并非如此。

我还想指出,手动截取的屏幕截图是完美的(例如,当我将它们粘贴到 MS Paint 中时)。出于这个原因,我在手动截屏后尝试从 System.Windows.Forms.Clipboard.GetImage() 获取图像,但这会返回一个 COMobject,我不知道该怎么做。当然,它必须包含有效屏幕截图的信息,因为 MS Paint 知道如何从该 COM 对象中提取它。我怎样才能自己提取它,最好是在 C# 中?

但我的主要问题是:有人能指出我使用 C# 捕获带有索引颜色表的程序屏幕截图的正确方向吗?

【问题讨论】:

    标签: c# screenshot pixel indexed-image


    【解决方案1】:

    如果我理解正确,这里的问题是您正在复制像素值(调色板索引)而不是调色板本身。我没有找到使用纯 C# 复制调色板的方法,但是使用 P/Invoke 和 DirectX 的替代方案......因为两者都添加了对 Windows 的依赖项,所以我选择了 P/Invoke,因为它更容易而且不会依赖于 DirectX。

    我有两种方法可以提供给你...

    第一种方法:

    [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        private static extern IntPtr SelectPalette(
            IntPtr hdc,
            IntPtr htPalette,
            bool bForceBackground);
    
        [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
        private static extern int RealizePalette(IntPtr hdc);
    
        private void ScreenShot()
        {
            IntPtr htPalette = Graphics.GetHalftonePalette();
            using (var screenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
            {
                using (var graphics = Graphics.FromImage(screenshot))
                {
                    IntPtr hdc = graphics.GetHdc();
                    SelectPalette(hdc, htPalette, true);
                    RealizePalette(hdc);
                    graphics.ReleaseHdc(hdc);
                    graphics.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size);
                }
                screenshot.Save("screenshot.png", System.Drawing.Imaging.ImageFormat.Png);
            }
        }
    

    此方法取决于GetHalftonePalette 为您提供正确的调色板。我从来没有测试过调色板(我太年轻了)......所以我决定编写第二种方法,基于 Windows 应该在某些情况下自动处理调色板。注意:不确定是否将对 CopyFromScreen 的调用移到 using 块的开头更好,或者忽略对 RealizePalette 的调用(我太年轻了)。

    第二种方法:

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        [return: System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.Bool)]
        static extern bool BitBlt(IntPtr hdc, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, TernaryRasterOperations dwRop);
    
        private void ScreenShot()
        {
            int width = Screen.PrimaryScreen.Bounds.Width;
            int height = Screen.PrimaryScreen.Bounds.Height;
            using (var screenshot = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb))
            {
                using (var fromHwnd = Graphics.FromHwnd(new IntPtr(0)))
                using (var graphics = Graphics.FromImage(screenshot))
                {
                    IntPtr hdc_screen = fromHwnd.GetHdc();
                    IntPtr hdc_screenshot = graphics.GetHdc();
                    BitBlt(hdc_screenshot, 0, 0, width, height, hdc_screen, 0, 0, 0x00CC0020); /*SRCCOPY = 0x00CC0020*/
                    graphics.ReleaseHdc(hdc_screenshot);
                    fromHwnd.ReleaseHdc(hdc_screen);
                }
                screenshot.Save("screenshot.png", System.Drawing.Imaging.ImageFormat.Png);
            }
        }
    

    这两种方法都适用于正常情况。请进行您的测试,您可能需要通过添加复制原始调色板来执行第二种方法(即...当 Windows 不自动处理它们时),但我不确定如何这样做(我太年轻了)并希望这种方法有效。

    最后,如果您要承担额外的依赖并希望它与 Windows 不同,也许 GTK#(有一个 Windows 可用的版本)是一个不错的选择。 请参阅How to take a screenshot with Mono C#?,因为它解决了类似的问题,并提供了 GTK# 的示例代码。

    注意: 以下代码在这里似乎运行良好,您是否通过此代码得到任何异常?

    System.Drawing.Image image = Clipboard.GetImage();
    image.Save("screenshot.png", System.Drawing.Imaging.ImageFormat.Png);
    

    【讨论】:

    • 也许你应该把那个“注”移到你的帖子的顶部......不管怎样都是好东西。
    • 我正在尝试类似于 OP 的东西,但使用的是 8 位 X11 客户端。第一个版本几乎可以工作,但混合了一些颜色。您对导致这种情况的原因有什么建议吗?
    【解决方案2】:

    这不是它的工作原理。您正在捕获屏幕,而不是直接捕获程序的输出。视频适配器的设置很重要,任何最近的机器都是 32bpp。旧的可能是 16bpp。不支持尝试将这种非索引像素格式复制到带有调色板的位图中。创建提供最佳色彩保真度的调色板的算法在计算上非常重要。

    别费心了,32bpp 图像与程序的输出无法区分。如果压缩文件真的很重要,那么将其存储为 .gif。看起来不太好,GIF 编码器使用抖动来压缩颜色表。

    【讨论】:

    • 谢谢。我已经怀疑用 Format8bppIndexed 替换 Format32bppArgb 是行不通的,但不得不尝试。我根本无意压缩文件,只是为了制作有效的屏幕截图。你在我重写我的问题时回答了,我们有矛盾。您说 32bpp 图像与程序的输出无法区分,而我发现并非如此。你知道是什么原因造成的吗?
    • 我不知道“否则”是什么意思,很难猜测是什么原因造成的。
    • 我已经在我编辑的帖子中尽可能详尽地描述了“否则”。
    • 它是如何进入全屏的?这是某种旧的 DOS 或 Windows 3.x 游戏吗?运行它的硬件有多少年了?
    • 这是一款 1996 年的游戏,我不知道它是不是专门为特定版本的 Windows 设计的。运行它的机器是一台全新的机器。
    【解决方案3】:

    建议的解决方案不起作用,我最近才解决了这个问题。 我通过向 Windows 发送 PrintScreen 按键并释放来截取屏幕截图,并以 MemoryStream 的形式从剪贴板获取数据,并弄清楚屏幕截图是如何在该流中解码并将其转换为位图的。不是很吸引人,但至少它有效......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-06
      • 1970-01-01
      • 2014-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多