使用索引颜色表捕获程序的屏幕截图

时间:2010-12-31 14:12:03

标签: c# screenshot pixel indexed-image

网上有很多关于如何使用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#索引颜色表的程序截图吗?

3 个答案:

答案 0 :(得分: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不同,那么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);

答案 1 :(得分:2)

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

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

答案 2 :(得分:0)

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