From 6488158b9fd003d690eb015cf9a644112a363f71 Mon Sep 17 00:00:00 2001 From: Roy Ben Shabat Date: Mon, 2 Mar 2020 00:10:25 +0200 Subject: Implemented Tango.RemoteDesktop using generic Diff Frame. --- .../Tango.RemoteDesktop/Tango.RemoteDesktop.sln | 43 + .../CaptureMethods/BitBltScreenCapture.cs | 56 ++ .../CaptureMethods/DirectXScreenCapture.cs | 138 ++++ .../CaptureMethods/GdiScreenCapture.cs | 29 + .../Tango.RemoteDesktop/CaptureRegion.cs | 52 ++ .../Comparers/RasterBitmapComparer.cs | 94 +++ .../Comparers/VectorBitmapComparer.cs | 85 ++ .../Tango.RemoteDesktop/Encoders/BitmapEncoder.cs | 24 + .../Tango.RemoteDesktop/Encoders/JpegEncoder.cs | 46 ++ .../Tango.RemoteDesktop/Encoders/PngEncoder.cs | 24 + .../Engines/RasterScreenCaptureEngine.cs | 18 + .../Engines/VectorScreenCaptureEngine.cs | 18 + .../Tango.RemoteDesktop/Frame.cs | 48 ++ .../Tango.RemoteDesktop/FrameEncoder.cs | 30 + .../Tango.RemoteDesktop/Frames/RasterFrame.cs | 42 + .../Tango.RemoteDesktop/Frames/VectorFrame.cs | 130 ++++ .../Tango.RemoteDesktop/Frames/VectorFramePixel.cs | 17 + .../Tango.RemoteDesktop/IBitmapComparer.cs | 14 + .../Tango.RemoteDesktop/ICaptureMethod.cs | 14 + .../Tango.RemoteDesktop/IFrame.cs | 47 ++ .../Tango.RemoteDesktop/IFrameEncoder.cs | 16 + .../Tango.RemoteDesktop/IScreenCaptureEngine.cs | 22 + .../Tango.RemoteDesktop/Properties/AssemblyInfo.cs | 36 + .../Tango.RemoteDesktop/ScreenCaptureEngine.cs | 124 +++ .../Tango.RemoteDesktop/ScreenCaptureFrame.cs | 46 ++ .../ScreenCaptureFrameReceivedEventArgs.cs | 13 + .../Tango.RemoteDesktop/Tango.RemoteDesktop.csproj | 95 +++ .../Tango.RemoteDesktop/Utils/CursorUtils.cs | 236 ++++++ .../Tango.RemoteDesktop/Utils/DirectBitmap.cs | 55 ++ .../Tango.RemoteDesktop/Utils/FastBitmap.cs | 864 +++++++++++++++++++++ .../Tango.ScreenCapture/BitBltScreenCapture.cs | 56 ++ .../Tango.ScreenCapture/CaptureRegion.cs | 52 ++ .../Tango.ScreenCapture/CursorUtils.cs | 236 ++++++ .../Tango.ScreenCapture/DXScreenCapture.cs | 436 +++++++++++ .../Tango.ScreenCapture/DirectBitmap.cs | 55 ++ .../Tango.ScreenCapture/DirectXScreenCapture.cs | 138 ++++ .../Tango.ScreenCapture/FastBitmap.cs | 864 +++++++++++++++++++++ .../Tango.ScreenCapture/GdiScreenCapture.cs | 29 + .../Tango.ScreenCapture/IScreenCaptureFrame.cs | 25 + .../Tango.ScreenCapture/IScreenCaptureMethod.cs | 14 + .../Tango.ScreenCapture/ImageComparer.cs | 162 ++++ .../Tango.ScreenCapture/ImageComparerResult.cs | 25 + .../Tango.ScreenCapture/Properties/AssemblyInfo.cs | 36 + .../RasterScreenCaptureFrame.cs | 17 + .../Tango.ScreenCapture/ScreenCaptureEngine.cs | 119 +++ .../Tango.ScreenCapture/ScreenCaptureFrame.cs | 120 +++ .../ScreenCaptureFrameReceivedEventArgs.cs | 13 + .../Tango.ScreenCapture/ScreenCaptureMethod.cs | 14 + .../Tango.ScreenCapture/Tango.ScreenCapture.csproj | 89 +++ .../Tango.ScreenCapture/VectorDifferenceImage.cs | 114 +++ .../Tango.ScreenCapture/VectorImagePixel.cs | 17 + .../VectorScreenCaptureFrame.cs | 17 + .../Tango.ScreenCapture/WpfApp1/App.config | 14 + .../Tango.ScreenCapture/WpfApp1/App.xaml | 9 + .../Tango.ScreenCapture/WpfApp1/App.xaml.cs | 17 + .../Tango.ScreenCapture/WpfApp1/MainWindow.xaml | 12 + .../Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs | 85 ++ .../WpfApp1/Properties/AssemblyInfo.cs | 55 ++ .../WpfApp1/Properties/Resources.Designer.cs | 71 ++ .../WpfApp1/Properties/Resources.resx | 117 +++ .../WpfApp1/Properties/Settings.Designer.cs | 30 + .../WpfApp1/Properties/Settings.settings | 7 + .../Tango.ScreenCapture/WpfApp1/WpfApp1.csproj | 109 +++ .../Tango.ScreenCapture/app.config | 11 + .../Tango.RemoteDesktop/WpfApp1/App.config | 14 + .../Tango.RemoteDesktop/WpfApp1/App.xaml | 9 + .../Tango.RemoteDesktop/WpfApp1/App.xaml.cs | 17 + .../Tango.RemoteDesktop/WpfApp1/MainWindow.xaml | 12 + .../Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs | 88 +++ .../WpfApp1/Properties/AssemblyInfo.cs | 55 ++ .../WpfApp1/Properties/Resources.Designer.cs | 71 ++ .../WpfApp1/Properties/Resources.resx | 117 +++ .../WpfApp1/Properties/Settings.Designer.cs | 30 + .../WpfApp1/Properties/Settings.settings | 7 + .../Tango.RemoteDesktop/WpfApp1/WpfApp1.csproj | 105 +++ .../Tango.RemoteDesktop/WpfApp2/App.config | 14 + .../Tango.RemoteDesktop/WpfApp2/App.xaml | 9 + .../Tango.RemoteDesktop/WpfApp2/App.xaml.cs | 17 + .../Tango.RemoteDesktop/WpfApp2/MainWindow.xaml | 12 + .../Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs | 85 ++ .../WpfApp2/Properties/AssemblyInfo.cs | 55 ++ .../WpfApp2/Properties/Resources.Designer.cs | 71 ++ .../WpfApp2/Properties/Resources.resx | 117 +++ .../WpfApp2/Properties/Settings.Designer.cs | 30 + .../WpfApp2/Properties/Settings.settings | 7 + .../Tango.RemoteDesktop/WpfApp2/WpfApp2.csproj | 105 +++ 86 files changed, 6708 insertions(+) create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop.sln create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Properties/AssemblyInfo.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/FastBitmap.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/BitBltScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CaptureRegion.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CursorUtils.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DXScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectBitmap.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectXScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/FastBitmap.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/GdiScreenCapture.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureMethod.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparerResult.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Properties/AssemblyInfo.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/RasterScreenCaptureFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureEngine.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureMethod.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Tango.ScreenCapture.csproj create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorDifferenceImage.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorImagePixel.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorScreenCaptureFrame.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.config create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj create mode 100644 Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/app.config create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.config create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/AssemblyInfo.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.Designer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.resx create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.Designer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.settings create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp1/WpfApp1.csproj create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.config create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/AssemblyInfo.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.Designer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.resx create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.Designer.cs create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.settings create mode 100644 Software/Experiments/Tango.RemoteDesktop/WpfApp2/WpfApp2.csproj (limited to 'Software/Experiments') diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop.sln b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop.sln new file mode 100644 index 000000000..303cacf0a --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.645 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.RemoteDesktop", "Tango.RemoteDesktop\Tango.RemoteDesktop.csproj", "{A78068D4-2061-4376-8EDE-583D8D880DEC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp1", "WpfApp1\WpfApp1.csproj", "{8F28CF3A-9B97-4463-B245-709D11B0AD7F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.ScreenCapture", "Tango.ScreenCapture\Tango.ScreenCapture.csproj", "{B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp2", "WpfApp2\WpfApp2.csproj", "{24009DF8-1CBB-4A38-9A12-3DB32516C204}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|Any CPU.Build.0 = Release|Any CPU + {8F28CF3A-9B97-4463-B245-709D11B0AD7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8F28CF3A-9B97-4463-B245-709D11B0AD7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8F28CF3A-9B97-4463-B245-709D11B0AD7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F28CF3A-9B97-4463-B245-709D11B0AD7F}.Release|Any CPU.Build.0 = Release|Any CPU + {B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}.Release|Any CPU.Build.0 = Release|Any CPU + {24009DF8-1CBB-4A38-9A12-3DB32516C204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24009DF8-1CBB-4A38-9A12-3DB32516C204}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24009DF8-1CBB-4A38-9A12-3DB32516C204}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24009DF8-1CBB-4A38-9A12-3DB32516C204}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6CC2E7A9-635F-43BC-855B-A9C43C333DE5} + EndGlobalSection +EndGlobal diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs new file mode 100644 index 000000000..c41ccbe03 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.CaptureMethods +{ + public class BitBltScreenCapture : ICaptureMethod + { + #region Win32 API Screen shot calls + + // Win32 API calls necessary to support screen capture + [DllImport("gdi32", EntryPoint = "BitBlt", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int BitBlt(int hDestDC, int x, int y, int nWidth, int nHeight, int hSrcDC, int xSrc, + int ySrc, int dwRop); + + [DllImport("user32", EntryPoint = "GetDC", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int GetDC(int hwnd); + + [DllImport("user32", EntryPoint = "ReleaseDC", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int ReleaseDC(int hwnd, int hdc); + + #endregion + + public Bitmap GetDesktopBitmap(CaptureRegion region) + { + const int SRCCOPY = 13369376; + + Bitmap bitmap = new Bitmap(region.Width, region.Height); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + // Get a device context to the windows desktop and our destination bitmaps + int hdcSrc = GetDC(0); + IntPtr hdcDest = g.GetHdc(); + + // Copy what is on the desktop to the bitmap + BitBlt(hdcDest.ToInt32(), 0, 0, region.Width, region.Height, hdcSrc, 0, 0, SRCCOPY); + + // Release device contexts + g.ReleaseHdc(hdcDest); + ReleaseDC(0, hdcSrc); + } + + return bitmap; + } + + public void Dispose() + { + //Do nothing. + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs new file mode 100644 index 000000000..50cb9dc76 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using Device = SharpDX.Direct3D11.Device; +using MapFlags = SharpDX.Direct3D11.MapFlags; + +namespace Tango.RemoteDesktop.CaptureMethods +{ + public class DirectXScreenCapture : ICaptureMethod + { + private static bool _hasInstance; + private Device device; + private Output1 output1; + private Texture2DDescription textureDesc; + private OutputDuplication duplicatedOutput; + private int monitorWidth; + private int monitorHeight; + + public int AdapterIndex { get; set; } + + public int MonitorIndex { get; set; } + + public DirectXScreenCapture() + { + if (_hasInstance) + { + throw new InvalidOperationException("An instance of the DirectX screen capture method already exists. Please dispose it before attempting to create a new one."); + } + + // Create DXGI Factory1 + var factory = new Factory1(); + var adapter = factory.GetAdapter1(AdapterIndex); + + // Create device from Adapter + device = new Device(adapter); + + // Get DXGI.Output + var output = adapter.GetOutput(MonitorIndex); + output1 = output.QueryInterface(); + + // Width/Height of desktop to capture + monitorWidth = ((SharpDX.Rectangle)output.Description.DesktopBounds).Width; + monitorHeight = ((SharpDX.Rectangle)output.Description.DesktopBounds).Height; + + // Create Staging texture CPU-accessible + textureDesc = new Texture2DDescription + { + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = monitorWidth, + Height = monitorHeight, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; + + // Duplicate the output + duplicatedOutput = output1.DuplicateOutput(device); + + _hasInstance = true; + } + + public virtual Bitmap GetDesktopBitmap(CaptureRegion region) + { + var screenTexture = new Texture2D(device, textureDesc); + + SharpDX.DXGI.Resource screenResource; + OutputDuplicateFrameInformation duplicateFrameInformation; + + // Try to get duplicated frame within given time + duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource); + + // copy resource into memory that can be accessed by the CPU + using (var screenTexture2D = screenResource.QueryInterface()) + device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); + + // Get the desktop capture texture + var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None); + + // Create Drawing.Bitmap + var bitmap = new Bitmap(monitorWidth, monitorHeight, System.Drawing.Imaging.PixelFormat.Format32bppRgb); + var boundsRect = new System.Drawing.Rectangle(0, 0, monitorWidth, monitorHeight); + + // Copy pixels from screen capture Texture to GDI bitmap + var mapDest = bitmap.LockBits(boundsRect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat); + var sourcePtr = mapSource.DataPointer; + var destPtr = mapDest.Scan0; + for (int y = 0; y < region.Height; y++) + { + // Copy a single line + Utilities.CopyMemory(destPtr, sourcePtr, monitorWidth * 4); + + // Advance pointers + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + destPtr = IntPtr.Add(destPtr, mapDest.Stride); + } + + // Release source and dest locks + bitmap.UnlockBits(mapDest); + device.ImmediateContext.UnmapSubresource(screenTexture, 0); + + if (region.Left > 0 || region.Top > 0 || region.Width < monitorWidth || region.Height < monitorHeight) + { + var cropped = CropImage(bitmap, new System.Drawing.Rectangle(region.Left, region.Top, region.Width, region.Height)); + bitmap.Dispose(); + bitmap = cropped; + } + + screenTexture.Dispose(); + screenResource.Dispose(); + duplicatedOutput.ReleaseFrame(); + + return bitmap; + } + + private Bitmap CropImage(Bitmap img, System.Drawing.Rectangle cropArea) + { + Bitmap bmpImage = new Bitmap(img); + return bmpImage.Clone(cropArea, bmpImage.PixelFormat); + } + + public void Dispose() + { + _hasInstance = false; + duplicatedOutput.Dispose(); + device.Dispose(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs new file mode 100644 index 000000000..a9d228e23 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.CaptureMethods +{ + public class GdiScreenCapture : ICaptureMethod + { + public Bitmap GetDesktopBitmap(CaptureRegion region) + { + var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CopyFromScreen(region.Left, region.Top, 0, 0, bitmap.Size, System.Drawing.CopyPixelOperation.SourceCopy); + } + + return bitmap; + } + + public void Dispose() + { + //Do nothing + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs new file mode 100644 index 000000000..18e48de4e --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace Tango.RemoteDesktop +{ + public class CaptureRegion + { + public int Left { get; set; } + public int Top { get; set; } + public int Width { get; set; } + public int Height { get; set; } + + public CaptureRegion() + { + + } + + public CaptureRegion(Rectangle rect) + { + Left = rect.Left; + Top = rect.Top; + Width = rect.Width; + Height = rect.Height; + } + + public CaptureRegion(Rect rect) + { + Left = (int)rect.Left; + Top = (int)rect.Top; + Width = (int)rect.Width; + Height = (int)rect.Height; + } + + public CaptureRegion(int left, int top, int width, int height) + { + Left = left; + Top = top; + Width = width; + Height = height; + } + + public static CaptureRegion PrimaryScreenBounds() + { + return new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs new file mode 100644 index 000000000..fc9de34e1 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Frames; + +namespace Tango.RemoteDesktop.Comparers +{ + public class RasterBitmapComparer : IBitmapComparer + { + unsafe public RasterFrame CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap) + { + if (previousBitmap == null | currentBitmap == null) + throw new InvalidOperationException("Cannot compare image. They are the same instance"); + + if (previousBitmap.Height != currentBitmap.Height || previousBitmap.Width != currentBitmap.Width) + throw new InvalidOperationException("Cannot compare image of different size."); + + Color matchColor = Color.Transparent; + + Bitmap diffImage = currentBitmap.Clone() as Bitmap; + + int height = previousBitmap.Height; + int width = previousBitmap.Width; + + BitmapData data1 = previousBitmap.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData data2 = currentBitmap.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData diffData = diffImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + + byte* data1Ptr = (byte*)data1.Scan0; + byte* data2Ptr = (byte*)data2.Scan0; + byte* diffPtr = (byte*)diffData.Scan0; + + byte[] swapColor = new byte[4]; + swapColor[0] = matchColor.B; + swapColor[1] = matchColor.G; + swapColor[2] = matchColor.R; + swapColor[3] = matchColor.A; + + int rowPadding = data1.Stride - (previousBitmap.Width * 4); + + // iterate over height (rows) + for (int i = 0; i < height; i++) + { + // iterate over width (columns) + for (int j = 0; j < width; j++) + { + int same = 0; + + byte[] tmp = new byte[4]; + + // compare pixels and copy new values into temporary array + for (int x = 0; x < 4; x++) + { + tmp[x] = data2Ptr[0]; + if (data1Ptr[0] == data2Ptr[0]) + { + same++; + } + data1Ptr++; // advance image1 ptr + data2Ptr++; // advance image2 ptr + } + + // swap color or add new values + for (int x = 0; x < 4; x++) + { + diffPtr[0] = (same == 4) ? swapColor[x] : tmp[x]; + diffPtr++; // advance diff image ptr + } + } + + // at the end of each column, skip extra padding + if (rowPadding > 0) + { + data1Ptr += rowPadding; + data2Ptr += rowPadding; + diffPtr += rowPadding; + } + } + + previousBitmap.UnlockBits(data1); + currentBitmap.UnlockBits(data2); + diffImage.UnlockBits(diffData); + + return new RasterFrame(diffImage); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs new file mode 100644 index 000000000..9ffd04c0b --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Frames; + +namespace Tango.RemoteDesktop.Comparers +{ + public class VectorBitmapComparer : IBitmapComparer + { + public unsafe VectorFrame CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap) + { + VectorFrame vector = new VectorFrame(previousBitmap.Width, previousBitmap.Height); + + if (previousBitmap == null | currentBitmap == null) + throw new InvalidOperationException("Cannot compare image. They are the same instance"); + + if (previousBitmap.Height != currentBitmap.Height || previousBitmap.Width != currentBitmap.Width) + throw new InvalidOperationException("Cannot compare image of different size."); + + int height = previousBitmap.Height; + int width = previousBitmap.Width; + + BitmapData data1 = previousBitmap.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData data2 = currentBitmap.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + byte* data1Ptr = (byte*)data1.Scan0; + byte* data2Ptr = (byte*)data2.Scan0; + + int rowPadding = data1.Stride - (previousBitmap.Width * 4); + + // iterate over height (rows) + for (int i = 0; i < height; i++) + { + // iterate over width (columns) + for (int j = 0; j < width; j++) + { + int same = 0; + + byte[] tmp = new byte[4]; + + // compare pixels and copy new values into temporary array + for (int x = 0; x < 4; x++) + { + tmp[x] = data2Ptr[0]; + if (data1Ptr[0] == data2Ptr[0]) + { + same++; + } + data1Ptr++; // advance image1 ptr + data2Ptr++; // advance image2 ptr + } + + if (same != 4) + { + vector.Pixels.Add(new VectorFramePixel() + { + X = (ushort)j, + Y = (ushort)i, + B = tmp[0], + G = tmp[1], + R = tmp[2], + }); + } + } + + // at the end of each column, skip extra padding + if (rowPadding > 0) + { + data1Ptr += rowPadding; + data2Ptr += rowPadding; + } + } + + previousBitmap.UnlockBits(data1); + currentBitmap.UnlockBits(data2); + + return vector; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs new file mode 100644 index 000000000..55fba9d59 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Encoders +{ + public class BitmapEncoder : FrameEncoder + { + public BitmapEncoder(IFrame frame) : base(frame) + { + } + + public override MemoryStream ToStream() + { + MemoryStream ms = new MemoryStream(); + Frame.ToBitmap().Save(ms, ImageFormat.Bmp); + return ms; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs new file mode 100644 index 000000000..467e37662 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Encoders +{ + public class JpegEncoder : FrameEncoder + { + public JpegEncoder(IFrame frame) : base(frame) + { + } + + public override MemoryStream ToStream() + { + return ToStream(100); + } + + public MemoryStream ToStream(long quality) + { + var encoderParameters = new EncoderParameters(1); + encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); + MemoryStream ms = new MemoryStream(); + Frame.ToBitmap().Save(ms, GetEncoder(ImageFormat.Jpeg), encoderParameters); + ms.Position = 0; + return ms; + } + + public byte[] ToArray(long quality) + { + using (MemoryStream ms = ToStream(quality)) + { + return ms.ToArray(); + } + } + + private ImageCodecInfo GetEncoder(ImageFormat format) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + return codecs.Single(codec => codec.FormatID == format.Guid); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs new file mode 100644 index 000000000..dead40302 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Encoders +{ + public class PngEncoder : FrameEncoder + { + public PngEncoder(IFrame frame) : base(frame) + { + } + + public override MemoryStream ToStream() + { + MemoryStream ms = new MemoryStream(); + Frame.ToBitmap().Save(ms, ImageFormat.Png); + return ms; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs new file mode 100644 index 000000000..3cd2a4e84 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Comparers; +using Tango.RemoteDesktop.Frames; + +namespace Tango.RemoteDesktop.Engines +{ + public class RasterScreenCaptureEngine : ScreenCaptureEngine + { + public RasterScreenCaptureEngine() : base(new RasterBitmapComparer()) + { + + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs new file mode 100644 index 000000000..e065cc6e5 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Comparers; +using Tango.RemoteDesktop.Frames; + +namespace Tango.RemoteDesktop.Engines +{ + public class VectorScreenCaptureEngine : ScreenCaptureEngine + { + public VectorScreenCaptureEngine() : base(new VectorBitmapComparer()) + { + + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs new file mode 100644 index 000000000..95e244a39 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.RemoteDesktop +{ + public abstract class Frame : IFrame + { + public abstract int Width { get; } + public abstract int Height { get; } + + public abstract Bitmap ToBitmap(); + + public void Apply(Bitmap bitmap) + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.DrawImage(ToBitmap(), new Point(0, 0)); + } + } + + public BitmapSource ToBitmapSource() + { + MemoryStream ms = new MemoryStream(); + ToBitmap().Save(ms, ImageFormat.Bmp); + ms.Position = 0; + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = ms; + bitmapImage.EndInit(); + bitmapImage.Freeze(); + return bitmapImage; + } + + public IFrameEncoder Encode() where T : IFrameEncoder + { + return Activator.CreateInstance(typeof(T), new object[] { this }) as IFrameEncoder; + } + + public abstract void Dispose(); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs new file mode 100644 index 000000000..183dcd1e8 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + public abstract class FrameEncoder : IFrameEncoder + { + public IFrame Frame { get; private set; } + + public FrameEncoder(IFrame frame) + { + Frame = frame; + } + + public abstract MemoryStream ToStream(); + + public byte[] ToArray() + { + using (var ms = ToStream()) + { + return ms.ToArray(); + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs new file mode 100644 index 000000000..45b84d1ef --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.RemoteDesktop.Frames +{ + public class RasterFrame : Frame + { + private Bitmap _bitmap; + + public override int Width + { + get { return _bitmap.Width; } + } + + public override int Height + { + get { return _bitmap.Height; } + } + + public RasterFrame(Bitmap bitmap) + { + _bitmap = bitmap; + } + + public override void Dispose() + { + _bitmap.Dispose(); + } + + public override Bitmap ToBitmap() + { + return _bitmap; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs new file mode 100644 index 000000000..72bcf53ea --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Utils; + +namespace Tango.RemoteDesktop.Frames +{ + public class VectorFrame : Frame + { + private int _width; + public override int Width + { + get { return _width; } + } + + private int _height; + public override int Height + { + get { return _height; } + } + + public List Pixels { get; private set; } + + public VectorFrame(int width, int height) + { + _width = width; + _height = height; + Pixels = new List(); + } + + public override Bitmap ToBitmap() + { + DirectBitmap directBitmap = new DirectBitmap(Width, Height); + foreach (var pixel in Pixels) + { + directBitmap.SetPixel((int)pixel.X, (int)pixel.Y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); + } + + directBitmap.Dispose(); + return directBitmap.Bitmap; + } + + public void ApplyUsingFastBitmap(Bitmap bitmap) + { + using (FastBitmap fast = bitmap.FastLock()) + { + foreach (var pixel in Pixels) + { + fast.SetPixel((int)pixel.X, (int)pixel.Y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); + } + } + } + + public byte[] Serialize() + { + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(ms)) + { + writer.Write(Width); + writer.Write(Height); + writer.Write(Pixels.Count); + + foreach (var pixel in Pixels) + { + writer.Write(pixel.X); + writer.Write(pixel.Y); + + writer.Write(pixel.R); + writer.Write(pixel.G); + writer.Write(pixel.B); + } + + ms.Position = 0; + return ms.ToArray(); + } + } + } + + public int CalculateSize() + { + int size = 0; + + size += sizeof(int) * 3; //Width, Height, Count + size += sizeof(ushort) * 2 * Pixels.Count; //X, Y + size += sizeof(byte) * 3 * Pixels.Count; //RGB + + return size; + } + + public static VectorFrame Deserialize(byte[] data) + { + using (MemoryStream ms = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(ms)) + { + ms.Position = 0; + var vector = new VectorFrame(reader.ReadInt32(), reader.ReadInt32()); + + int count = reader.ReadInt32(); + + for (int i = 0; i < count; i++) + { + VectorFramePixel pixel = new VectorFramePixel(); + pixel.X = reader.ReadUInt16(); + pixel.Y = reader.ReadUInt16(); + + pixel.R = reader.ReadByte(); + pixel.G = reader.ReadByte(); + pixel.B = reader.ReadByte(); + + vector.Pixels.Add(pixel); + } + + return vector; + } + } + } + + public override void Dispose() + { + Pixels.Clear(); + Pixels = null; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs new file mode 100644 index 000000000..048b7b94b --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Frames +{ + public struct VectorFramePixel + { + public ushort X { get; set; } + public ushort Y { get; set; } + public byte R { get; set; } + public byte G { get; set; } + public byte B { get; set; } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs new file mode 100644 index 000000000..f20edac98 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + public interface IBitmapComparer where T : IFrame + { + T CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs new file mode 100644 index 000000000..b93df2109 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + public interface ICaptureMethod : IDisposable + { + Bitmap GetDesktopBitmap(CaptureRegion region); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs new file mode 100644 index 000000000..a217d7189 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.RemoteDesktop +{ + /// + /// Represents an image frame. + /// + /// + public interface IFrame : IDisposable + { + /// + /// Gets the frame width. + /// + int Width { get; } + + /// + /// Gets the frame height. + /// + int Height { get; } + + /// + /// Returns a GDI bitmap representing the frame. + /// + /// + Bitmap ToBitmap(); + + /// + /// Returns a WPF BitmapSource representing the frame. + /// + /// + BitmapSource ToBitmapSource(); + + /// + /// Returns an instance of ready encode this frame. + /// + /// + /// + IFrameEncoder Encode() where T : IFrameEncoder; + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs new file mode 100644 index 000000000..946c90479 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + public interface IFrameEncoder + { + IFrame Frame { get; } + byte[] ToArray(); + MemoryStream ToStream(); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs new file mode 100644 index 000000000..17623180c --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + public interface IScreenCaptureEngine : IDisposable where TFrame : IFrame + { + event EventHandler> FrameReceived; + ICaptureMethod CaptureMethod { get; set; } + CaptureRegion CaptureRegion { get; set; } + bool IsStarted { get; } + TimeSpan Interval { get; set; } + bool CaptureCursor { get; set; } + IBitmapComparer Comparer { get; set; } + bool EnableComparer { get; set; } + void Start(); + void Stop(); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Properties/AssemblyInfo.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..7d2e42b5d --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Tango.RemoteDesktop")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tango.RemoteDesktop")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("a78068d4-2061-4376-8ede-583d8d880dec")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs new file mode 100644 index 000000000..2515815cb --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Utils; + +namespace Tango.RemoteDesktop +{ + public class ScreenCaptureEngine : IScreenCaptureEngine where TFrame : class, IFrame + { + private Thread _captureThread; + private Bitmap _previousBitmap; + private bool _isDisposed; + + public event EventHandler> FrameReceived; + + public ICaptureMethod CaptureMethod { get; set; } + public CaptureRegion CaptureRegion { get; set; } + public bool IsStarted { get; private set; } + public TimeSpan Interval { get; set; } + public bool CaptureCursor { get; set; } + public IBitmapComparer Comparer { get; set; } + public bool EnableComparer { get; set; } + + public ScreenCaptureEngine(IBitmapComparer comparer) + { + Interval = TimeSpan.FromMilliseconds(100); + CaptureMethod = new CaptureMethods.DirectXScreenCapture(); + CaptureRegion = new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); + Comparer = comparer; + CaptureCursor = true; + + if (Comparer != null) + { + EnableComparer = true; + } + } + + public void Start() + { + if (_isDisposed) + { + throw new ObjectDisposedException("Screen capture engine cannot be started after disposed."); + } + + if (!IsStarted) + { + IsStarted = true; + + _captureThread = new Thread(CaptureThreadMethod); + _captureThread.IsBackground = true; + _captureThread.Name = "Screen Capture Thread"; + _captureThread.Start(); + } + } + + private void CaptureThreadMethod() + { + while (IsStarted) + { + var bitmap = CaptureMethod.GetDesktopBitmap(CaptureRegion); + + if (CaptureCursor) + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + CursorUtils.ApplyCursor(g, bitmap, CaptureRegion.Left, CaptureRegion.Top); + } + } + + if (EnableComparer && Comparer != null) + { + if (_previousBitmap == null) + { + _previousBitmap = bitmap.Clone() as Bitmap; + OnFrameReceived(bitmap, null); + } + else + { + var diffFrame = Comparer.CreateDifference(_previousBitmap, bitmap); + _previousBitmap.Dispose(); + _previousBitmap = bitmap.Clone() as Bitmap; + OnFrameReceived(bitmap, diffFrame); + } + } + else + { + OnFrameReceived(bitmap, null); + } + + Thread.Sleep(Interval); + } + } + + public void Stop() + { + if (IsStarted) + { + IsStarted = false; + } + } + + public void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + Stop(); + CaptureMethod?.Dispose(); + } + } + + private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame) + { + FrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs() + { + Frame = new ScreenCaptureFrame(currentBitmap, diffFrame) + }); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs new file mode 100644 index 000000000..b45a9c9bc --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; +using Tango.RemoteDesktop.Frames; + +namespace Tango.RemoteDesktop +{ + public class ScreenCaptureFrame : RasterFrame, IDisposable where T : IFrame + { + private T _diffFrame; + + public ScreenCaptureFrame(Bitmap bitmap) : base(bitmap) + { + } + + public ScreenCaptureFrame(Bitmap bitmap, T diffFrame) : this(bitmap) + { + _diffFrame = diffFrame; + } + + public bool DifferenceAvailable + { + get { return _diffFrame != null; } + } + + public T ToDifference() + { + return _diffFrame; + } + + public override void Dispose() + { + base.Dispose(); + + if (_diffFrame != null) + { + _diffFrame.Dispose(); + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs new file mode 100644 index 000000000..5732744a4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + public class ScreenCaptureFrameReceivedEventArgs : EventArgs where T: IFrame + { + public ScreenCaptureFrame Frame { get; set; } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj new file mode 100644 index 000000000..35f797626 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj @@ -0,0 +1,95 @@ + + + + + Debug + AnyCPU + {A78068D4-2061-4376-8EDE-583D8D880DEC} + Library + Properties + Tango.RemoteDesktop + Tango.RemoteDesktop + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.dll + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Direct3D11.dll + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.DXGI.dll + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Mathematics.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs new file mode 100644 index 000000000..11bd4f6ff --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Utils +{ + public static class CursorUtils + { + #region INTERNALS + + internal static class User32 + { + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; + public Int32 xHotspot; + public Int32 yHotspot; + public IntPtr hbmMask; + public IntPtr hbmColor; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINT ptScreenPos; + } + + [DllImport("user32.dll")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); + + [DllImport("user32.dll")] + public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon); + + [DllImport("user32.dll")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + } + + #endregion + + #region Internal Classes + + [StructLayout(LayoutKind.Sequential)] + struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINTAPI ptScreenPos; + } + + [StructLayout(LayoutKind.Sequential)] + struct POINTAPI + { + public int x; + public int y; + } + + [DllImport("user32.dll")] + static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + const Int32 CURSOR_SHOWING = 0x00000001; + + internal struct SIZE + { + public int cx; + public int cy; + } + + internal class GDIStuff + { + #region Class Variables + public const int SRCCOPY = 13369376; + #endregion + + + #region Class Functions + [DllImport("gdi32.dll", EntryPoint = "CreateDC")] + public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData); + + [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] + public static extern IntPtr DeleteDC(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + public static extern IntPtr DeleteObject(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "BitBlt")] + public static extern bool BitBlt(IntPtr hdcDest, int xDest, + int yDest, int wDest, + int hDest, IntPtr hdcSource, + int xSrc, int ySrc, int RasterOp); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] + public static extern IntPtr CreateCompatibleBitmap + (IntPtr hdc, int nWidth, int nHeight); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll", EntryPoint = "SelectObject")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); + #endregion + } + + internal class Win32Stuff + { + + #region Class Variables + + public const int SM_CXSCREEN = 0; + public const int SM_CYSCREEN = 1; + + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies + public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot + public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot + public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, + public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this + } + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; // Specifies the size, in bytes, of the structure. + public Int32 flags; // Specifies the cursor state. This parameter can be one of the following values: + public IntPtr hCursor; // Handle to the cursor. + public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor. + } + + #endregion + + + #region Class Functions + + [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")] + public static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll", EntryPoint = "GetDC")] + public static extern IntPtr GetDC(IntPtr ptr); + + [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] + public static extern int GetSystemMetrics(int abc); + + [DllImport("user32.dll", EntryPoint = "GetWindowDC")] + public static extern IntPtr GetWindowDC(Int32 ptr); + + [DllImport("user32.dll", EntryPoint = "ReleaseDC")] + public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); + + + [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll", EntryPoint = "CopyIcon")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", EntryPoint = "GetIconInfo")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + + + #endregion + } + + #endregion + + [DebuggerHidden] + [DebuggerStepThrough] + public static void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top) + { + try + { + CURSORINFO pci; + pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); + + if (GetCursorInfo(out pci)) + { + if (pci.flags == CURSOR_SHOWING) + { + //var iconPointer = User32.CopyIcon(pci.hCursor); + //User32.ICONINFO iconInfo; + //User32.GetIconInfo(iconPointer, out iconInfo); + + var hdc = g.GetHdc(); + User32.DrawIconEx(hdc, pci.ptScreenPos.x - left, pci.ptScreenPos.y - top, pci.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); + + //User32.DestroyIcon(iconPointer); + GDIStuff.DeleteObject(pci.hCursor); + } + } + g.ReleaseHdc(); + } + catch { } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs new file mode 100644 index 000000000..fbcc5710c --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Utils +{ + public class DirectBitmap : IDisposable + { + public Bitmap Bitmap { get; private set; } + public Int32[] Bits { get; private set; } + public bool Disposed { get; private set; } + public int Height { get; private set; } + public int Width { get; private set; } + + protected GCHandle BitsHandle { get; private set; } + + public DirectBitmap(int width, int height) + { + Width = width; + Height = height; + Bits = new Int32[width * height]; + BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); + Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); + } + + public void SetPixel(int x, int y, Color colour) + { + int index = x + (y * Width); + int col = colour.ToArgb(); + + Bits[index] = col; + } + + public Color GetPixel(int x, int y) + { + int index = x + (y * Width); + int col = Bits[index]; + Color result = Color.FromArgb(col); + + return result; + } + + public void Dispose() + { + if (Disposed) return; + Disposed = true; + BitsHandle.Free(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/FastBitmap.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/FastBitmap.cs new file mode 100644 index 000000000..f32bd4b39 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/FastBitmap.cs @@ -0,0 +1,864 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Utils +{ + /// + /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images + /// + public unsafe class FastBitmap : IDisposable + { + /// + /// Specifies the number of bytes available per pixel of the bitmap object being manipulated + /// + public const int BytesPerPixel = 4; + + /// + /// The Bitmap object encapsulated on this FastBitmap + /// + private readonly Bitmap _bitmap; + + /// + /// The BitmapData resulted from the lock operation + /// + private BitmapData _bitmapData; + + /// + /// The first pixel of the bitmap + /// + private int* _scan0; + + /// + /// Gets the width of this FastBitmap object + /// + public int Width { get; } + + /// + /// Gets the height of this FastBitmap object + /// + public int Height { get; } + + /// + /// Gets the pointer to the first pixel of the bitmap + /// + public IntPtr Scan0 => _bitmapData.Scan0; + + /// + /// Gets the stride width (in int32-sized values) of the bitmap + /// + public int Stride { get; private set; } + + /// + /// Gets the stride width (in bytes) of the bitmap + /// + public int StrideInBytes { get; private set; } + + /// + /// Gets a boolean value that states whether this FastBitmap is currently locked in memory + /// + public bool Locked { get; private set; } + + /// + /// Gets an array of 32-bit color pixel values that represent this FastBitmap + /// + /// The locking operation required to extract the values off from the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + public int[] DataArray + { + get + { + bool unlockAfter = false; + if (!Locked) + { + Lock(); + unlockAfter = true; + } + + // Declare an array to hold the bytes of the bitmap + int bytes = Math.Abs(_bitmapData.Stride) * _bitmap.Height; + int[] argbValues = new int[bytes / BytesPerPixel]; + + // Copy the RGB values into the array + Marshal.Copy(_bitmapData.Scan0, argbValues, 0, bytes / BytesPerPixel); + + if (unlockAfter) + { + Unlock(); + } + + return argbValues; + } + } + + /// + /// Creates a new instance of the FastBitmap class with a specified Bitmap. + /// The bitmap provided must have a 32bpp depth + /// + /// The Bitmap object to encapsulate on this FastBitmap object + /// The bitmap provided does not have a 32bpp pixel format + public FastBitmap(Bitmap bitmap) + { + if (Image.GetPixelFormatSize(bitmap.PixelFormat) != 32) + { + throw new ArgumentException(@"The provided bitmap must have a 32bpp depth", nameof(bitmap)); + } + + _bitmap = bitmap; + + Width = bitmap.Width; + Height = bitmap.Height; + } + + /// + /// Disposes of this fast bitmap object and releases any pending resources. + /// The underlying bitmap is not disposes, and is unlocked, if currently locked + /// + public void Dispose() + { + if (Locked) + { + Unlock(); + } + } + + /// + /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, + /// an exception is thrown + /// + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The bitmap is already locked + /// The locking operation in the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + public FastBitmapLocker Lock() + { + return Lock((FastBitmapLockFormat)_bitmap.PixelFormat); + } + + /// + /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, + /// an exception is thrown. + /// + /// The provided pixel format should be a 32bpp format. + /// + /// A pixel format to use when locking the underlying bitmap + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The bitmap is already locked + /// The locking operation in the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat) + { + if (Locked) + { + throw new InvalidOperationException("Unlock must be called before a Lock operation"); + } + + return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat); + } + + /// + /// Locks the bitmap to start the bitmap operations + /// + /// The lock mode to use on the bitmap + /// A pixel format to use when locking the underlying bitmap + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The locking operation in the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + /// is not a 32bpp format + private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat) + { + var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height); + + return Lock(lockMode, rect, pixelFormat); + } + + /// + /// Locks the bitmap to start the bitmap operations + /// + /// The lock mode to use on the bitmap + /// The rectangle to lock + /// A pixel format to use when locking the underlying bitmap + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The provided region is invalid + /// The locking operation in the underlying bitmap failed + /// The bitmap region is already locked + /// is not a 32bpp format + private FastBitmapLocker Lock(ImageLockMode lockMode, Rectangle rect, PixelFormat pixelFormat) + { + // Lock the bitmap's bits + _bitmapData = _bitmap.LockBits(rect, lockMode, pixelFormat); + + _scan0 = (int*)_bitmapData.Scan0; + Stride = _bitmapData.Stride / BytesPerPixel; + StrideInBytes = _bitmapData.Stride; + + Locked = true; + + return new FastBitmapLocker(this); + } + + /// + /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked + /// beforehand, an exception is thrown + /// + /// The bitmap is already unlocked + /// The unlocking operation in the underlying bitmap failed + public void Unlock() + { + if (!Locked) + { + throw new InvalidOperationException("Lock must be called before an Unlock operation"); + } + + _bitmap.UnlockBits(_bitmapData); + + Locked = false; + } + + /// + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to set + /// The Y coordinate of the pixel to set + /// The new color of the pixel to set + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public void SetPixel(int x, int y, Color color) + { + SetPixel(x, y, color.ToArgb()); + } + + /// + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to set + /// The Y coordinate of the pixel to set + /// The new color of the pixel to set + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public void SetPixel(int x, int y, int color) + { + SetPixel(x, y, unchecked((uint)color)); + } + + /// + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to set + /// The Y coordinate of the pixel to set + /// The new color of the pixel to set + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public void SetPixel(int x, int y, uint color) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + *(uint*)(_scan0 + x + y * Stride) = color; + } + + /// + /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to get + /// The Y coordinate of the pixel to get + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public Color GetPixel(int x, int y) + { + return Color.FromArgb(GetPixelInt(x, y)); + } + + /// + /// Gets the pixel color at the given coordinates as an integer value. If the bitmap + /// was not locked beforehands, an exception is thrown + /// + /// The X coordinate of the pixel to get + /// The Y coordinate of the pixel to get + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public int GetPixelInt(int x, int y) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + return *(_scan0 + x + y * Stride); + } + + /// + /// Gets the pixel color at the given coordinates as an unsigned integer value. + /// If the bitmap was not locked beforehands, an exception is thrown + /// + /// The X coordinate of the pixel to get + /// The Y coordinate of the pixel to get + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public uint GetPixelUInt(int x, int y) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + return *((uint*)_scan0 + x + y * Stride); + } + + /// + /// Copies the contents of the given array of colors into this FastBitmap. + /// Throws an ArgumentException if the count of colors on the array mismatches the pixel count from this FastBitmap + /// + /// The array of colors to copy + /// Whether to ignore zeroes when copying the data + public void CopyFromArray(int[] colors, bool ignoreZeroes = false) + { + if (colors.Length != Width * Height) + { + throw new ArgumentException(@"The number of colors of the given array mismatch the pixel count of the bitmap", nameof(colors)); + } + + // Simply copy the argb values array + // ReSharper disable once InconsistentNaming + int* s0t = _scan0; + + fixed (int* source = colors) + { + // ReSharper disable once InconsistentNaming + int* s0s = source; + + int count = Width * Height; + + if (!ignoreZeroes) + { + // Unfold the loop + const int sizeBlock = 8; + int rem = count % sizeBlock; + + count /= sizeBlock; + + while (count-- > 0) + { + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + } + + while (rem-- > 0) + { + *(s0t++) = *(s0s++); + } + } + else + { + while (count-- > 0) + { + if (*(s0s) == 0) { s0t++; s0s++; continue; } + *(s0t++) = *(s0s++); + } + } + } + } + + /// + /// Clears the bitmap with the given color + /// + /// The color to clear the bitmap with + public void Clear(Color color) + { + Clear(color.ToArgb()); + } + + /// + /// Clears the bitmap with the given color + /// + /// The color to clear the bitmap with + public void Clear(int color) + { + bool unlockAfter = false; + if (!Locked) + { + Lock(); + unlockAfter = true; + } + + // Clear all the pixels + int count = Width * Height; + int* curScan = _scan0; + + // Uniform color pixel values can be mem-set straight away + int component = (color & 0xFF); + if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && component == ((color >> 24) & 0xFF)) + { + memset(_scan0, component, (ulong)(Height * Stride * BytesPerPixel)); + } + else + { + // Defines the ammount of assignments that the main while() loop is performing per loop. + // The value specified here must match the number of assignment statements inside that loop + const int assignsPerLoop = 8; + + int rem = count % assignsPerLoop; + count /= assignsPerLoop; + + while (count-- > 0) + { + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + } + while (rem-- > 0) + { + *(curScan++) = color; + } + + if (unlockAfter) + { + Unlock(); + } + } + } + + /// + /// Clears a square region of this image w/ a given color + /// + /// + /// + public void ClearRegion(Rectangle region, Color color) + { + ClearRegion(region, color.ToArgb()); + } + + /// + /// Clears a square region of this image w/ a given color + /// + /// + /// + public void ClearRegion(Rectangle region, int color) + { + var thisReg = new Rectangle(0, 0, Width, Height); + if (!region.IntersectsWith(thisReg)) + return; + + // If the region covers the entire image, use faster Clear(). + if (region == thisReg) + { + Clear(color); + return; + } + + int minX = region.X; + int maxX = region.X + region.Width; + + int minY = region.Y; + int maxY = region.Y + region.Height; + + // Bail out of optimization if there's too few rows to make this worth it + if (maxY - minY < 16) + { + for (int y = minY; y < maxY; y++) + { + for (int x = minX; x < maxX; x++) + { + *(_scan0 + x + y * Stride) = color; + } + } + return; + } + + ulong strideWidth = (ulong)region.Width * BytesPerPixel; + + // Uniform color pixel values can be mem-set straight away + int component = (color & 0xFF); + if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && + component == ((color >> 24) & 0xFF)) + { + for (int y = minY; y < maxY; y++) + { + memset(_scan0 + minX + y * Stride, component, strideWidth); + } + } + else + { + // Prepare a horizontal slice of pixels that will be copied over each horizontal row down. + var row = new int[region.Width]; + + fixed (int* pRow = row) + { + int count = region.Width; + int rem = count % 8; + count /= 8; + int* pSrc = pRow; + while (count-- > 0) + { + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + } + while (rem-- > 0) + { + *pSrc++ = color; + } + + var sx = _scan0 + minX; + for (int y = minY; y < maxY; y++) + { + memcpy(sx + y * Stride, pRow, strideWidth); + } + } + } + } + + /// + /// Copies a region of the source bitmap into this fast bitmap + /// + /// The source image to copy + /// The region on the source bitmap that will be copied over + /// The region on this fast bitmap that will be changed + /// The provided source bitmap is the same bitmap locked in this FastBitmap + public void CopyRegion(Bitmap source, Rectangle srcRect, Rectangle destRect) + { + // Throw exception when trying to copy same bitmap over + if (source == _bitmap) + { + throw new ArgumentException(@"Copying regions across the same bitmap is not supported", nameof(source)); + } + + var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); + var destBitmapRect = new Rectangle(0, 0, Width, Height); + + // Check if the rectangle configuration doesn't generate invalid states or does not affect the target image + if (srcRect.Width <= 0 || srcRect.Height <= 0 || destRect.Width <= 0 || destRect.Height <= 0 || + !srcBitmapRect.IntersectsWith(srcRect) || !destRect.IntersectsWith(destBitmapRect)) + return; + + // Find the areas of the first and second bitmaps that are going to be affected + srcBitmapRect = Rectangle.Intersect(srcRect, srcBitmapRect); + + // Clip the source rectangle on top of the destination rectangle in a way that clips out the regions of the original bitmap + // that will not be drawn on the destination bitmap for being out of bounds + srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(srcRect.X, srcRect.Y, destRect.Width, destRect.Height)); + + destBitmapRect = Rectangle.Intersect(destRect, destBitmapRect); + + // Clip the source bitmap region yet again here + srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(-destRect.X + srcRect.X, -destRect.Y + srcRect.Y, Width, Height)); + + // Calculate the rectangle containing the maximum possible area that is supposed to be affected by the copy region operation + int copyWidth = Math.Min(srcBitmapRect.Width, destBitmapRect.Width); + int copyHeight = Math.Min(srcBitmapRect.Height, destBitmapRect.Height); + + if (copyWidth == 0 || copyHeight == 0) + return; + + int srcStartX = srcBitmapRect.Left; + int srcStartY = srcBitmapRect.Top; + + int destStartX = destBitmapRect.Left; + int destStartY = destBitmapRect.Top; + + using (var fastSource = source.FastLock()) + { + ulong strideWidth = (ulong)copyWidth * BytesPerPixel; + + // Perform copies of whole pixel rows + for (int y = 0; y < copyHeight; y++) + { + int destX = destStartX; + int destY = destStartY + y; + + int srcX = srcStartX; + int srcY = srcStartY + y; + + long offsetSrc = (srcX + srcY * fastSource.Stride); + long offsetDest = (destX + destY * Stride); + + memcpy(_scan0 + offsetDest, fastSource._scan0 + offsetSrc, strideWidth); + } + } + } + + /// + /// Performs a copy operation of the pixels from the Source bitmap to the Target bitmap. + /// If the dimensions or pixel depths of both images don't match, the copy is not performed + /// + /// The bitmap to copy the pixels from + /// The bitmap to copy the pixels to + /// Whether the copy proceedure was successful + /// The provided source and target bitmaps are the same + public static bool CopyPixels(Bitmap source, Bitmap target) + { + if (source == target) + { + throw new ArgumentException(@"Copying pixels across the same bitmap is not supported", nameof(source)); + } + + if (source.Width != target.Width || source.Height != target.Height || source.PixelFormat != target.PixelFormat) + return false; + + using (FastBitmap fastSource = source.FastLock(), + fastTarget = target.FastLock()) + { + memcpy(fastTarget.Scan0, fastSource.Scan0, (ulong)(fastSource.Height * fastSource.Stride * BytesPerPixel)); + } + + return true; + } + + /// + /// Clears the given bitmap with the given color + /// + /// The bitmap to clear + /// The color to clear the bitmap with + public static void ClearBitmap(Bitmap bitmap, Color color) + { + ClearBitmap(bitmap, color.ToArgb()); + } + + /// + /// Clears the given bitmap with the given color + /// + /// The bitmap to clear + /// The color to clear the bitmap with + public static void ClearBitmap(Bitmap bitmap, int color) + { + using (var fb = bitmap.FastLock()) + { + fb.Clear(color); + } + } + + /// + /// Copies a region of the source bitmap to a target bitmap + /// + /// The source image to copy + /// The target image to be altered + /// The region on the source bitmap that will be copied over + /// The region on the target bitmap that will be changed + /// The provided source and target bitmaps are the same bitmap + public static void CopyRegion(Bitmap source, Bitmap target, Rectangle srcRect, Rectangle destRect) + { + var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); + var destBitmapRect = new Rectangle(0, 0, target.Width, target.Height); + + // If the copy operation results in an entire copy, use CopyPixels instead + if (srcBitmapRect == srcRect && destBitmapRect == destRect && srcBitmapRect == destBitmapRect) + { + CopyPixels(source, target); + return; + } + + using (var fastTarget = target.FastLock()) + { + fastTarget.CopyRegion(source, srcRect, destRect); + } + } + + /// + /// Returns a bitmap that is a slice of the original provided 32bpp Bitmap. + /// The region must have a width and a height > 0, and must lie inside the source bitmap's area + /// + /// The source bitmap to slice + /// The region of the source bitmap to slice + /// A Bitmap that represents the rectangle region slice of the source bitmap + /// The provided bimap is not 32bpp + /// The provided region is invalid + public static Bitmap SliceBitmap(Bitmap source, Rectangle region) + { + if (region.Width <= 0 || region.Height <= 0) + { + throw new ArgumentException(@"The provided region must have a width and a height > 0", nameof(region)); + } + + var sliceRectangle = Rectangle.Intersect(new Rectangle(Point.Empty, source.Size), region); + + if (sliceRectangle.IsEmpty) + { + throw new ArgumentException(@"The provided region must not lie outside of the bitmap's region completely", nameof(region)); + } + + var slicedBitmap = new Bitmap(sliceRectangle.Width, sliceRectangle.Height); + CopyRegion(source, slicedBitmap, sliceRectangle, new Rectangle(0, 0, sliceRectangle.Width, sliceRectangle.Height)); + + return slicedBitmap; + } + +#if NETSTANDARD + public static void memcpy(IntPtr dest, IntPtr src, ulong count) + { + Buffer.MemoryCopy(src.ToPointer(), dest.ToPointer(), count, count); + } + + public static void memcpy(void* dest, void* src, ulong count) + { + Buffer.MemoryCopy(src, dest, count, count); + } + + public static void memset(void* dest, int value, ulong count) + { + Unsafe.InitBlock(dest, (byte)value, (uint)count); + } +#else + /// + /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed + /// + [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count); + + /// + /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed + /// + [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memcpy(void* dest, void* src, ulong count); + + /// + /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed + /// + [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memset(void* dest, int value, ulong count); +#endif + + /// + /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls + /// + public struct FastBitmapLocker : IDisposable + { + /// + /// Gets the fast bitmap instance attached to this locker + /// + public FastBitmap FastBitmap { get; } + + /// + /// Initializes a new instance of the FastBitmapLocker struct with an initial fast bitmap object. + /// The fast bitmap object passed will be unlocked after calling Dispose() on this struct + /// + /// A fast bitmap to attach to this locker which will be released after a call to Dispose + public FastBitmapLocker(FastBitmap fastBitmap) + { + FastBitmap = fastBitmap; + } + + /// + /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap + /// + public void Dispose() + { + if (FastBitmap.Locked) + FastBitmap.Unlock(); + } + } + } + + /// + /// Describes a pixel format to use when locking a bitmap using . + /// + public enum FastBitmapLockFormat + { + /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, and blue components. The remaining 8 bits are not used. + Format32bppRgb = 139273, + /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component. + Format32bppPArgb = 925707, + /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. + Format32bppArgb = 2498570, + } + + /// + /// Static class that contains fast bitmap extension methdos for the Bitmap class + /// + public static class FastBitmapExtensions + { + /// + /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels + /// + /// The bitmap to lock + /// A locked FastBitmap + public static FastBitmap FastLock(this Bitmap bitmap) + { + var fast = new FastBitmap(bitmap); + fast.Lock(); + + return fast; + } + + /// + /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels + /// + /// The bitmap to lock + /// The underlying pixel format to use when locking the bitmap + /// A locked FastBitmap + public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat) + { + var fast = new FastBitmap(bitmap); + fast.Lock(lockFormat); + + return fast; + } + + /// + /// Returns a deep clone of this Bitmap object, with all the data copied over. + /// After a deep clone, the new bitmap is completely independent from the original + /// + /// The bitmap to clone + /// A deep clone of this Bitmap object, with all the data copied over + public static Bitmap DeepClone(this Bitmap bitmap) + { + return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/BitBltScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/BitBltScreenCapture.cs new file mode 100644 index 000000000..bd2386387 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/BitBltScreenCapture.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class BitBltScreenCapture : IScreenCaptureMethod + { + #region Win32 API Screen shot calls + + // Win32 API calls necessary to support screen capture + [DllImport("gdi32", EntryPoint = "BitBlt", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int BitBlt(int hDestDC, int x, int y, int nWidth, int nHeight, int hSrcDC, int xSrc, + int ySrc, int dwRop); + + [DllImport("user32", EntryPoint = "GetDC", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int GetDC(int hwnd); + + [DllImport("user32", EntryPoint = "ReleaseDC", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int ReleaseDC(int hwnd, int hdc); + + #endregion + + public Bitmap GetDesktopBitmap(CaptureRegion region) + { + const int SRCCOPY = 13369376; + + Bitmap bitmap = new Bitmap(region.Width, region.Height); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + // Get a device context to the windows desktop and our destination bitmaps + int hdcSrc = GetDC(0); + IntPtr hdcDest = g.GetHdc(); + + // Copy what is on the desktop to the bitmap + BitBlt(hdcDest.ToInt32(), 0, 0, region.Width, region.Height, hdcSrc, 0, 0, SRCCOPY); + + // Release device contexts + g.ReleaseHdc(hdcDest); + ReleaseDC(0, hdcSrc); + } + + return bitmap; + } + + public void Dispose() + { + //Do nothing. + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CaptureRegion.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CaptureRegion.cs new file mode 100644 index 000000000..69f29ed03 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CaptureRegion.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; + +namespace Tango.ScreenCapture +{ + public class CaptureRegion + { + public int Left { get; set; } + public int Top { get; set; } + public int Width { get; set; } + public int Height { get; set; } + + public CaptureRegion() + { + + } + + public CaptureRegion(Rectangle rect) + { + Left = rect.Left; + Top = rect.Top; + Width = rect.Width; + Height = rect.Height; + } + + public CaptureRegion(Rect rect) + { + Left = (int)rect.Left; + Top = (int)rect.Top; + Width = (int)rect.Width; + Height = (int)rect.Height; + } + + public CaptureRegion(int left, int top, int width, int height) + { + Left = left; + Top = top; + Width = width; + Height = height; + } + + public static CaptureRegion PrimaryScreenBounds() + { + return new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CursorUtils.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CursorUtils.cs new file mode 100644 index 000000000..db47839a3 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CursorUtils.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public static class CursorUtils + { + #region INTERNALS + + internal static class User32 + { + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; + public Int32 xHotspot; + public Int32 yHotspot; + public IntPtr hbmMask; + public IntPtr hbmColor; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINT ptScreenPos; + } + + [DllImport("user32.dll")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); + + [DllImport("user32.dll")] + public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon); + + [DllImport("user32.dll")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + } + + #endregion + + #region Internal Classes + + [StructLayout(LayoutKind.Sequential)] + struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINTAPI ptScreenPos; + } + + [StructLayout(LayoutKind.Sequential)] + struct POINTAPI + { + public int x; + public int y; + } + + [DllImport("user32.dll")] + static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + const Int32 CURSOR_SHOWING = 0x00000001; + + internal struct SIZE + { + public int cx; + public int cy; + } + + internal class GDIStuff + { + #region Class Variables + public const int SRCCOPY = 13369376; + #endregion + + + #region Class Functions + [DllImport("gdi32.dll", EntryPoint = "CreateDC")] + public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData); + + [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] + public static extern IntPtr DeleteDC(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + public static extern IntPtr DeleteObject(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "BitBlt")] + public static extern bool BitBlt(IntPtr hdcDest, int xDest, + int yDest, int wDest, + int hDest, IntPtr hdcSource, + int xSrc, int ySrc, int RasterOp); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] + public static extern IntPtr CreateCompatibleBitmap + (IntPtr hdc, int nWidth, int nHeight); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll", EntryPoint = "SelectObject")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); + #endregion + } + + internal class Win32Stuff + { + + #region Class Variables + + public const int SM_CXSCREEN = 0; + public const int SM_CYSCREEN = 1; + + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies + public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot + public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot + public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, + public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this + } + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; // Specifies the size, in bytes, of the structure. + public Int32 flags; // Specifies the cursor state. This parameter can be one of the following values: + public IntPtr hCursor; // Handle to the cursor. + public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor. + } + + #endregion + + + #region Class Functions + + [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")] + public static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll", EntryPoint = "GetDC")] + public static extern IntPtr GetDC(IntPtr ptr); + + [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] + public static extern int GetSystemMetrics(int abc); + + [DllImport("user32.dll", EntryPoint = "GetWindowDC")] + public static extern IntPtr GetWindowDC(Int32 ptr); + + [DllImport("user32.dll", EntryPoint = "ReleaseDC")] + public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); + + + [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll", EntryPoint = "CopyIcon")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", EntryPoint = "GetIconInfo")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + + + #endregion + } + + #endregion + + [DebuggerHidden] + [DebuggerStepThrough] + public static void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top) + { + try + { + CURSORINFO pci; + pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); + + if (GetCursorInfo(out pci)) + { + if (pci.flags == CURSOR_SHOWING) + { + //var iconPointer = User32.CopyIcon(pci.hCursor); + //User32.ICONINFO iconInfo; + //User32.GetIconInfo(iconPointer, out iconInfo); + + var hdc = g.GetHdc(); + User32.DrawIconEx(hdc, pci.ptScreenPos.x - left, pci.ptScreenPos.y - top, pci.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); + + //User32.DestroyIcon(iconPointer); + GDIStuff.DeleteObject(pci.hCursor); + } + } + g.ReleaseHdc(); + } + catch { } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DXScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DXScreenCapture.cs new file mode 100644 index 000000000..77428fd8d --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DXScreenCapture.cs @@ -0,0 +1,436 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using Device = SharpDX.Direct3D11.Device; +using MapFlags = SharpDX.Direct3D11.MapFlags; +using System.IO; +using System.Windows.Media.Imaging; +using System.Runtime.InteropServices; +using System.Windows.Media; +using System.Runtime.ExceptionServices; +using System.Diagnostics; + +namespace Tango.RemoteDesktop +{ + public class DXScreenCapture : IDisposable + { + #region INTERNALS + + internal static class User32 + { + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; + public Int32 xHotspot; + public Int32 yHotspot; + public IntPtr hbmMask; + public IntPtr hbmColor; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINT ptScreenPos; + } + + [DllImport("user32.dll")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); + + [DllImport("user32.dll")] + public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon); + + [DllImport("user32.dll")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + } + + #endregion + + #region Internal Classes + + [StructLayout(LayoutKind.Sequential)] + struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINTAPI ptScreenPos; + } + + [StructLayout(LayoutKind.Sequential)] + struct POINTAPI + { + public int x; + public int y; + } + + [DllImport("user32.dll")] + static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + const Int32 CURSOR_SHOWING = 0x00000001; + + internal struct SIZE + { + public int cx; + public int cy; + } + + internal class GDIStuff + { + #region Class Variables + public const int SRCCOPY = 13369376; + #endregion + + + #region Class Functions + [DllImport("gdi32.dll", EntryPoint = "CreateDC")] + public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData); + + [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] + public static extern IntPtr DeleteDC(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + public static extern IntPtr DeleteObject(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "BitBlt")] + public static extern bool BitBlt(IntPtr hdcDest, int xDest, + int yDest, int wDest, + int hDest, IntPtr hdcSource, + int xSrc, int ySrc, int RasterOp); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] + public static extern IntPtr CreateCompatibleBitmap + (IntPtr hdc, int nWidth, int nHeight); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll", EntryPoint = "SelectObject")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); + #endregion + } + + internal class Win32Stuff + { + + #region Class Variables + + public const int SM_CXSCREEN = 0; + public const int SM_CYSCREEN = 1; + + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies + public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot + public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot + public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, + public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this + } + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; // Specifies the size, in bytes, of the structure. + public Int32 flags; // Specifies the cursor state. This parameter can be one of the following values: + public IntPtr hCursor; // Handle to the cursor. + public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor. + } + + #endregion + + + #region Class Functions + + [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")] + public static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll", EntryPoint = "GetDC")] + public static extern IntPtr GetDC(IntPtr ptr); + + [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] + public static extern int GetSystemMetrics(int abc); + + [DllImport("user32.dll", EntryPoint = "GetWindowDC")] + public static extern IntPtr GetWindowDC(Int32 ptr); + + [DllImport("user32.dll", EntryPoint = "ReleaseDC")] + public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); + + + [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll", EntryPoint = "CopyIcon")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", EntryPoint = "GetIconInfo")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + + + #endregion + } + + #endregion + + // # of graphics card adapter + private int numAdapter = 0; + + // # of output device (i.e. monitor) + private int numOutput = 0; + + private Device device; + + private Output1 output1; + + private Texture2DDescription textureDesc; + + private OutputDuplication duplicatedOutput; + + private int widthDX; + + private int heightDX; + + public void Initialize() + { + // Create DXGI Factory1 + var factory = new Factory1(); + var adapter = factory.GetAdapter1(numAdapter); + + // Create device from Adapter + device = new Device(adapter); + + // Get DXGI.Output + var output = adapter.GetOutput(numOutput); + output1 = output.QueryInterface(); + + // Width/Height of desktop to capture + widthDX = ((Rectangle)output.Description.DesktopBounds).Width; + heightDX = ((Rectangle)output.Description.DesktopBounds).Height; + + // Create Staging texture CPU-accessible + textureDesc = new Texture2DDescription + { + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = widthDX, + Height = heightDX, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; + + // Duplicate the output + duplicatedOutput = output1.DuplicateOutput(device); + } + + /// + /// Captures the bitmap source. + /// + /// The left. + /// The top. + /// The width. + /// The height. + /// Append cursor. + /// + [HandleProcessCorruptedStateExceptions] + [DebuggerStepThrough] + [DebuggerHidden] + public BitmapSource CaptureBitmapSource(int left, int top, int width, int height, bool captureCursor) + { + var screenTexture = new Texture2D(device, textureDesc); + + try + { + SharpDX.DXGI.Resource screenResource; + OutputDuplicateFrameInformation duplicateFrameInformation; + + // Try to get duplicated frame within given time + duplicatedOutput.AcquireNextFrame(5, out duplicateFrameInformation, out screenResource); + + //if (i > 0) + //{ + // copy resource into memory that can be accessed by the CPU + using (var screenTexture2D = screenResource.QueryInterface()) + device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); + + // Get the desktop capture texture + var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None); + + // Create Drawing.Bitmap + var bitmap = new System.Drawing.Bitmap(widthDX, heightDX, System.Drawing.Imaging.PixelFormat.Format32bppRgb); + var boundsRect = new System.Drawing.Rectangle(0, 0, widthDX, heightDX); + + // Copy pixels from screen capture Texture to GDI bitmap + var mapDest = bitmap.LockBits(boundsRect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat); + var sourcePtr = mapSource.DataPointer; + var destPtr = mapDest.Scan0; + for (int y = 0; y < height; y++) + { + // Copy a single line + Utilities.CopyMemory(destPtr, sourcePtr, widthDX * 4); + + // Advance pointers + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + destPtr = IntPtr.Add(destPtr, mapDest.Stride); + } + + // Release source and dest locks + bitmap.UnlockBits(mapDest); + device.ImmediateContext.UnmapSubresource(screenTexture, 0); + + if (left > 0 || top > 0 || width < widthDX || height < heightDX) + { + CropImage(bitmap, new System.Drawing.Rectangle(left, top, width, height)); + } + + if (captureCursor) + { + using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap)) + { + ApplyCursor(g, bitmap, left, top); + } + } + + MemoryStream ms = new MemoryStream(); + bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp); + ms.Position = 0; + var img = GetBitmapImage(ms); + img.Freeze(); + + bitmap.Dispose(); + screenTexture.Dispose(); + screenResource.Dispose(); + duplicatedOutput.ReleaseFrame(); + + + + return img; + } + catch (SharpDXException e) + { + if (e.ResultCode.Code != SharpDX.DXGI.ResultCode.WaitTimeout.Result.Code) + { + Trace.TraceError(e.Message); + Trace.TraceError(e.StackTrace); + } + + throw; + } + } + + /// + /// Applies the cursor icon. + /// + /// The graphics context. + /// The bitmap. + /// The left. + /// The top. + [DebuggerHidden] + [DebuggerStepThrough] + private void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top) + { + try + { + CURSORINFO pci; + pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); + + if (GetCursorInfo(out pci)) + { + if (pci.flags == CURSOR_SHOWING) + { + //var iconPointer = User32.CopyIcon(pci.hCursor); + //User32.ICONINFO iconInfo; + //User32.GetIconInfo(iconPointer, out iconInfo); + + var hdc = g.GetHdc(); + User32.DrawIconEx(hdc, pci.ptScreenPos.x - left, pci.ptScreenPos.y - top, pci.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); + + //User32.DestroyIcon(iconPointer); + GDIStuff.DeleteObject(pci.hCursor); + } + } + g.ReleaseHdc(); + } + catch { } + } + + /// + /// Gets the bitmap image. + /// + /// The ms. + /// + private BitmapImage GetBitmapImage(MemoryStream ms) + { + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = ms; + bitmapImage.EndInit(); + return bitmapImage; + } + + /// + /// Crops the image. + /// + /// The img. + /// The crop area. + /// + private System.Drawing.Bitmap CropImage(System.Drawing.Bitmap img, System.Drawing.Rectangle cropArea) + { + System.Drawing.Bitmap bmpImage = new System.Drawing.Bitmap(img); + return bmpImage.Clone(cropArea, bmpImage.PixelFormat); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + duplicatedOutput.Dispose(); + device.Dispose(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectBitmap.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectBitmap.cs new file mode 100644 index 000000000..fac39cc04 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectBitmap.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class DirectBitmap : IDisposable + { + public Bitmap Bitmap { get; private set; } + public Int32[] Bits { get; private set; } + public bool Disposed { get; private set; } + public int Height { get; private set; } + public int Width { get; private set; } + + protected GCHandle BitsHandle { get; private set; } + + public DirectBitmap(int width, int height) + { + Width = width; + Height = height; + Bits = new Int32[width * height]; + BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); + Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); + } + + public void SetPixel(int x, int y, Color colour) + { + int index = x + (y * Width); + int col = colour.ToArgb(); + + Bits[index] = col; + } + + public Color GetPixel(int x, int y) + { + int index = x + (y * Width); + int col = Bits[index]; + Color result = Color.FromArgb(col); + + return result; + } + + public void Dispose() + { + if (Disposed) return; + Disposed = true; + BitsHandle.Free(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectXScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectXScreenCapture.cs new file mode 100644 index 000000000..e72f20a82 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectXScreenCapture.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using SharpDX; +using SharpDX.Direct3D11; +using SharpDX.DXGI; +using Device = SharpDX.Direct3D11.Device; +using MapFlags = SharpDX.Direct3D11.MapFlags; + +namespace Tango.ScreenCapture +{ + public class DirectXScreenCapture : IScreenCaptureMethod + { + private static bool _hasInstance; + private Device device; + private Output1 output1; + private Texture2DDescription textureDesc; + private OutputDuplication duplicatedOutput; + private int monitorWidth; + private int monitorHeight; + + public int AdapterIndex { get; set; } + + public int MonitorIndex { get; set; } + + public DirectXScreenCapture() + { + if (_hasInstance) + { + throw new InvalidOperationException("An instance of the DirectX screen capture method already exists. Please dispose it before attempting to create a new one."); + } + + // Create DXGI Factory1 + var factory = new Factory1(); + var adapter = factory.GetAdapter1(AdapterIndex); + + // Create device from Adapter + device = new Device(adapter); + + // Get DXGI.Output + var output = adapter.GetOutput(MonitorIndex); + output1 = output.QueryInterface(); + + // Width/Height of desktop to capture + monitorWidth = ((SharpDX.Rectangle)output.Description.DesktopBounds).Width; + monitorHeight = ((SharpDX.Rectangle)output.Description.DesktopBounds).Height; + + // Create Staging texture CPU-accessible + textureDesc = new Texture2DDescription + { + CpuAccessFlags = CpuAccessFlags.Read, + BindFlags = BindFlags.None, + Format = Format.B8G8R8A8_UNorm, + Width = monitorWidth, + Height = monitorHeight, + OptionFlags = ResourceOptionFlags.None, + MipLevels = 1, + ArraySize = 1, + SampleDescription = { Count = 1, Quality = 0 }, + Usage = ResourceUsage.Staging + }; + + // Duplicate the output + duplicatedOutput = output1.DuplicateOutput(device); + + _hasInstance = true; + } + + public virtual Bitmap GetDesktopBitmap(CaptureRegion region) + { + var screenTexture = new Texture2D(device, textureDesc); + + SharpDX.DXGI.Resource screenResource; + OutputDuplicateFrameInformation duplicateFrameInformation; + + // Try to get duplicated frame within given time + duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource); + + // copy resource into memory that can be accessed by the CPU + using (var screenTexture2D = screenResource.QueryInterface()) + device.ImmediateContext.CopyResource(screenTexture2D, screenTexture); + + // Get the desktop capture texture + var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None); + + // Create Drawing.Bitmap + var bitmap = new Bitmap(monitorWidth, monitorHeight, System.Drawing.Imaging.PixelFormat.Format32bppRgb); + var boundsRect = new System.Drawing.Rectangle(0, 0, monitorWidth, monitorHeight); + + // Copy pixels from screen capture Texture to GDI bitmap + var mapDest = bitmap.LockBits(boundsRect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat); + var sourcePtr = mapSource.DataPointer; + var destPtr = mapDest.Scan0; + for (int y = 0; y < region.Height; y++) + { + // Copy a single line + Utilities.CopyMemory(destPtr, sourcePtr, monitorWidth * 4); + + // Advance pointers + sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch); + destPtr = IntPtr.Add(destPtr, mapDest.Stride); + } + + // Release source and dest locks + bitmap.UnlockBits(mapDest); + device.ImmediateContext.UnmapSubresource(screenTexture, 0); + + if (region.Left > 0 || region.Top > 0 || region.Width < monitorWidth || region.Height < monitorHeight) + { + var cropped = CropImage(bitmap, new System.Drawing.Rectangle(region.Left, region.Top, region.Width, region.Height)); + bitmap.Dispose(); + bitmap = cropped; + } + + screenTexture.Dispose(); + screenResource.Dispose(); + duplicatedOutput.ReleaseFrame(); + + return bitmap; + } + + private Bitmap CropImage(Bitmap img, System.Drawing.Rectangle cropArea) + { + Bitmap bmpImage = new Bitmap(img); + return bmpImage.Clone(cropArea, bmpImage.PixelFormat); + } + + public void Dispose() + { + _hasInstance = false; + duplicatedOutput.Dispose(); + device.Dispose(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/FastBitmap.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/FastBitmap.cs new file mode 100644 index 000000000..5588daeda --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/FastBitmap.cs @@ -0,0 +1,864 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + /// + /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images + /// + public unsafe class FastBitmap : IDisposable + { + /// + /// Specifies the number of bytes available per pixel of the bitmap object being manipulated + /// + public const int BytesPerPixel = 4; + + /// + /// The Bitmap object encapsulated on this FastBitmap + /// + private readonly Bitmap _bitmap; + + /// + /// The BitmapData resulted from the lock operation + /// + private BitmapData _bitmapData; + + /// + /// The first pixel of the bitmap + /// + private int* _scan0; + + /// + /// Gets the width of this FastBitmap object + /// + public int Width { get; } + + /// + /// Gets the height of this FastBitmap object + /// + public int Height { get; } + + /// + /// Gets the pointer to the first pixel of the bitmap + /// + public IntPtr Scan0 => _bitmapData.Scan0; + + /// + /// Gets the stride width (in int32-sized values) of the bitmap + /// + public int Stride { get; private set; } + + /// + /// Gets the stride width (in bytes) of the bitmap + /// + public int StrideInBytes { get; private set; } + + /// + /// Gets a boolean value that states whether this FastBitmap is currently locked in memory + /// + public bool Locked { get; private set; } + + /// + /// Gets an array of 32-bit color pixel values that represent this FastBitmap + /// + /// The locking operation required to extract the values off from the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + public int[] DataArray + { + get + { + bool unlockAfter = false; + if (!Locked) + { + Lock(); + unlockAfter = true; + } + + // Declare an array to hold the bytes of the bitmap + int bytes = Math.Abs(_bitmapData.Stride) * _bitmap.Height; + int[] argbValues = new int[bytes / BytesPerPixel]; + + // Copy the RGB values into the array + Marshal.Copy(_bitmapData.Scan0, argbValues, 0, bytes / BytesPerPixel); + + if (unlockAfter) + { + Unlock(); + } + + return argbValues; + } + } + + /// + /// Creates a new instance of the FastBitmap class with a specified Bitmap. + /// The bitmap provided must have a 32bpp depth + /// + /// The Bitmap object to encapsulate on this FastBitmap object + /// The bitmap provided does not have a 32bpp pixel format + public FastBitmap(Bitmap bitmap) + { + if (Image.GetPixelFormatSize(bitmap.PixelFormat) != 32) + { + throw new ArgumentException(@"The provided bitmap must have a 32bpp depth", nameof(bitmap)); + } + + _bitmap = bitmap; + + Width = bitmap.Width; + Height = bitmap.Height; + } + + /// + /// Disposes of this fast bitmap object and releases any pending resources. + /// The underlying bitmap is not disposes, and is unlocked, if currently locked + /// + public void Dispose() + { + if (Locked) + { + Unlock(); + } + } + + /// + /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, + /// an exception is thrown + /// + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The bitmap is already locked + /// The locking operation in the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + public FastBitmapLocker Lock() + { + return Lock((FastBitmapLockFormat)_bitmap.PixelFormat); + } + + /// + /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, + /// an exception is thrown. + /// + /// The provided pixel format should be a 32bpp format. + /// + /// A pixel format to use when locking the underlying bitmap + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The bitmap is already locked + /// The locking operation in the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat) + { + if (Locked) + { + throw new InvalidOperationException("Unlock must be called before a Lock operation"); + } + + return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat); + } + + /// + /// Locks the bitmap to start the bitmap operations + /// + /// The lock mode to use on the bitmap + /// A pixel format to use when locking the underlying bitmap + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The locking operation in the underlying bitmap failed + /// The bitmap is already locked outside this fast bitmap + /// is not a 32bpp format + private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat) + { + var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height); + + return Lock(lockMode, rect, pixelFormat); + } + + /// + /// Locks the bitmap to start the bitmap operations + /// + /// The lock mode to use on the bitmap + /// The rectangle to lock + /// A pixel format to use when locking the underlying bitmap + /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal + /// The provided region is invalid + /// The locking operation in the underlying bitmap failed + /// The bitmap region is already locked + /// is not a 32bpp format + private FastBitmapLocker Lock(ImageLockMode lockMode, Rectangle rect, PixelFormat pixelFormat) + { + // Lock the bitmap's bits + _bitmapData = _bitmap.LockBits(rect, lockMode, pixelFormat); + + _scan0 = (int*)_bitmapData.Scan0; + Stride = _bitmapData.Stride / BytesPerPixel; + StrideInBytes = _bitmapData.Stride; + + Locked = true; + + return new FastBitmapLocker(this); + } + + /// + /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked + /// beforehand, an exception is thrown + /// + /// The bitmap is already unlocked + /// The unlocking operation in the underlying bitmap failed + public void Unlock() + { + if (!Locked) + { + throw new InvalidOperationException("Lock must be called before an Unlock operation"); + } + + _bitmap.UnlockBits(_bitmapData); + + Locked = false; + } + + /// + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to set + /// The Y coordinate of the pixel to set + /// The new color of the pixel to set + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public void SetPixel(int x, int y, Color color) + { + SetPixel(x, y, color.ToArgb()); + } + + /// + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to set + /// The Y coordinate of the pixel to set + /// The new color of the pixel to set + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public void SetPixel(int x, int y, int color) + { + SetPixel(x, y, unchecked((uint)color)); + } + + /// + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to set + /// The Y coordinate of the pixel to set + /// The new color of the pixel to set + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public void SetPixel(int x, int y, uint color) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + *(uint*)(_scan0 + x + y * Stride) = color; + } + + /// + /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// + /// The X coordinate of the pixel to get + /// The Y coordinate of the pixel to get + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public Color GetPixel(int x, int y) + { + return Color.FromArgb(GetPixelInt(x, y)); + } + + /// + /// Gets the pixel color at the given coordinates as an integer value. If the bitmap + /// was not locked beforehands, an exception is thrown + /// + /// The X coordinate of the pixel to get + /// The Y coordinate of the pixel to get + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public int GetPixelInt(int x, int y) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + return *(_scan0 + x + y * Stride); + } + + /// + /// Gets the pixel color at the given coordinates as an unsigned integer value. + /// If the bitmap was not locked beforehands, an exception is thrown + /// + /// The X coordinate of the pixel to get + /// The Y coordinate of the pixel to get + /// The fast bitmap is not locked + /// The provided coordinates are out of bounds of the bitmap + public uint GetPixelUInt(int x, int y) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + return *((uint*)_scan0 + x + y * Stride); + } + + /// + /// Copies the contents of the given array of colors into this FastBitmap. + /// Throws an ArgumentException if the count of colors on the array mismatches the pixel count from this FastBitmap + /// + /// The array of colors to copy + /// Whether to ignore zeroes when copying the data + public void CopyFromArray(int[] colors, bool ignoreZeroes = false) + { + if (colors.Length != Width * Height) + { + throw new ArgumentException(@"The number of colors of the given array mismatch the pixel count of the bitmap", nameof(colors)); + } + + // Simply copy the argb values array + // ReSharper disable once InconsistentNaming + int* s0t = _scan0; + + fixed (int* source = colors) + { + // ReSharper disable once InconsistentNaming + int* s0s = source; + + int count = Width * Height; + + if (!ignoreZeroes) + { + // Unfold the loop + const int sizeBlock = 8; + int rem = count % sizeBlock; + + count /= sizeBlock; + + while (count-- > 0) + { + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + } + + while (rem-- > 0) + { + *(s0t++) = *(s0s++); + } + } + else + { + while (count-- > 0) + { + if (*(s0s) == 0) { s0t++; s0s++; continue; } + *(s0t++) = *(s0s++); + } + } + } + } + + /// + /// Clears the bitmap with the given color + /// + /// The color to clear the bitmap with + public void Clear(Color color) + { + Clear(color.ToArgb()); + } + + /// + /// Clears the bitmap with the given color + /// + /// The color to clear the bitmap with + public void Clear(int color) + { + bool unlockAfter = false; + if (!Locked) + { + Lock(); + unlockAfter = true; + } + + // Clear all the pixels + int count = Width * Height; + int* curScan = _scan0; + + // Uniform color pixel values can be mem-set straight away + int component = (color & 0xFF); + if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && component == ((color >> 24) & 0xFF)) + { + memset(_scan0, component, (ulong)(Height * Stride * BytesPerPixel)); + } + else + { + // Defines the ammount of assignments that the main while() loop is performing per loop. + // The value specified here must match the number of assignment statements inside that loop + const int assignsPerLoop = 8; + + int rem = count % assignsPerLoop; + count /= assignsPerLoop; + + while (count-- > 0) + { + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + } + while (rem-- > 0) + { + *(curScan++) = color; + } + + if (unlockAfter) + { + Unlock(); + } + } + } + + /// + /// Clears a square region of this image w/ a given color + /// + /// + /// + public void ClearRegion(Rectangle region, Color color) + { + ClearRegion(region, color.ToArgb()); + } + + /// + /// Clears a square region of this image w/ a given color + /// + /// + /// + public void ClearRegion(Rectangle region, int color) + { + var thisReg = new Rectangle(0, 0, Width, Height); + if (!region.IntersectsWith(thisReg)) + return; + + // If the region covers the entire image, use faster Clear(). + if (region == thisReg) + { + Clear(color); + return; + } + + int minX = region.X; + int maxX = region.X + region.Width; + + int minY = region.Y; + int maxY = region.Y + region.Height; + + // Bail out of optimization if there's too few rows to make this worth it + if (maxY - minY < 16) + { + for (int y = minY; y < maxY; y++) + { + for (int x = minX; x < maxX; x++) + { + *(_scan0 + x + y * Stride) = color; + } + } + return; + } + + ulong strideWidth = (ulong)region.Width * BytesPerPixel; + + // Uniform color pixel values can be mem-set straight away + int component = (color & 0xFF); + if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && + component == ((color >> 24) & 0xFF)) + { + for (int y = minY; y < maxY; y++) + { + memset(_scan0 + minX + y * Stride, component, strideWidth); + } + } + else + { + // Prepare a horizontal slice of pixels that will be copied over each horizontal row down. + var row = new int[region.Width]; + + fixed (int* pRow = row) + { + int count = region.Width; + int rem = count % 8; + count /= 8; + int* pSrc = pRow; + while (count-- > 0) + { + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + } + while (rem-- > 0) + { + *pSrc++ = color; + } + + var sx = _scan0 + minX; + for (int y = minY; y < maxY; y++) + { + memcpy(sx + y * Stride, pRow, strideWidth); + } + } + } + } + + /// + /// Copies a region of the source bitmap into this fast bitmap + /// + /// The source image to copy + /// The region on the source bitmap that will be copied over + /// The region on this fast bitmap that will be changed + /// The provided source bitmap is the same bitmap locked in this FastBitmap + public void CopyRegion(Bitmap source, Rectangle srcRect, Rectangle destRect) + { + // Throw exception when trying to copy same bitmap over + if (source == _bitmap) + { + throw new ArgumentException(@"Copying regions across the same bitmap is not supported", nameof(source)); + } + + var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); + var destBitmapRect = new Rectangle(0, 0, Width, Height); + + // Check if the rectangle configuration doesn't generate invalid states or does not affect the target image + if (srcRect.Width <= 0 || srcRect.Height <= 0 || destRect.Width <= 0 || destRect.Height <= 0 || + !srcBitmapRect.IntersectsWith(srcRect) || !destRect.IntersectsWith(destBitmapRect)) + return; + + // Find the areas of the first and second bitmaps that are going to be affected + srcBitmapRect = Rectangle.Intersect(srcRect, srcBitmapRect); + + // Clip the source rectangle on top of the destination rectangle in a way that clips out the regions of the original bitmap + // that will not be drawn on the destination bitmap for being out of bounds + srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(srcRect.X, srcRect.Y, destRect.Width, destRect.Height)); + + destBitmapRect = Rectangle.Intersect(destRect, destBitmapRect); + + // Clip the source bitmap region yet again here + srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(-destRect.X + srcRect.X, -destRect.Y + srcRect.Y, Width, Height)); + + // Calculate the rectangle containing the maximum possible area that is supposed to be affected by the copy region operation + int copyWidth = Math.Min(srcBitmapRect.Width, destBitmapRect.Width); + int copyHeight = Math.Min(srcBitmapRect.Height, destBitmapRect.Height); + + if (copyWidth == 0 || copyHeight == 0) + return; + + int srcStartX = srcBitmapRect.Left; + int srcStartY = srcBitmapRect.Top; + + int destStartX = destBitmapRect.Left; + int destStartY = destBitmapRect.Top; + + using (var fastSource = source.FastLock()) + { + ulong strideWidth = (ulong)copyWidth * BytesPerPixel; + + // Perform copies of whole pixel rows + for (int y = 0; y < copyHeight; y++) + { + int destX = destStartX; + int destY = destStartY + y; + + int srcX = srcStartX; + int srcY = srcStartY + y; + + long offsetSrc = (srcX + srcY * fastSource.Stride); + long offsetDest = (destX + destY * Stride); + + memcpy(_scan0 + offsetDest, fastSource._scan0 + offsetSrc, strideWidth); + } + } + } + + /// + /// Performs a copy operation of the pixels from the Source bitmap to the Target bitmap. + /// If the dimensions or pixel depths of both images don't match, the copy is not performed + /// + /// The bitmap to copy the pixels from + /// The bitmap to copy the pixels to + /// Whether the copy proceedure was successful + /// The provided source and target bitmaps are the same + public static bool CopyPixels(Bitmap source, Bitmap target) + { + if (source == target) + { + throw new ArgumentException(@"Copying pixels across the same bitmap is not supported", nameof(source)); + } + + if (source.Width != target.Width || source.Height != target.Height || source.PixelFormat != target.PixelFormat) + return false; + + using (FastBitmap fastSource = source.FastLock(), + fastTarget = target.FastLock()) + { + memcpy(fastTarget.Scan0, fastSource.Scan0, (ulong)(fastSource.Height * fastSource.Stride * BytesPerPixel)); + } + + return true; + } + + /// + /// Clears the given bitmap with the given color + /// + /// The bitmap to clear + /// The color to clear the bitmap with + public static void ClearBitmap(Bitmap bitmap, Color color) + { + ClearBitmap(bitmap, color.ToArgb()); + } + + /// + /// Clears the given bitmap with the given color + /// + /// The bitmap to clear + /// The color to clear the bitmap with + public static void ClearBitmap(Bitmap bitmap, int color) + { + using (var fb = bitmap.FastLock()) + { + fb.Clear(color); + } + } + + /// + /// Copies a region of the source bitmap to a target bitmap + /// + /// The source image to copy + /// The target image to be altered + /// The region on the source bitmap that will be copied over + /// The region on the target bitmap that will be changed + /// The provided source and target bitmaps are the same bitmap + public static void CopyRegion(Bitmap source, Bitmap target, Rectangle srcRect, Rectangle destRect) + { + var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); + var destBitmapRect = new Rectangle(0, 0, target.Width, target.Height); + + // If the copy operation results in an entire copy, use CopyPixels instead + if (srcBitmapRect == srcRect && destBitmapRect == destRect && srcBitmapRect == destBitmapRect) + { + CopyPixels(source, target); + return; + } + + using (var fastTarget = target.FastLock()) + { + fastTarget.CopyRegion(source, srcRect, destRect); + } + } + + /// + /// Returns a bitmap that is a slice of the original provided 32bpp Bitmap. + /// The region must have a width and a height > 0, and must lie inside the source bitmap's area + /// + /// The source bitmap to slice + /// The region of the source bitmap to slice + /// A Bitmap that represents the rectangle region slice of the source bitmap + /// The provided bimap is not 32bpp + /// The provided region is invalid + public static Bitmap SliceBitmap(Bitmap source, Rectangle region) + { + if (region.Width <= 0 || region.Height <= 0) + { + throw new ArgumentException(@"The provided region must have a width and a height > 0", nameof(region)); + } + + var sliceRectangle = Rectangle.Intersect(new Rectangle(Point.Empty, source.Size), region); + + if (sliceRectangle.IsEmpty) + { + throw new ArgumentException(@"The provided region must not lie outside of the bitmap's region completely", nameof(region)); + } + + var slicedBitmap = new Bitmap(sliceRectangle.Width, sliceRectangle.Height); + CopyRegion(source, slicedBitmap, sliceRectangle, new Rectangle(0, 0, sliceRectangle.Width, sliceRectangle.Height)); + + return slicedBitmap; + } + +#if NETSTANDARD + public static void memcpy(IntPtr dest, IntPtr src, ulong count) + { + Buffer.MemoryCopy(src.ToPointer(), dest.ToPointer(), count, count); + } + + public static void memcpy(void* dest, void* src, ulong count) + { + Buffer.MemoryCopy(src, dest, count, count); + } + + public static void memset(void* dest, int value, ulong count) + { + Unsafe.InitBlock(dest, (byte)value, (uint)count); + } +#else + /// + /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed + /// + [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count); + + /// + /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed + /// + [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memcpy(void* dest, void* src, ulong count); + + /// + /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed + /// + [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memset(void* dest, int value, ulong count); +#endif + + /// + /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls + /// + public struct FastBitmapLocker : IDisposable + { + /// + /// Gets the fast bitmap instance attached to this locker + /// + public FastBitmap FastBitmap { get; } + + /// + /// Initializes a new instance of the FastBitmapLocker struct with an initial fast bitmap object. + /// The fast bitmap object passed will be unlocked after calling Dispose() on this struct + /// + /// A fast bitmap to attach to this locker which will be released after a call to Dispose + public FastBitmapLocker(FastBitmap fastBitmap) + { + FastBitmap = fastBitmap; + } + + /// + /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap + /// + public void Dispose() + { + if (FastBitmap.Locked) + FastBitmap.Unlock(); + } + } + } + + /// + /// Describes a pixel format to use when locking a bitmap using . + /// + public enum FastBitmapLockFormat + { + /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, and blue components. The remaining 8 bits are not used. + Format32bppRgb = 139273, + /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component. + Format32bppPArgb = 925707, + /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. + Format32bppArgb = 2498570, + } + + /// + /// Static class that contains fast bitmap extension methdos for the Bitmap class + /// + public static class FastBitmapExtensions + { + /// + /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels + /// + /// The bitmap to lock + /// A locked FastBitmap + public static FastBitmap FastLock(this Bitmap bitmap) + { + var fast = new FastBitmap(bitmap); + fast.Lock(); + + return fast; + } + + /// + /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels + /// + /// The bitmap to lock + /// The underlying pixel format to use when locking the bitmap + /// A locked FastBitmap + public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat) + { + var fast = new FastBitmap(bitmap); + fast.Lock(lockFormat); + + return fast; + } + + /// + /// Returns a deep clone of this Bitmap object, with all the data copied over. + /// After a deep clone, the new bitmap is completely independent from the original + /// + /// The bitmap to clone + /// A deep clone of this Bitmap object, with all the data copied over + public static Bitmap DeepClone(this Bitmap bitmap) + { + return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/GdiScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/GdiScreenCapture.cs new file mode 100644 index 000000000..51591ec12 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/GdiScreenCapture.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class GdiScreenCapture : IScreenCaptureMethod + { + public Bitmap GetDesktopBitmap(CaptureRegion region) + { + var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.CopyFromScreen(region.Left, region.Top, 0, 0, bitmap.Size, System.Drawing.CopyPixelOperation.SourceCopy); + } + + return bitmap; + } + + public void Dispose() + { + //Do nothing + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureFrame.cs new file mode 100644 index 000000000..496847db0 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureFrame.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.ScreenCapture +{ + public interface IScreenCaptureFrame : IDisposable where T : class, IDisposable + { + int Width { get; } + int Height { get; } + bool DifferenceAvailable { get; } + BitmapSource ToBitmapSource(); + Bitmap ToBitmap(); + T ToDifference(); + byte[] ToArray(); + byte[] ToJpeg(int quality = 100); + byte[] ToPng(); + MemoryStream ToStream(); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureMethod.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureMethod.cs new file mode 100644 index 000000000..868299ebe --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureMethod.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public interface IScreenCaptureMethod : IDisposable + { + Bitmap GetDesktopBitmap(CaptureRegion region); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparer.cs new file mode 100644 index 000000000..fba20a00a --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparer.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class ImageComparer + { + unsafe public Bitmap CreateDifferenceBitmap(Bitmap previousImage, Bitmap currentImage, Color matchColor) + { + if (previousImage == null | currentImage == null) + throw new InvalidOperationException("Cannot compare image. They are the same instance"); + + if (previousImage.Height != currentImage.Height || previousImage.Width != currentImage.Width) + throw new InvalidOperationException("Cannot compare image of different size."); + + Bitmap diffImage = currentImage.Clone() as Bitmap; + + int height = previousImage.Height; + int width = previousImage.Width; + + BitmapData data1 = previousImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData data2 = currentImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData diffData = diffImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + + byte* data1Ptr = (byte*)data1.Scan0; + byte* data2Ptr = (byte*)data2.Scan0; + byte* diffPtr = (byte*)diffData.Scan0; + + byte[] swapColor = new byte[4]; + swapColor[0] = matchColor.B; + swapColor[1] = matchColor.G; + swapColor[2] = matchColor.R; + swapColor[3] = matchColor.A; + + int rowPadding = data1.Stride - (previousImage.Width * 4); + + // iterate over height (rows) + for (int i = 0; i < height; i++) + { + // iterate over width (columns) + for (int j = 0; j < width; j++) + { + int same = 0; + + byte[] tmp = new byte[4]; + + // compare pixels and copy new values into temporary array + for (int x = 0; x < 4; x++) + { + tmp[x] = data2Ptr[0]; + if (data1Ptr[0] == data2Ptr[0]) + { + same++; + } + data1Ptr++; // advance image1 ptr + data2Ptr++; // advance image2 ptr + } + + // swap color or add new values + for (int x = 0; x < 4; x++) + { + diffPtr[0] = (same == 4) ? swapColor[x] : tmp[x]; + diffPtr++; // advance diff image ptr + } + } + + // at the end of each column, skip extra padding + if (rowPadding > 0) + { + data1Ptr += rowPadding; + data2Ptr += rowPadding; + diffPtr += rowPadding; + } + } + + previousImage.UnlockBits(data1); + currentImage.UnlockBits(data2); + diffImage.UnlockBits(diffData); + + return diffImage; + } + + unsafe public VectorDifferenceImage CreateDifferenceVector(Bitmap previousImage, Bitmap currentImage) + { + VectorDifferenceImage vector = new VectorDifferenceImage(previousImage.Width, previousImage.Height); + + if (previousImage == null | currentImage == null) + throw new InvalidOperationException("Cannot compare image. They are the same instance"); + + if (previousImage.Height != currentImage.Height || previousImage.Width != currentImage.Width) + throw new InvalidOperationException("Cannot compare image of different size."); + + int height = previousImage.Height; + int width = previousImage.Width; + + BitmapData data1 = previousImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + BitmapData data2 = currentImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + byte* data1Ptr = (byte*)data1.Scan0; + byte* data2Ptr = (byte*)data2.Scan0; + + int rowPadding = data1.Stride - (previousImage.Width * 4); + + // iterate over height (rows) + for (int i = 0; i < height; i++) + { + // iterate over width (columns) + for (int j = 0; j < width; j++) + { + int same = 0; + + byte[] tmp = new byte[4]; + + // compare pixels and copy new values into temporary array + for (int x = 0; x < 4; x++) + { + tmp[x] = data2Ptr[0]; + if (data1Ptr[0] == data2Ptr[0]) + { + same++; + } + data1Ptr++; // advance image1 ptr + data2Ptr++; // advance image2 ptr + } + + if (same != 4) + { + vector.Pixels.Add(new VectorImagePixel() + { + X = (ushort)j, + Y = (ushort)i, + B = tmp[0], + G = tmp[1], + R = tmp[2], + }); + } + } + + // at the end of each column, skip extra padding + if (rowPadding > 0) + { + data1Ptr += rowPadding; + data2Ptr += rowPadding; + } + } + + previousImage.UnlockBits(data1); + currentImage.UnlockBits(data2); + + return vector; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparerResult.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparerResult.cs new file mode 100644 index 000000000..556d5077e --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparerResult.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class ImageComparerResult + { + public Bitmap Bitmap { get; set; } + public VectorDifferenceImage Vector { get; set; } + + public ImageComparerResult() + { + + } + + public ImageComparerResult(int width, int height) + { + Vector = new VectorDifferenceImage(width, height); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Properties/AssemblyInfo.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..85b6ec515 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Tango.ScreenCapture")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tango.ScreenCapture")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b87ca1de-ed08-42b6-8d9c-a1c1a602d03b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/RasterScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/RasterScreenCaptureFrame.cs new file mode 100644 index 000000000..51e239376 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/RasterScreenCaptureFrame.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class RasterScreenCaptureFrame : ScreenCaptureFrame + { + public RasterScreenCaptureFrame(Bitmap bitmap, Bitmap diff) : base(bitmap, diff) + { + + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureEngine.cs new file mode 100644 index 000000000..9933512c3 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureEngine.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class ScreenCaptureEngine : IDisposable + { + private bool _isDisposed; + private Thread _captureThread; + private Bitmap _previousBitmap; + private ImageComparer _comparer; + + public event EventHandler ScreenFrameReceived; + + public IScreenCaptureMethod CaptureMethod { get; set; } + public CaptureRegion CaptureRegion { get; set; } + public bool IsStarted { get; set; } + public TimeSpan Interval { get; set; } + public bool CaptureCursor { get; set; } + public bool EnableImageComparison { get; set; } + + public ScreenCaptureEngine() + { + Interval = TimeSpan.FromMilliseconds(100); + CaptureMethod = new GdiScreenCapture(); + CaptureRegion = new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); + _comparer = new ImageComparer(); + EnableImageComparison = true; + } + + public void Start() + { + if (_isDisposed) + { + throw new ObjectDisposedException("Screen capture engine cannot be started after disposed."); + } + + if (!IsStarted) + { + IsStarted = true; + + _captureThread = new Thread(CaptureThreadMethod); + _captureThread.IsBackground = true; + _captureThread.Name = "Screen Capture Thread"; + _captureThread.Start(); + } + } + + public void Stop() + { + if (IsStarted) + { + IsStarted = false; + } + } + + private void CaptureThreadMethod() + { + while (IsStarted) + { + var bitmap = CaptureMethod.GetDesktopBitmap(CaptureRegion); + + if (CaptureCursor) + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + CursorUtils.ApplyCursor(g, bitmap, CaptureRegion.Left, CaptureRegion.Top); + } + } + + if (EnableImageComparison) + { + if (_previousBitmap == null) + { + _previousBitmap = bitmap.Clone() as Bitmap; + OnScreenFrameReceived(new ScreenCaptureFrame(bitmap, null)); + } + else + { + var diffBitmap = _comparer.CreateDifferenceBitmap(_previousBitmap, bitmap, Color.Transparent); + _previousBitmap.Dispose(); + _previousBitmap = bitmap.Clone() as Bitmap; + OnScreenFrameReceived(new ScreenCaptureFrame(bitmap, diffBitmap)); + } + } + else + { + OnScreenFrameReceived(new ScreenCaptureFrame(bitmap, null)); + } + + Thread.Sleep(Interval); + } + } + + public void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + Stop(); + CaptureMethod?.Dispose(); + } + } + + protected virtual void OnScreenFrameReceived(ScreenCaptureFrame frame) + { + ScreenFrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs() + { + Frame = frame, + }); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrame.cs new file mode 100644 index 000000000..104bbccb1 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrame.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Media.Imaging; + +namespace Tango.ScreenCapture +{ + public class ScreenCaptureFrame : IDisposable + { + private Bitmap _bitmap; + private Bitmap _diffBitmap; + + public int Width + { + get { return _bitmap.Width; } + } + + public int Height + { + get { return _bitmap.Height; } + } + + public bool HasDifferenceFrame + { + get { return _diffBitmap != null; } + } + + public ScreenCaptureFrame(Bitmap bitmap, Bitmap diffBitmap) + { + _diffBitmap = diffBitmap; + _bitmap = bitmap; + } + + public void Dispose() + { + _bitmap.Dispose(); + + if (_diffBitmap != null) + { + _diffBitmap.Dispose(); + } + } + + public BitmapSource ToBitmapSource() + { + var source = GetBitmapImage(ToStream()); + source.Freeze(); + return source; + } + + public Bitmap ToBitmap() + { + return _bitmap; + } + + public ScreenCaptureFrame ToDifferenceCaptureFrame() + { + return new ScreenCaptureFrame(_diffBitmap, null); + } + + public byte[] ToArray() + { + using (var ms = ToStream()) + { + return ms.ToArray(); + } + } + + public byte[] ToJpeg(int quality = 100) + { + var encoderParameters = new EncoderParameters(1); + encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); + + using (MemoryStream ms = new MemoryStream()) + { + _bitmap.Save(ms, GetEncoder(ImageFormat.Jpeg), encoderParameters); + ms.Position = 0; + return ms.ToArray(); + } + } + + public byte[] ToPng() + { + using (MemoryStream ms = new MemoryStream()) + { + _bitmap.Save(ms, ImageFormat.Png); + ms.Position = 0; + return ms.ToArray(); + } + } + + public MemoryStream ToStream() + { + MemoryStream ms = new MemoryStream(); + _bitmap.Save(ms, ImageFormat.Bmp); + ms.Position = 0; + return ms; + } + + private BitmapImage GetBitmapImage(MemoryStream ms) + { + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = ms; + bitmapImage.EndInit(); + return bitmapImage; + } + + private ImageCodecInfo GetEncoder(ImageFormat format) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + return codecs.Single(codec => codec.FormatID == format.Guid); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs new file mode 100644 index 000000000..b9b2edec4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class ScreenCaptureFrameReceivedEventArgs : EventArgs + { + public ScreenCaptureFrame Frame { get; set; } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureMethod.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureMethod.cs new file mode 100644 index 000000000..be8d25cef --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureMethod.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public enum ScreenCaptureMethod + { + GDI, + DirectX + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Tango.ScreenCapture.csproj b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Tango.ScreenCapture.csproj new file mode 100644 index 000000000..879e651a2 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Tango.ScreenCapture.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B} + Library + Properties + Tango.ScreenCapture + Tango.ScreenCapture + v4.6.1 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.dll + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Direct3D11.dll + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.DXGI.dll + + + ..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Mathematics.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorDifferenceImage.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorDifferenceImage.cs new file mode 100644 index 000000000..7fa2c82df --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorDifferenceImage.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class VectorDifferenceImage + { + public int Width { get; private set; } + public int Height { get; private set; } + + public List Pixels { get; private set; } + + public VectorDifferenceImage(int width, int height) + { + Width = width; + Height = height; + Pixels = new List(); + } + + public Bitmap CreateBitmap() + { + DirectBitmap directBitmap = new DirectBitmap(Width, Height); + foreach (var pixel in Pixels) + { + directBitmap.SetPixel((int)pixel.X, (int)pixel.Y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); + } + + directBitmap.Dispose(); + return directBitmap.Bitmap; + } + + public void ApplyOnBitmap(Bitmap bitmap) + { + using (FastBitmap fast = bitmap.FastLock()) + { + foreach (var pixel in Pixels) + { + fast.SetPixel((int)pixel.X, (int)pixel.Y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); + } + } + } + + public byte[] Serialize() + { + using (MemoryStream ms = new MemoryStream()) + { + using (BinaryWriter writer = new BinaryWriter(ms)) + { + writer.Write(Width); + writer.Write(Height); + writer.Write(Pixels.Count); + + foreach (var pixel in Pixels) + { + writer.Write(pixel.X); + writer.Write(pixel.Y); + + writer.Write(pixel.R); + writer.Write(pixel.G); + writer.Write(pixel.B); + } + + ms.Position = 0; + return ms.ToArray(); + } + } + } + + public int CalculateSize() + { + int size = 0; + + size += sizeof(int) * 3; //Width, Height, Count + size += sizeof(ushort) * 2 * Pixels.Count; //X, Y + size += sizeof(byte) * 3 * Pixels.Count; //RGB + + return size; + } + + public static VectorDifferenceImage Deserialize(byte[] data) + { + using (MemoryStream ms = new MemoryStream(data)) + { + using (BinaryReader reader = new BinaryReader(ms)) + { + ms.Position = 0; + var vector = new VectorDifferenceImage(reader.ReadInt32(), reader.ReadInt32()); + + int count = reader.ReadInt32(); + + for (int i = 0; i < count; i++) + { + VectorImagePixel pixel = new VectorImagePixel(); + pixel.X = reader.ReadUInt16(); + pixel.Y = reader.ReadUInt16(); + + pixel.R = reader.ReadByte(); + pixel.G = reader.ReadByte(); + pixel.B = reader.ReadByte(); + + vector.Pixels.Add(pixel); + } + + return vector; + } + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorImagePixel.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorImagePixel.cs new file mode 100644 index 000000000..7cf621125 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorImagePixel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public struct VectorImagePixel + { + public ushort X { get; set; } + public ushort Y { get; set; } + public byte R { get; set; } + public byte G { get; set; } + public byte B { get; set; } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorScreenCaptureFrame.cs new file mode 100644 index 000000000..240924c5a --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorScreenCaptureFrame.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.ScreenCapture +{ + public class VectorScreenCaptureFrame : ScreenCaptureFrame + { + public VectorScreenCaptureFrame(Bitmap bitmap, VectorDifferenceImage diff) : base(bitmap, diff) + { + + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.config b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.config new file mode 100644 index 000000000..dced5aa43 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml new file mode 100644 index 000000000..2e70522d4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml.cs new file mode 100644 index 000000000..909eaa541 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace WpfApp1 +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml new file mode 100644 index 000000000..2a1c3a498 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs new file mode 100644 index 000000000..2ebcc9d73 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; +using Tango.ScreenCapture; + +namespace WpfApp1 +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private ScreenCaptureEngine _capture; + private Bitmap _currentBitmap; + + public MainWindow() + { + InitializeComponent(); + ContentRendered += MainWindow_ContentRendered; + + _capture = new ScreenCaptureEngine() + { + CaptureRegion = new CaptureRegion(0, 0, 1280, 800) + }; + _capture.Interval = TimeSpan.FromMilliseconds(30); + _capture.CaptureCursor = true; + _capture.CaptureMethod = new DirectXScreenCapture(); + _capture.ScreenFrameReceived += _capture_ScreenFrameReceived; + } + + private void MainWindow_ContentRendered(object sender, EventArgs e) + { + _capture.Start(); + } + + private void _capture_ScreenFrameReceived(object sender, ScreenCaptureFrameReceivedEventArgs e) + { + BitmapSource preview = null; + + int size = 0; + + if (e.Frame.HasDifferenceFrame) + { + size = e.Frame.ToDifferenceCaptureFrame().ToPng().Length; + + using (Graphics g = Graphics.FromImage(_currentBitmap)) + { + g.DrawImage(e.Frame.ToBitmap(), new System.Drawing.Point(0, 0)); + ScreenCaptureFrame frame = new ScreenCaptureFrame(_currentBitmap, null); + preview = frame.ToBitmapSource(); + } + } + else + { + _currentBitmap = e.Frame.ToBitmap().Clone() as Bitmap; + preview = e.Frame.ToBitmapSource(); + size = e.Frame.ToPng().Length; + } + + //Debug.WriteLine($"Actual frame size on network: {size / 1000} kb"); + + Dispatcher.BeginInvoke(new Action(() => + { + img.Source = preview; + })); + + e.Frame.Dispose(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..05e4327a4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WpfApp1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WpfApp1")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs new file mode 100644 index 000000000..c8c627c30 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfApp1.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApp1.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs new file mode 100644 index 000000000..6512fd02f --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfApp1.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj new file mode 100644 index 000000000..dcabe00bb --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj @@ -0,0 +1,109 @@ + + + + + Debug + AnyCPU + {8F28CF3A-9B97-4463-B245-709D11B0AD7F} + WinExe + WpfApp1 + WpfApp1 + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {a78068d4-2061-4376-8ede-583d8d880dec} + Tango.RemoteDesktop + + + {b87ca1de-ed08-42b6-8d9c-a1c1a602d03b} + Tango.ScreenCapture + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/app.config b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/app.config new file mode 100644 index 000000000..4e74e78b6 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.config b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.config new file mode 100644 index 000000000..dced5aa43 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml new file mode 100644 index 000000000..2e70522d4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml.cs new file mode 100644 index 000000000..909eaa541 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace WpfApp1 +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml new file mode 100644 index 000000000..2a1c3a498 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs new file mode 100644 index 000000000..85e5ca0bd --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; +using Tango.RemoteDesktop; +using Tango.RemoteDesktop.CaptureMethods; +using Tango.RemoteDesktop.Encoders; +using Tango.RemoteDesktop.Engines; +using Tango.RemoteDesktop.Frames; + +namespace WpfApp1 +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private RasterScreenCaptureEngine _engine; + private Bitmap _currentBitmap; + + public MainWindow() + { + InitializeComponent(); + ContentRendered += MainWindow_ContentRendered; + + _engine = new RasterScreenCaptureEngine() + { + CaptureRegion = new CaptureRegion(0, 0, 1280, 800) + }; + _engine.Interval = TimeSpan.FromMilliseconds(100); + _engine.CaptureCursor = true; + _engine.FrameReceived += _engine_FrameReceived; + } + + private void _engine_FrameReceived(object sender, ScreenCaptureFrameReceivedEventArgs e) + { + BitmapSource preview = null; + + int size = 0; + + if (e.Frame.DifferenceAvailable) + { + var diffFrame = e.Frame.ToDifference(); + + size = diffFrame.Encode().ToArray().Length; + + //diffFrame.Apply(_currentBitmap); + + //var updatedFrame = new RasterFrame(_currentBitmap); + //preview = updatedFrame.ToBitmapSource(); + } + else + { + _currentBitmap = e.Frame.ToBitmap().Clone() as Bitmap; + preview = e.Frame.ToBitmapSource(); + size = e.Frame.Encode().ToArray().Length; + } + + //Debug.WriteLine($"Actual frame size on network: {size / 1000} kb"); + + //Dispatcher.BeginInvoke(new Action(() => + //{ + // img.Source = preview; + //})); + + e.Frame.Dispose(); + } + + private void MainWindow_ContentRendered(object sender, EventArgs e) + { + _engine.Start(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/AssemblyInfo.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..05e4327a4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WpfApp1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WpfApp1")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.Designer.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.Designer.cs new file mode 100644 index 000000000..c8c627c30 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfApp1.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApp1.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.resx b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.Designer.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.Designer.cs new file mode 100644 index 000000000..6512fd02f --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfApp1.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.settings b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/WpfApp1.csproj b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/WpfApp1.csproj new file mode 100644 index 000000000..e84b2b381 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/WpfApp1.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {8F28CF3A-9B97-4463-B245-709D11B0AD7F} + WinExe + WpfApp1 + WpfApp1 + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {a78068d4-2061-4376-8ede-583d8d880dec} + Tango.RemoteDesktop + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.config b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.config new file mode 100644 index 000000000..dced5aa43 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml new file mode 100644 index 000000000..2e70522d4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml.cs new file mode 100644 index 000000000..909eaa541 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +namespace WpfApp1 +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml new file mode 100644 index 000000000..2a1c3a498 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml @@ -0,0 +1,12 @@ + + + + + diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs new file mode 100644 index 000000000..0a0f7ff57 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Windows.Navigation; +using System.Windows.Shapes; +using System.Windows.Threading; +using Tango.ScreenCapture; + +namespace WpfApp1 +{ + /// + /// Interaction logic for MainWindow.xaml + /// + public partial class MainWindow : Window + { + private ScreenCaptureEngine _capture; + private Bitmap _currentBitmap; + + public MainWindow() + { + InitializeComponent(); + ContentRendered += MainWindow_ContentRendered; + + _capture = new ScreenCaptureEngine() + { + CaptureRegion = new CaptureRegion(0, 0, 1280, 800) + }; + _capture.Interval = TimeSpan.FromMilliseconds(100); + _capture.CaptureCursor = true; + _capture.CaptureMethod = new DirectXScreenCapture(); + _capture.ScreenFrameReceived += _capture_ScreenFrameReceived; + } + + private void MainWindow_ContentRendered(object sender, EventArgs e) + { + _capture.Start(); + } + + private void _capture_ScreenFrameReceived(object sender, ScreenCaptureFrameReceivedEventArgs e) + { + BitmapSource preview = null; + + int size = 0; + + if (e.Frame.HasDifferenceFrame) + { + size = e.Frame.ToDifferenceCaptureFrame().ToPng().Length; + + //using (Graphics g = Graphics.FromImage(_currentBitmap)) + //{ + // g.DrawImage(e.Frame.ToBitmap(), new System.Drawing.Point(0, 0)); + // ScreenCaptureFrame frame = new ScreenCaptureFrame(_currentBitmap, null); + // preview = frame.ToBitmapSource(); + //} + } + else + { + _currentBitmap = e.Frame.ToBitmap().Clone() as Bitmap; + preview = e.Frame.ToBitmapSource(); + size = e.Frame.ToPng().Length; + } + + //Debug.WriteLine($"Actual frame size on network: {size / 1000} kb"); + + //Dispatcher.BeginInvoke(new Action(() => + //{ + // img.Source = preview; + //})); + + e.Frame.Dispose(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/AssemblyInfo.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..05e4327a4 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/AssemblyInfo.cs @@ -0,0 +1,55 @@ +using System.Reflection; +using System.Resources; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("WpfApp1")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("WpfApp1")] +[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +//In order to begin building localizable applications, set +//CultureYouAreCodingWith in your .csproj file +//inside a . For example, if you are using US english +//in your source files, set the to en-US. Then uncomment +//the NeutralResourceLanguage attribute below. Update the "en-US" in +//the line below to match the UICulture setting in the project file. + +//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] + + +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located + //(used if a resource is not found in the page, + // or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located + //(used if a resource is not found in the page, + // app, or any theme specific resource dictionaries) +)] + + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.Designer.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.Designer.cs new file mode 100644 index 000000000..c8c627c30 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfApp1.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("WpfApp1.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.resx b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.Designer.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.Designer.cs new file mode 100644 index 000000000..6512fd02f --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace WpfApp1.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.settings b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.settings new file mode 100644 index 000000000..033d7a5e9 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/WpfApp2.csproj b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/WpfApp2.csproj new file mode 100644 index 000000000..d9a9b45fc --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/WpfApp2.csproj @@ -0,0 +1,105 @@ + + + + + Debug + AnyCPU + {24009DF8-1CBB-4A38-9A12-3DB32516C204} + WinExe + WpfApp1 + WpfApp1 + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + App.xaml + Code + + + MainWindow.xaml + Code + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + {b87ca1de-ed08-42b6-8d9c-a1c1a602d03b} + Tango.ScreenCapture + + + + \ No newline at end of file -- cgit v1.3.1