2

网上有很多关于如何使用 C# 捕获和保存屏幕截图的教程。例如,我使用这个网站来获取我的解决方案:

        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# 捕获带有索引颜色表的程序屏幕截图的正确方向吗?

4

3 回答 3

3

如果我理解正确,这里的问题是您正在复制像素值(调色板索引)而不是调色板本身。我没有找到使用纯 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 可用的版本)是一个不错的选择。请参阅如何使用 Mono C# 截屏?因为它解决了类似的问题,并提供了 GTK# 的示例代码。

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

System.Drawing.Image image = Clipboard.GetImage();
image.Save("screenshot.png", System.Drawing.Imaging.ImageFormat.Png);
于 2011-01-10T05:10:31.713 回答
2

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

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

于 2010-12-31T16:00:03.433 回答
0

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

于 2011-07-04T23:48:57.493 回答