WindowCaptureと透過オーバーレイ

https://qiita.com/tanakah/items/e36e764ae4592523e11b

の続編。

自分の作ったアプリで、年に1回あるかどうかの設定更新が必要なものがあります。
「まずこのボタンを押して、動作を確認したら次にこのボタンを押してー」ってのがあるんですが、その際にいちいちマニュアル見るのも面倒だし、マニュアル見ながらでも操作を間違う人はいます。というか自分ですｗ

なので画面上の誘導が欲しいのですが、レイヤードウィンドウで作ったオーバーレイを重ねただけじゃ透明度の設定で躓くし、```WS_EX_NOREDIRECTIONBITMAP```でつくったウィンドウを重ねると下のボタンを押せないということがあって、お手軽に作るにはどうすっかなぁと思ってました。

自分自身をキャプチャして同サイズのオーバーレイ画面に描画して重ね合わせ、その上に矢印とか描画できるといいなとは思っていたのですが、案だけで終わってました。

今回、時間ができてキャプチャまで手を出したので、少しやってみることにしました。

前回のサンプルアプリに、追加していきます。

```
HWND hwndOverlay=NULL;
MyWin mywinO;
unsigned int overlay_image_width, overlay_image_height;
ID2D1Bitmap1 *Overlay_Image_Bitmap=NULL;
```

32bit-pngで作っておいた矢印のビットマップを読み込んでおきます。

```
  sprintf(filename, "%s\\RedArrow.png", HomeFolderName);
  if(PathFileExists(filename)==TRUE){
    printf("load image %s\n", filename);
    IWICBitmapSource *pWIC_BitmapSource=CreateWICBITMAPfromFile(filename);
    if(pWIC_BitmapSource!=NULL){
      pWIC_BitmapSource->GetSize(&overlay_image_width, &overlay_image_height);
      printf("image_size=(%d,%d)\n", overlay_image_width, overlay_image_height);
      ID2D1DeviceContext *pD2D_DCtemp;
      pD2D_Device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, reinterpret_cast<ID2D1DeviceContext **>(&pD2D_DCtemp));
      pD2D_DCtemp->CreateBitmapFromWicBitmap(pWIC_BitmapSource, &Overlay_Image_Bitmap);
      pD2D_DCtemp->Release();
      pWIC_BitmapSource->Release();
    }
  }
```

```WS_EX_LAYERED| WS_EX_TRANSPARENT| WS_EX_TOPMOST``` の組み合わせでレイヤードウィンドウを作成します。

```
  wc.lpfnWndProc   =WndProcOverlay;
  wc.hInstance     =hInstance;
  wc.lpszClassName ="MyTest_Class_Overlay";
	wc.hbrBackground =(HBRUSH)GetStockObject(BLACK_BRUSH);
  ATOM atomOverlay=RegisterClass(&wc);

  hwndOverlay=CreateWindowEx(WS_EX_LAYERED| WS_EX_TRANSPARENT| WS_EX_TOPMOST,
                             MAKEINTATOM(atomOverlay), "", WS_POPUP, // not available WS_POPUP and WS_CHILD simultaneously
                             0, 0, 0, 0,                    NULL/*hwndMain*/, (HMENU)NULL,       hInstance, NULL);

  float transparency=1.f;
  SetLayeredWindowAttributes(hwndOverlay, 0, (unsigned char)(transparency*(float)0xff), LWA_ALPHA);
  RECT rc{};
  GetWindowRect(hwndOverlay, &rc);
  mywinO.CreateSwapChain(hwndOverlay, rc.right-rc.left, rc.bottom-rc.top);
  ShowWindow(hwndOverlay, SW_HIDE);

```

```WndProc()``` はこんな感じで


```
LONG_PTR __stdcall WndProcOverlay(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
  switch(msg){
  case WM_CREATE:
    {
      RECT rc; GetClientRect(hwndMain, &rc);
      POINT p{}; MapWindowPoints(hwndMain, NULL, &p, 1); // num of points=1
      SetWindowPos(hwnd, 0,
                   p.x+rc.left, p.y+rc.top, rc.right-rc.left, rc.bottom-rc.top,
                   SWP_NOZORDER|SWP_NOACTIVATE);
    }
    break;
  case WM_MOUSEACTIVATE:
    return MA_NOACTIVATE;
  default:
    return DefWindowProc(hwnd, msg, wparam, lparam);
  }
  return 0;
}

```

メインウィンドウと追従するように、メインウィンドウの```WndProc()``` に追加

```
  case WM_MOVE:
    {
      if(hwndOverlay!=NULL){
        RECT rc; GetClientRect(hwndMain, &rc);
        POINT p{}; MapWindowPoints(hwndMain, NULL, &p, 1); // num of points=1
        SetWindowPos(hwndOverlay, 0,
                     p.x+rc.left, p.y+rc.top, rc.right-rc.left, rc.bottom-rc.top,
                     SWP_NOZORDER|SWP_NOACTIVATE);
      }
    }
    break;

```

キャプチャしたテクスチャを適当な位置合わせでコピー、サンプルなんでごめんなさい＾＾；

```
bool CaptWinRT_onFrameArrived(IDXGISwapChain1 *swapchain,
                              winrt::Direct3D11CaptureFramePool const& sender, winrt::IInspectable const& args =nullptr)
{
  winrt::Direct3D11CaptureFrame capt_frame =sender.TryGetNextFrame();
  if(capt_frame==nullptr) return false;

  DXGI_SWAP_CHAIN_DESC1 sc_desc{};
  swapchain->GetDesc1(&sc_desc);

  int offset_x=1;
  int offset_y=GetSystemMetrics(SM_CYCAPTION)+8;
  D3D11_BOX src_region{};
  src_region.left   =offset_x;
  src_region.right  =offset_x+sc_desc.Width;
  src_region.top    =offset_y;
  src_region.bottom =offset_y+sc_desc.Height;
  src_region.front  =0;
  src_region.back   =1;
  
  winrt::SizeInt32 c_size =capt_frame.ContentSize();
  auto access =capt_frame.Surface().as<IDirect3DDxgiInterfaceAccess>();
  
  ID3D11Texture2D *texture_src;
  access->GetInterface(winrt::guid_of<ID3D11Texture2D>(), reinterpret_cast<void **>(&texture_src));

  ID3D11Texture2D *texture_dst;
  swapchain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&texture_dst);
  
  pD3D_DC->CopySubresourceRegion(texture_dst, 0, 0, 0, 0, texture_src, 0, &src_region);
  
  texture_dst->Release();
  texture_src->Release();
  capt_frame.Close();
  
  if(capt_automode==true) swapchain->Present(1, 0);
  return true;
}
```

マニュアルモードでキャプチャを開始して、メッセージループ内でキャプチャ画像が上がってきたら矢印を描画

```
  MSG msg;
  for(;;){
    if(PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)!=0){ // ==0: no message !=0: has message
      if(msg.message==WM_QUIT) break;
      if((msg.message==WM_KEYDOWN)&&(msg.wParam == VK_ESCAPE)){ DestroyWindow(hwndOverlay); PostQuitMessage(0); continue; }
      DispatchMessage(&msg);
    }
    if(mywinO.pDXGI_DC!=NULL){
      if((capt_session!=nullptr)&&(capt_framepool!=nullptr)){
        if((capt_automode==false)&&(CaptWinRT_onFrameArrived(mywinO.pDXGI_SwapChain, capt_framepool)==true)){
          mywinO.pDXGI_DC->BeginDraw();
          mywinO.pDXGI_DC->SetTransform(D2D1::Matrix3x2F::Identity());
          mywinO.pDXGI_DC->DrawBitmap(Overlay_Image_Bitmap,
                                      D2D1::RectF(220.f, 0.f, 220.f+(float)overlay_image_width, 0.f+(float)overlay_image_height),
                                      1.f, D2D1_BITMAP_INTERPOLATION_MODE_LINEAR);
          mywinO.pDXGI_DC->EndDraw();
          mywinO.pDXGI_SwapChain->Present(1, 0);
          ShowWindow(hwndOverlay, SW_SHOWNA);          
        }
      }
    }
  }
```

まぁこんなもんか。あとはアニメーションマネージャでも使って経過時間に従った表示位置の移動をすればいいか。
レイヤードウィンドウだとDirectXの恩恵は受けないんだっけ？

でやっぱり、「あなた、盗撮されてますわよ」の黄色い枠が邪魔ですなｗ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/167175/142b16d9-e5ff-5667-2be4-3a009b335323.png)