OpenCvSharpのフルスクリーン表示でウィンドウ枠を除去

OpenCvSharp(OpenCv)による画像のフルスクリーン表示で、灰色の1ピクセル幅のウィンドウ枠が消えなかったので、削除しました。

WM_NCCALCSIZEの領域を画面よりも1ピクセル広げています。マルチモニタの場合に隣の画面にはみ出すようなこともありませんでした。

やってみたらできたという状態で詳しいことは知らず、これで全く問題ないかどうかは分からないので、もしもこうしたほうが良い等ありましたら、ぜひご指摘下さい。

Windows 10＋Visual Studio 2019＋OpenCvSharp4 v4.1.1.20191110で、対象プラットフォーム「Any CPU」、「32ビットを選ぶ」ON(デフォルト)/OFFの両方で確認しています。

■自前のWndProcに変更

```C#
private delegate IntPtr WndProc2(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
```

```C#
    // 自前のWndProcに変更する
    oldWndProc = SetWindowLongPtr(hWnd, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(new WndProc2(WindowProc)));

    // SWP_FRAMECHANGED指定でこれを実行しておかないと再描画されないことがあるので実行する。
    // 念のためX、Y、幅、高さも変更する。指定しない場合には、SWP_NOMOVE | SWP_NOSIZE を追加する。
    SetWindowPos(hWnd, 0, 0, 0, workMat.Width, workMat.Height, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOOWNERZORDER);
```


■自前WndProc()でWM_NCCALCSIZEを処理

```C#
private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
{
    if (msg == WM_NCCALCSIZE && wParam != IntPtr.Zero)
    {
        // 画面よりも1ピクセル広げる
        var ncCalcSizeParams = Marshal.PtrToStructure<NCCALCSIZE_PARAMS>(lParam);
        ncCalcSizeParams.rgrc[0].Left = -1;
        ncCalcSizeParams.rgrc[0].Top = -1;
        ncCalcSizeParams.rgrc[0].Right = SCREEN_WIDTH + 1;
        ncCalcSizeParams.rgrc[0].Bottom = SCREEN_HEIGHT + 1;
        Marshal.StructureToPtr(ncCalcSizeParams, lParam, false);

        return IntPtr.Zero;
    }

    return CallWindowProc(oldWndProc, hwnd, msg, wParam, lParam);
}
```

■ソース全体

```C#:Form1.cs
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using OpenCvSharp;

namespace OpenCVSharpTest1
{
    public partial class Form1 : Form
    {
        [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
        private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int Y, int cx, int cy, int wFlags);

        [DllImport("user32.dll", EntryPoint = "FindWindow")]
        public static extern IntPtr FindWindow(System.String className, System.String windowName);

        
        // This static method is required because legacy OSes do not support
        // SetWindowLongPtr
        public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
        {
            if (IntPtr.Size == 8)
                return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
            else
                return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
        }

        [DllImport("user32.dll", EntryPoint = "SetWindowLong")]
        private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);

        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
        private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);


        [DllImport("user32.dll")]
        static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);


        [StructLayout(LayoutKind.Sequential)]
        public struct RECT
        {
            public int Left, Top, Right, Bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct WINDOWPOS
        {
            public IntPtr hwnd;
            public IntPtr hwndInsertAfter;
            public int x;
            public int y;
            public int cx;
            public int cy;
            public uint flags;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct NCCALCSIZE_PARAMS
        {
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
            public readonly RECT[] rgrc;
            public WINDOWPOS lppos;
        }


        public const int SWP_FRAMECHANGED = 0x20;
        public const int SWP_NOMOVE = 0x2;
        public const int SWP_NOSIZE = 0x1;
        public const int SWP_NOZORDER = 0x4;
        public const int SWP_NOOWNERZORDER = 0x200;

        public const int WM_NCCALCSIZE = 0x0083;

        public const int GWL_WNDPROC = (-4);


        const string WINDOW_TITLE = "test1";
        const int SCREEN_WIDTH = 1920;
        const int SCREEN_HEIGHT = 1080;


        IntPtr oldWndProc = IntPtr.Zero;

        private delegate IntPtr WndProc2(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
        
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            using (Mat mat = new Mat(@"C:\temp\debug\image.jpg"))
            {
                Mat workMat = Mat.Zeros(new OpenCvSharp.Size(SCREEN_WIDTH, SCREEN_HEIGHT), MatType.CV_8UC3);

                // アスペクト比を維持したまま画面サイズに収まるように拡大縮小する
                int newX = 0;
                int newY = 0;
                int newWidth = workMat.Width;
                int newHeight = workMat.Height;
                int t = mat.Height * workMat.Width / workMat.Height;
                if (t < mat.Width)
                {
                    // 表示先より横長
                    newHeight = mat.Height * workMat.Width / mat.Width;
                    newY = (workMat.Height - newHeight) / 2;
                }
                else if (t > mat.Width)
                {
                    // 表示先より縦長
                    newWidth = mat.Width * workMat.Height / mat.Height;
                    newX = (workMat.Width - newWidth) / 2;
                }

                Mat mat2 = new Mat();
                Cv2.Resize(mat, mat2, new OpenCvSharp.Size(newWidth, newHeight), 0, 0, InterpolationFlags.Linear);
                // ROI
                Mat mat3 = new Mat(workMat, new OpenCvSharp.Rect(newX, newY, mat2.Width, mat2.Height));
                mat2.CopyTo(mat3);

                // 最初からFullscreenにするとフルスクリーンにならないので、
                // Normalで作成してからFullscreenにする。
                Cv2.NamedWindow(WINDOW_TITLE, WindowMode.Normal);
                Cv2.SetWindowProperty(WINDOW_TITLE, WindowProperty.Fullscreen, 1);

                IntPtr hWnd = IntPtr.Zero;
                try
                {
                    hWnd = FindWindow(null, WINDOW_TITLE);
                    if (hWnd != null)
                    {
                        // 自前のWndProcに変更する
                        oldWndProc = SetWindowLongPtr(hWnd, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(new WndProc2(WindowProc)));

                        // SWP_FRAMECHANGED指定でこれを実行しておかないと再描画されないことがあるので実行する。
                        // 念のためX、Y、幅、高さも変更する。指定しない場合には、SWP_NOMOVE | SWP_NOSIZE を追加する。
                        SetWindowPos(hWnd, 0, 0, 0, workMat.Width, workMat.Height, SWP_FRAMECHANGED | SWP_NOZORDER | SWP_NOOWNERZORDER);
                    }

                    Cv2.ImShow(WINDOW_TITLE, workMat);
                    Cv2.WaitKey(0);
                }
                finally
                {
                    if ( hWnd != IntPtr.Zero && oldWndProc != IntPtr.Zero)
                    {
                        // WndProcを元に戻す
                        SetWindowLongPtr(hWnd, GWL_WNDPROC, oldWndProc);
                    }
                }

                Cv2.DestroyAllWindows();
            }
        }

        private IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam)
        {
            if (msg == WM_NCCALCSIZE && wParam != IntPtr.Zero)
            {
                // 画面よりも1ピクセル広げる
                var ncCalcSizeParams = Marshal.PtrToStructure<NCCALCSIZE_PARAMS>(lParam);
                ncCalcSizeParams.rgrc[0].Left = -1;
                ncCalcSizeParams.rgrc[0].Top = -1;
                ncCalcSizeParams.rgrc[0].Right = SCREEN_WIDTH + 1;
                ncCalcSizeParams.rgrc[0].Bottom = SCREEN_HEIGHT + 1;
                Marshal.StructureToPtr(ncCalcSizeParams, lParam, false);

                return IntPtr.Zero;
            }

            return CallWindowProc(oldWndProc, hwnd, msg, wParam, lParam);
        }

    }
}
```

■参考
[C#とOpenCVSharpで画像処理！(Visual Studio 2019) | 千草ウェブ](https://chigusa-web.com/opencvsharp%E3%81%A7%E7%94%BB%E5%83%8F%E5%87%A6%E7%90%86/)
[【C#メモ】WindowsAPI：FindWindow関数 | フィロの村note](http://note.phyllo.net/?eid=1106262)
[OpenCVを使って画像を縮小、拡大してみる。 - なんとなくのブログ](http://beetreehitsuji.hatenablog.com/entry/2019/02/20/114249)
[OpenCV ウインドウ 枠 (ほぼ)非表示 - gologiusの巣](http://gologius.hatenadiary.com/entry/2016/03/09/111650)
[OpenCVでフルスクリーン表示（ウィンドウ枠なし） - ymutoの日記](https://ymuto.hatenadiary.org/entry/20111013/1318518515)
[.NET Compact Framework で Win32 メッセージを処理する - しばやん雑記](https://blog.shibayan.jp/entry/20071112/1194871025)
[c＃ – DWMを使ったカスタムウィンドウフレーム：WM_NCCALCSIZEを正しく扱う方法 - コードログ](https://codeday.me/jp/qa/20190425/697959.html)
[クライアント領域に関するメモ的な何か | 株式会社エイチ・オー・エス](https://www.hos.co.jp/blog/20140317/)
[C# - 【Unity】タイトルバーを非表示にする｜teratail](https://teratail.com/questions/190646)