/ src / modules / MouseUtils / MouseJump.Common / Imaging / DesktopImageRegionCopyService.cs
DesktopImageRegionCopyService.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System.Diagnostics;
  6  
  7  using MouseJump.Common.Models.Drawing;
  8  using MouseJump.Common.NativeMethods;
  9  using static MouseJump.Common.NativeMethods.Core;
 10  
 11  namespace MouseJump.Common.Imaging;
 12  
 13  /// <summary>
 14  /// Implements an IImageRegionCopyService that uses the current desktop window as the copy source.
 15  /// This is used during the main application runtime to generate preview images of the desktop.
 16  /// </summary>
 17  public sealed class DesktopImageRegionCopyService : IImageRegionCopyService
 18  {
 19      /// <summary>
 20      /// Copies the source region from the current desktop window
 21      /// to the target region on the specified Graphics object.
 22      /// </summary>
 23      public void CopyImageRegion(
 24          Graphics targetGraphics,
 25          RectangleInfo sourceBounds,
 26          RectangleInfo targetBounds)
 27      {
 28          var stopwatch = Stopwatch.StartNew();
 29          var (desktopHwnd, desktopHdc) = DesktopImageRegionCopyService.GetDesktopDeviceContext();
 30          var previewHdc = DesktopImageRegionCopyService.GetGraphicsDeviceContext(
 31              targetGraphics, Gdi32.STRETCH_BLT_MODE.STRETCH_HALFTONE);
 32          stopwatch.Stop();
 33  
 34          var source = sourceBounds.ToRectangle();
 35          var target = targetBounds.ToRectangle();
 36          var result = Gdi32.StretchBlt(
 37              previewHdc,
 38              target.X,
 39              target.Y,
 40              target.Width,
 41              target.Height,
 42              desktopHdc,
 43              source.X,
 44              source.Y,
 45              source.Width,
 46              source.Height,
 47              Gdi32.ROP_CODE.SRCCOPY);
 48          if (!result)
 49          {
 50              throw new InvalidOperationException(
 51                  $"{nameof(Gdi32.StretchBlt)} returned {result.Value}");
 52          }
 53  
 54          // we need to release the graphics device context handle before anything
 55          // else tries to use the Graphics object otherwise it'll give an error
 56          // from GDI saying "Object is currently in use elsewhere"
 57          DesktopImageRegionCopyService.FreeGraphicsDeviceContext(targetGraphics, ref previewHdc);
 58  
 59          DesktopImageRegionCopyService.FreeDesktopDeviceContext(ref desktopHwnd, ref desktopHdc);
 60      }
 61  
 62      private static (HWND DesktopHwnd, HDC DesktopHdc) GetDesktopDeviceContext()
 63      {
 64          var desktopHwnd = User32.GetDesktopWindow();
 65          var desktopHdc = User32.GetWindowDC(desktopHwnd);
 66          if (desktopHdc.IsNull)
 67          {
 68              throw new InvalidOperationException(
 69                  $"{nameof(User32.GetWindowDC)} returned null");
 70          }
 71  
 72          return (desktopHwnd, desktopHdc);
 73      }
 74  
 75      private static void FreeDesktopDeviceContext(ref HWND desktopHwnd, ref HDC desktopHdc)
 76      {
 77          if (!desktopHwnd.IsNull && !desktopHdc.IsNull)
 78          {
 79              var result = User32.ReleaseDC(desktopHwnd, desktopHdc);
 80              if (result == 0)
 81              {
 82                  throw new InvalidOperationException(
 83                      $"{nameof(User32.ReleaseDC)} returned {result}");
 84              }
 85          }
 86  
 87          desktopHwnd = HWND.Null;
 88          desktopHdc = HDC.Null;
 89      }
 90  
 91      /// <summary>
 92      /// Checks if the target device context handle exists, and creates a new one from the
 93      /// specified Graphics object if not.
 94      /// </summary>
 95      private static HDC GetGraphicsDeviceContext(Graphics graphics, Gdi32.STRETCH_BLT_MODE mode)
 96      {
 97          var graphicsHdc = (HDC)graphics.GetHdc();
 98  
 99          var result = Gdi32.SetStretchBltMode(graphicsHdc, mode);
100          if (result == 0)
101          {
102              throw new InvalidOperationException(
103                  $"{nameof(Gdi32.SetStretchBltMode)} returned {result}");
104          }
105  
106          return graphicsHdc;
107      }
108  
109      /// <summary>
110      /// Free the specified device context handle if it exists.
111      /// </summary>
112      private static void FreeGraphicsDeviceContext(Graphics graphics, ref HDC graphicsHdc)
113      {
114          if (graphicsHdc.IsNull)
115          {
116              return;
117          }
118  
119          graphics.ReleaseHdc(graphicsHdc.Value);
120          graphicsHdc = HDC.Null;
121      }
122  }