Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/265.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 使用索引颜色表捕获程序的屏幕截图_C#_Screenshot_Pixel_Indexed Image - Fatal编程技术网

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

C# 使用索引颜色表捕获程序的屏幕截图,c#,screenshot,pixel,indexed-image,C#,Screenshot,Pixel,Indexed Image,网上有很多关于如何使用C#捕获和保存屏幕截图的教程。 例如,我曾经获得我的解决方案: using (var screenshot = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb)) using (var g = Graphics.FromImage(screenshot))

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

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


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

如果我理解正确,这里的问题是您复制的是像素值(调色板索引),而不是调色板本身。我没有找到使用纯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);
        }
    }
    [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);
        }
    }
此方法取决于
gethalftonepalete
是否为您提供正确的调色板。我从来没有测试过调色板(我太年轻了)。。。所以我决定写第二个方法,基于windows应该在某些情况下自动处理选项板。注意:不确定将对CopyFromScreen的调用移动到using块的开头是否更好,还是忽略对RealizePalette的调用(我太年轻了)

第二种方法:

[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);
        }
    }
    [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提供了一个版本)可能是一个不错的选择。 请参阅,因为它解决了类似的问题,并提供了GTK#的示例代码

注意: 下面的代码在这里似乎工作得很好,您是否有任何异常

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

建议的解决方案不起作用,我只是最近才解决了这个问题。
我通过向windows发送一个PrintScreen按键并释放来获取一个屏幕截图,然后以
内存流的形式从剪贴板中获取数据,并计算出该屏幕截图是如何在该流中解码并转换为位图的。不是很吸引人,但至少它是有效的…

谢谢。我已经怀疑用Format8BPindexed替换Format32bppArgb是行不通的,但我不得不尝试。我根本不打算压缩文件,只是为了制作一个有效的屏幕截图。当我重写我的问题时,你回答了,我们之间有矛盾。您说32bpp的图像与程序的输出无法区分,而我发现并非如此。你知道是什么原因造成的吗?我不知道“否则”是什么意思,很难猜测是什么原因造成的。我在编辑的帖子中尽可能详尽地描述了“否则”。它是如何进入全屏的?这是某种旧的DOS或Windows3.x游戏吗?它运行的硬件有多旧?这是一款1996年的游戏,我不知道它是否是专门为特定版本的Windows设计的。它运行的机器是一台全新的机器。也许你应该把那张“便条”移到你帖子的顶部。。。无论哪种方式都是好东西。我正在尝试类似于OP的东西,但使用的是8位X11客户端。第一个版本几乎可以工作,但是混合了一些颜色。你对什么可能导致这种情况有什么建议吗?