2017年5月21日日曜日

フォームの一部を透過して、背景をキャプチャする

 タイトルのようなことをやりたかったので、やってみました。



 静止画だとわかりづらいですが、左の赤枠の中が透過になっており、スクリーンのその領域を拡大して右に表示しています。簡易的なスクリーン拡大鏡のような動作です。

 スクリーンの一部を透過にするには、FormのTransparencyKeyに適当なColorを設定し、透過させたいコントロールのBackColorにTransparencyKeyを指定すれば、その部分は透過になります。
 適当な色というのはなんでも良いのですが、Form上のその色の部分はすべて透過になってしまうので、あまり使われない色を指定する必要があります。透過するとどんな色が設定されるかは背景次第ですが、単純計算では1677万分の1の確率で透過が発生します。確率的には大したことないでしょうけども、例えば#FFFFFFや#000000といった、ありふれた色を設定するとよろしくありません。ということで今回は#FFFFFEを指定しました。#FEFEFEとかでも良いのですが、RGBが同じ輝度というのはグレイスケール画像が後ろにある際に256分の1の確率で発生する可能性が高いので、すべて同じ輝度は避けたほうが良いと思います。

 画面をキャプチャするのも、いろいろな方法があるようですが、いくつか試した限りではうまくいくものは1個しかありませんでした。というか何個か試した内でうまく動いたところでそれを採用したので、探せば何個も有るんでしょうが。
 とりあえず、BitBltを使った方法でうまく動いてます。以下のページを参考にしました。
 画面をキャプチャする: .NET Tips: C#, VB.NET
 サンプルコードをちょっと書き換えて、引数でキャプチャする領域を指定するようにしています。

 TransparencyKeyで透過した領域は、マウスも透過して扱われます。なので、スクリーン拡大鏡の中でマウスをクリックすれば、透過先のプログラムにクリックが発生します。ただ、クリックした際にそのWindowがアクティブになるので、拡大鏡が隠れてしまいます。そのような動作をされたくない場合は、常に手前に表示するようにしてやる必要があります。




private void button1_Click(object sender, EventArgs e)
{
    Bitmap bmp = CaptureScreen(pictureBox1.PointToScreen(new Point()), pictureBox1.Size);

    pictureBox2.Image?.Dispose();
    pictureBox2.Image = bmp;
}

private const int SRCCOPY = 0x00CC0020;

[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);

[DllImport("gdi32.dll")]
private static extern int BitBlt(IntPtr hDestDC, int x, int y, int nWidth, int nHeight, IntPtr hSrcDC, int xSrc, int ySrc, int dwRop);

[DllImport("user32.dll")]
private static extern IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);

public static Bitmap CaptureScreen(Point Position, Size Size)
{
    Bitmap bmp = new Bitmap(Size.Width, Size.Height);

    using (Graphics g = Graphics.FromImage(bmp))
    {
        IntPtr hDC = g.GetHdc();
        IntPtr disDC = GetDC(IntPtr.Zero);
        BitBlt(hDC, 0, 0, bmp.Width, bmp.Height, disDC, Position.X, Position.Y, SRCCOPY);
        g.ReleaseHdc(hDC);
        ReleaseDC(IntPtr.Zero, disDC);
    }

    return bmp;
}

0 件のコメント:

コメントを投稿