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 }