2

C#を使用してスクリーンショットをキャプチャして保存する方法については、Web上に多数のチュートリアルがあります。たとえば、私はこのWebサイトを使用してソリューションを入手しました。

        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色を見つけたいと思っていました。これは、単純な1対1の関係を示している可能性がありますが、そうではありません。

また、手動で撮影したスクリーンショットは完璧です(たとえば、MSペイントに貼り付ける場合)。そのため、スクリーンショットを手動で撮った後、System.Windows.Forms.Clipboard.GetImage()から画像をフェッチしようとしましたが、COMオブジェクトが返され、それをどうすればよいかわかりません。確かに、MSペイントはそのCOMオブジェクトからスクリーンショットを抽出する方法を知っているので、有効なスクリーンショットの情報が含まれている必要があります。どうすればそれを自分で、できればC#で抽出できますか?

しかし、私の主な質問:誰かがC#でインデックスカラーテーブルを使用してプログラムのスクリーンショットをキャプチャするための正しい方向に私を向けることができますか?

4

3 に答える 3

3

私の理解が正しければ、ここでの問題は、パレット自体ではなく、ピクセル値 (パレット インデックス) をコピーしていることです。純粋な C# でパレットをコピーする方法が見つかりませんでしたが、P/Invoke と DirectX の代替手段を使用して...どちらも Windows への依存関係を追加するため、P/Invoke を選択しました。 DirectX に依存します。

2つの方法をご紹介します...

最初の方法:

[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この方法は、適切なパレットを提供することに依存します。私はパレットでテストしたことがありません (私は若すぎます)... そこで、ウィンドウが特定の条件でパレットを自動的に処理する必要があることに基づいて、2 番目のメソッドを作成することにしました。注: CopyFromScreen への呼び出しを using ブロックの先頭に移動する方がよいかどうか、または RealizePalette への呼び出しを無視する方がよいかどうかはわかりません (私は若すぎます)。

2番目の方法:

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

どちらの方法も通常の状態で機能します。テストを行ってください。元のパレットを複製することを追加して2番目の方法を実行する必要があるかもしれません(つまり、... 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

提案された解決策は機能せず、最近この問題を解決しました。PrintScreenキーを押して離すことをWindowsに送信してスクリーンショットを撮り、クリップボードからデータをaの形式で取得し、MemoryStreamそのストリームでスクリーンショットがどのようにデコードされ、それをビットマップに変換したかを理解しました。あまり魅力的ではありませんが、少なくとも機能します...

于 2011-07-04T23:48:57.493 に答える