aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Experiments
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Experiments')
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop.sln43
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs56
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs138
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs29
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs52
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs94
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs85
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs24
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs46
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs24
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs18
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs18
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs48
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs30
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs42
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs130
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs47
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs16
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs22
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Properties/AssemblyInfo.cs36
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs124
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs46
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs13
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj95
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs236
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs55
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/FastBitmap.cs864
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/BitBltScreenCapture.cs56
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CaptureRegion.cs52
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/CursorUtils.cs236
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DXScreenCapture.cs436
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectBitmap.cs55
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/DirectXScreenCapture.cs138
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/FastBitmap.cs864
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/GdiScreenCapture.cs29
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureFrame.cs25
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/IScreenCaptureMethod.cs14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparer.cs162
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ImageComparerResult.cs25
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Properties/AssemblyInfo.cs36
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/RasterScreenCaptureFrame.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureEngine.cs119
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrame.cs120
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs13
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/ScreenCaptureMethod.cs14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/Tango.ScreenCapture.csproj89
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorDifferenceImage.cs114
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorImagePixel.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/VectorScreenCaptureFrame.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.config14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml9
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/App.xaml.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml12
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs85
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs55
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs71
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx117
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs30
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings7
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj109
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.ScreenCapture/app.config11
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.config14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml9
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/App.xaml.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml12
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs88
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/AssemblyInfo.cs55
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.Designer.cs71
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Resources.resx117
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.Designer.cs30
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/Properties/Settings.settings7
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/WpfApp1.csproj105
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.config14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml9
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/App.xaml.cs17
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml12
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs85
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/AssemblyInfo.cs55
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.Designer.cs71
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Resources.resx117
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.Designer.cs30
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/Properties/Settings.settings7
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/WpfApp2.csproj105
86 files changed, 6708 insertions, 0 deletions
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<Output1>();
+
+ // 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<Texture2D>())
+ 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<RasterFrame>
+ {
+ 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<VectorFrame>
+ {
+ 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<RasterFrame>
+ {
+ 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<VectorFrame>
+ {
+ 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<T>() 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<VectorFramePixel> Pixels { get; private set; }
+
+ public VectorFrame(int width, int height)
+ {
+ _width = width;
+ _height = height;
+ Pixels = new List<VectorFramePixel>();
+ }
+
+ 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<T> 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
+{
+ /// <summary>
+ /// Represents an image frame.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public interface IFrame : IDisposable
+ {
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
+ int Width { get; }
+
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
+ int Height { get; }
+
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
+ Bitmap ToBitmap();
+
+ /// <summary>
+ /// Returns a WPF BitmapSource representing the frame.
+ /// </summary>
+ /// <returns></returns>
+ BitmapSource ToBitmapSource();
+
+ /// <summary>
+ /// Returns an instance of <see cref="IFrameEncoder"/> ready encode this frame.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ IFrameEncoder Encode<T>() 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<TFrame> : IDisposable where TFrame : IFrame
+ {
+ event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived;
+ ICaptureMethod CaptureMethod { get; set; }
+ CaptureRegion CaptureRegion { get; set; }
+ bool IsStarted { get; }
+ TimeSpan Interval { get; set; }
+ bool CaptureCursor { get; set; }
+ IBitmapComparer<TFrame> 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<TFrame> : IScreenCaptureEngine<TFrame> where TFrame : class, IFrame
+ {
+ private Thread _captureThread;
+ private Bitmap _previousBitmap;
+ private bool _isDisposed;
+
+ public event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> 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<TFrame> Comparer { get; set; }
+ public bool EnableComparer { get; set; }
+
+ public ScreenCaptureEngine(IBitmapComparer<TFrame> 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<TFrame>()
+ {
+ Frame = new ScreenCaptureFrame<TFrame>(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<T> : 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<T> : EventArgs where T: IFrame
+ {
+ public ScreenCaptureFrame<T> 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{A78068D4-2061-4376-8EDE-583D8D880DEC}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.RemoteDesktop</RootNamespace>
+ <AssemblyName>Tango.RemoteDesktop</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ <Reference Include="SharpDX">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.Direct3D11">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Direct3D11.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.DXGI">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.DXGI.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.Mathematics">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Mathematics.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Windows" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xaml" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="CaptureRegion.cs" />
+ <Compile Include="CaptureMethods\BitBltScreenCapture.cs" />
+ <Compile Include="CaptureMethods\DirectXScreenCapture.cs" />
+ <Compile Include="CaptureMethods\GdiScreenCapture.cs" />
+ <Compile Include="Comparers\VectorBitmapComparer.cs" />
+ <Compile Include="Engines\RasterScreenCaptureEngine.cs" />
+ <Compile Include="Engines\VectorScreenCaptureEngine.cs" />
+ <Compile Include="IScreenCaptureEngine.cs" />
+ <Compile Include="ScreenCaptureEngine.cs" />
+ <Compile Include="ScreenCaptureFrameReceivedEventArgs.cs" />
+ <Compile Include="Utils\CursorUtils.cs" />
+ <Compile Include="Utils\DirectBitmap.cs" />
+ <Compile Include="Utils\FastBitmap.cs" />
+ <Compile Include="ICaptureMethod.cs" />
+ <Compile Include="Frames\RasterFrame.cs" />
+ <Compile Include="Comparers\RasterBitmapComparer.cs" />
+ <Compile Include="IBitmapComparer.cs" />
+ <Compile Include="Encoders\BitmapEncoder.cs" />
+ <Compile Include="Encoders\JpegEncoder.cs" />
+ <Compile Include="Encoders\PngEncoder.cs" />
+ <Compile Include="Frame.cs" />
+ <Compile Include="FrameEncoder.cs" />
+ <Compile Include="IFrameEncoder.cs" />
+ <Compile Include="ScreenCaptureFrame.cs" />
+ <Compile Include="IFrame.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="Frames\VectorFrame.cs" />
+ <Compile Include="Frames\VectorFramePixel.cs" />
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ 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
+{
+ /// <summary>
+ /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images
+ /// </summary>
+ public unsafe class FastBitmap : IDisposable
+ {
+ /// <summary>
+ /// Specifies the number of bytes available per pixel of the bitmap object being manipulated
+ /// </summary>
+ public const int BytesPerPixel = 4;
+
+ /// <summary>
+ /// The Bitmap object encapsulated on this FastBitmap
+ /// </summary>
+ private readonly Bitmap _bitmap;
+
+ /// <summary>
+ /// The BitmapData resulted from the lock operation
+ /// </summary>
+ private BitmapData _bitmapData;
+
+ /// <summary>
+ /// The first pixel of the bitmap
+ /// </summary>
+ private int* _scan0;
+
+ /// <summary>
+ /// Gets the width of this FastBitmap object
+ /// </summary>
+ public int Width { get; }
+
+ /// <summary>
+ /// Gets the height of this FastBitmap object
+ /// </summary>
+ public int Height { get; }
+
+ /// <summary>
+ /// Gets the pointer to the first pixel of the bitmap
+ /// </summary>
+ public IntPtr Scan0 => _bitmapData.Scan0;
+
+ /// <summary>
+ /// Gets the stride width (in int32-sized values) of the bitmap
+ /// </summary>
+ public int Stride { get; private set; }
+
+ /// <summary>
+ /// Gets the stride width (in bytes) of the bitmap
+ /// </summary>
+ public int StrideInBytes { get; private set; }
+
+ /// <summary>
+ /// Gets a boolean value that states whether this FastBitmap is currently locked in memory
+ /// </summary>
+ public bool Locked { get; private set; }
+
+ /// <summary>
+ /// Gets an array of 32-bit color pixel values that represent this FastBitmap
+ /// </summary>
+ /// <exception cref="Exception">The locking operation required to extract the values off from the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new instance of the FastBitmap class with a specified Bitmap.
+ /// The bitmap provided must have a 32bpp depth
+ /// </summary>
+ /// <param name="bitmap">The Bitmap object to encapsulate on this FastBitmap object</param>
+ /// <exception cref="ArgumentException">The bitmap provided does not have a 32bpp pixel format</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Disposes of this fast bitmap object and releases any pending resources.
+ /// The underlying bitmap is not disposes, and is unlocked, if currently locked
+ /// </summary>
+ public void Dispose()
+ {
+ if (Locked)
+ {
+ Unlock();
+ }
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked,
+ /// an exception is thrown
+ /// </summary>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked</exception>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public FastBitmapLocker Lock()
+ {
+ return Lock((FastBitmapLockFormat)_bitmap.PixelFormat);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked</exception>
+ /// <exception cref="Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat)
+ {
+ if (Locked)
+ {
+ throw new InvalidOperationException("Unlock must be called before a Lock operation");
+ }
+
+ return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations
+ /// </summary>
+ /// <param name="lockMode">The lock mode to use on the bitmap</param>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception>
+ private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat)
+ {
+ var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height);
+
+ return Lock(lockMode, rect, pixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations
+ /// </summary>
+ /// <param name="lockMode">The lock mode to use on the bitmap</param>
+ /// <param name="rect">The rectangle to lock</param>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="System.ArgumentException">The provided region is invalid</exception>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap region is already locked</exception>
+ /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked
+ /// beforehand, an exception is thrown
+ /// </summary>
+ /// <exception cref="InvalidOperationException">The bitmap is already unlocked</exception>
+ /// <exception cref="System.Exception">The unlocking operation in the underlying bitmap failed</exception>
+ public void Unlock()
+ {
+ if (!Locked)
+ {
+ throw new InvalidOperationException("Lock must be called before an Unlock operation");
+ }
+
+ _bitmap.UnlockBits(_bitmapData);
+
+ Locked = false;
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, Color color)
+ {
+ SetPixel(x, y, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, int color)
+ {
+ SetPixel(x, y, unchecked((uint)color));
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public Color GetPixel(int x, int y)
+ {
+ return Color.FromArgb(GetPixelInt(x, y));
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates as an integer value. If the bitmap
+ /// was not locked beforehands, an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates as an unsigned integer value.
+ /// If the bitmap was not locked beforehands, an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="colors">The array of colors to copy</param>
+ /// <param name="ignoreZeroes">Whether to ignore zeroes when copying the data</param>
+ 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++);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears the bitmap with the given color
+ /// </summary>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public void Clear(Color color)
+ {
+ Clear(color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears the bitmap with the given color
+ /// </summary>
+ /// <param name="color">The color to clear the bitmap with</param>
+ 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();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears a square region of this image w/ a given color
+ /// </summary>
+ /// <param name="region"></param>
+ /// <param name="color"></param>
+ public void ClearRegion(Rectangle region, Color color)
+ {
+ ClearRegion(region, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears a square region of this image w/ a given color
+ /// </summary>
+ /// <param name="region"></param>
+ /// <param name="color"></param>
+ 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);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies a region of the source bitmap into this fast bitmap
+ /// </summary>
+ /// <param name="source">The source image to copy</param>
+ /// <param name="srcRect">The region on the source bitmap that will be copied over</param>
+ /// <param name="destRect">The region on this fast bitmap that will be changed</param>
+ /// <exception cref="ArgumentException">The provided source bitmap is the same bitmap locked in this FastBitmap</exception>
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="source">The bitmap to copy the pixels from</param>
+ /// <param name="target">The bitmap to copy the pixels to</param>
+ /// <returns>Whether the copy proceedure was successful</returns>
+ /// <exception cref="ArgumentException">The provided source and target bitmaps are the same</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Clears the given bitmap with the given color
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clear</param>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public static void ClearBitmap(Bitmap bitmap, Color color)
+ {
+ ClearBitmap(bitmap, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears the given bitmap with the given color
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clear</param>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public static void ClearBitmap(Bitmap bitmap, int color)
+ {
+ using (var fb = bitmap.FastLock())
+ {
+ fb.Clear(color);
+ }
+ }
+
+ /// <summary>
+ /// Copies a region of the source bitmap to a target bitmap
+ /// </summary>
+ /// <param name="source">The source image to copy</param>
+ /// <param name="target">The target image to be altered</param>
+ /// <param name="srcRect">The region on the source bitmap that will be copied over</param>
+ /// <param name="destRect">The region on the target bitmap that will be changed</param>
+ /// <exception cref="ArgumentException">The provided source and target bitmaps are the same bitmap</exception>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="source">The source bitmap to slice</param>
+ /// <param name="region">The region of the source bitmap to slice</param>
+ /// <returns>A Bitmap that represents the rectangle region slice of the source bitmap</returns>
+ /// <exception cref="ArgumentException">The provided bimap is not 32bpp</exception>
+ /// <exception cref="ArgumentException">The provided region is invalid</exception>
+ 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
+ /// <summary>
+ /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count);
+
+ /// <summary>
+ /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memcpy(void* dest, void* src, ulong count);
+
+ /// <summary>
+ /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memset(void* dest, int value, ulong count);
+#endif
+
+ /// <summary>
+ /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls
+ /// </summary>
+ public struct FastBitmapLocker : IDisposable
+ {
+ /// <summary>
+ /// Gets the fast bitmap instance attached to this locker
+ /// </summary>
+ public FastBitmap FastBitmap { get; }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="fastBitmap">A fast bitmap to attach to this locker which will be released after a call to Dispose</param>
+ public FastBitmapLocker(FastBitmap fastBitmap)
+ {
+ FastBitmap = fastBitmap;
+ }
+
+ /// <summary>
+ /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap
+ /// </summary>
+ public void Dispose()
+ {
+ if (FastBitmap.Locked)
+ FastBitmap.Unlock();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Describes a pixel format to use when locking a bitmap using <see cref="FastBitmap"/>.
+ /// </summary>
+ public enum FastBitmapLockFormat
+ {
+ /// <summary>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.</summary>
+ Format32bppRgb = 139273,
+ /// <summary>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.</summary>
+ Format32bppPArgb = 925707,
+ /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.</summary>
+ Format32bppArgb = 2498570,
+ }
+
+ /// <summary>
+ /// Static class that contains fast bitmap extension methdos for the Bitmap class
+ /// </summary>
+ public static class FastBitmapExtensions
+ {
+ /// <summary>
+ /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels
+ /// </summary>
+ /// <param name="bitmap">The bitmap to lock</param>
+ /// <returns>A locked FastBitmap</returns>
+ public static FastBitmap FastLock(this Bitmap bitmap)
+ {
+ var fast = new FastBitmap(bitmap);
+ fast.Lock();
+
+ return fast;
+ }
+
+ /// <summary>
+ /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels
+ /// </summary>
+ /// <param name="bitmap">The bitmap to lock</param>
+ /// <param name="lockFormat">The underlying pixel format to use when locking the bitmap</param>
+ /// <returns>A locked FastBitmap</returns>
+ public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat)
+ {
+ var fast = new FastBitmap(bitmap);
+ fast.Lock(lockFormat);
+
+ return fast;
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clone</param>
+ /// <returns>A deep clone of this Bitmap object, with all the data copied over</returns>
+ 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<Output1>();
+
+ // 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);
+ }
+
+ /// <summary>
+ /// Captures the bitmap source.
+ /// </summary>
+ /// <param name="left">The left.</param>
+ /// <param name="top">The top.</param>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
+ /// <param name="captureCursor">Append cursor.</param>
+ /// <returns></returns>
+ [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<Texture2D>())
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Applies the cursor icon.
+ /// </summary>
+ /// <param name="g">The graphics context.</param>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="left">The left.</param>
+ /// <param name="top">The top.</param>
+ [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 { }
+ }
+
+ /// <summary>
+ /// Gets the bitmap image.
+ /// </summary>
+ /// <param name="ms">The ms.</param>
+ /// <returns></returns>
+ private BitmapImage GetBitmapImage(MemoryStream ms)
+ {
+ var bitmapImage = new BitmapImage();
+ bitmapImage.BeginInit();
+ bitmapImage.StreamSource = ms;
+ bitmapImage.EndInit();
+ return bitmapImage;
+ }
+
+ /// <summary>
+ /// Crops the image.
+ /// </summary>
+ /// <param name="img">The img.</param>
+ /// <param name="cropArea">The crop area.</param>
+ /// <returns></returns>
+ 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);
+ }
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ 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<Output1>();
+
+ // 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<Texture2D>())
+ 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
+{
+ /// <summary>
+ /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images
+ /// </summary>
+ public unsafe class FastBitmap : IDisposable
+ {
+ /// <summary>
+ /// Specifies the number of bytes available per pixel of the bitmap object being manipulated
+ /// </summary>
+ public const int BytesPerPixel = 4;
+
+ /// <summary>
+ /// The Bitmap object encapsulated on this FastBitmap
+ /// </summary>
+ private readonly Bitmap _bitmap;
+
+ /// <summary>
+ /// The BitmapData resulted from the lock operation
+ /// </summary>
+ private BitmapData _bitmapData;
+
+ /// <summary>
+ /// The first pixel of the bitmap
+ /// </summary>
+ private int* _scan0;
+
+ /// <summary>
+ /// Gets the width of this FastBitmap object
+ /// </summary>
+ public int Width { get; }
+
+ /// <summary>
+ /// Gets the height of this FastBitmap object
+ /// </summary>
+ public int Height { get; }
+
+ /// <summary>
+ /// Gets the pointer to the first pixel of the bitmap
+ /// </summary>
+ public IntPtr Scan0 => _bitmapData.Scan0;
+
+ /// <summary>
+ /// Gets the stride width (in int32-sized values) of the bitmap
+ /// </summary>
+ public int Stride { get; private set; }
+
+ /// <summary>
+ /// Gets the stride width (in bytes) of the bitmap
+ /// </summary>
+ public int StrideInBytes { get; private set; }
+
+ /// <summary>
+ /// Gets a boolean value that states whether this FastBitmap is currently locked in memory
+ /// </summary>
+ public bool Locked { get; private set; }
+
+ /// <summary>
+ /// Gets an array of 32-bit color pixel values that represent this FastBitmap
+ /// </summary>
+ /// <exception cref="Exception">The locking operation required to extract the values off from the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ 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;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new instance of the FastBitmap class with a specified Bitmap.
+ /// The bitmap provided must have a 32bpp depth
+ /// </summary>
+ /// <param name="bitmap">The Bitmap object to encapsulate on this FastBitmap object</param>
+ /// <exception cref="ArgumentException">The bitmap provided does not have a 32bpp pixel format</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Disposes of this fast bitmap object and releases any pending resources.
+ /// The underlying bitmap is not disposes, and is unlocked, if currently locked
+ /// </summary>
+ public void Dispose()
+ {
+ if (Locked)
+ {
+ Unlock();
+ }
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked,
+ /// an exception is thrown
+ /// </summary>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked</exception>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public FastBitmapLocker Lock()
+ {
+ return Lock((FastBitmapLockFormat)_bitmap.PixelFormat);
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked</exception>
+ /// <exception cref="Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat)
+ {
+ if (Locked)
+ {
+ throw new InvalidOperationException("Unlock must be called before a Lock operation");
+ }
+
+ return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations
+ /// </summary>
+ /// <param name="lockMode">The lock mode to use on the bitmap</param>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception>
+ private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat)
+ {
+ var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height);
+
+ return Lock(lockMode, rect, pixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations
+ /// </summary>
+ /// <param name="lockMode">The lock mode to use on the bitmap</param>
+ /// <param name="rect">The rectangle to lock</param>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="System.ArgumentException">The provided region is invalid</exception>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap region is already locked</exception>
+ /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked
+ /// beforehand, an exception is thrown
+ /// </summary>
+ /// <exception cref="InvalidOperationException">The bitmap is already unlocked</exception>
+ /// <exception cref="System.Exception">The unlocking operation in the underlying bitmap failed</exception>
+ public void Unlock()
+ {
+ if (!Locked)
+ {
+ throw new InvalidOperationException("Lock must be called before an Unlock operation");
+ }
+
+ _bitmap.UnlockBits(_bitmapData);
+
+ Locked = false;
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, Color color)
+ {
+ SetPixel(x, y, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, int color)
+ {
+ SetPixel(x, y, unchecked((uint)color));
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public Color GetPixel(int x, int y)
+ {
+ return Color.FromArgb(GetPixelInt(x, y));
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates as an integer value. If the bitmap
+ /// was not locked beforehands, an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates as an unsigned integer value.
+ /// If the bitmap was not locked beforehands, an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ 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);
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="colors">The array of colors to copy</param>
+ /// <param name="ignoreZeroes">Whether to ignore zeroes when copying the data</param>
+ 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++);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears the bitmap with the given color
+ /// </summary>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public void Clear(Color color)
+ {
+ Clear(color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears the bitmap with the given color
+ /// </summary>
+ /// <param name="color">The color to clear the bitmap with</param>
+ 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();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears a square region of this image w/ a given color
+ /// </summary>
+ /// <param name="region"></param>
+ /// <param name="color"></param>
+ public void ClearRegion(Rectangle region, Color color)
+ {
+ ClearRegion(region, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears a square region of this image w/ a given color
+ /// </summary>
+ /// <param name="region"></param>
+ /// <param name="color"></param>
+ 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);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies a region of the source bitmap into this fast bitmap
+ /// </summary>
+ /// <param name="source">The source image to copy</param>
+ /// <param name="srcRect">The region on the source bitmap that will be copied over</param>
+ /// <param name="destRect">The region on this fast bitmap that will be changed</param>
+ /// <exception cref="ArgumentException">The provided source bitmap is the same bitmap locked in this FastBitmap</exception>
+ 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);
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="source">The bitmap to copy the pixels from</param>
+ /// <param name="target">The bitmap to copy the pixels to</param>
+ /// <returns>Whether the copy proceedure was successful</returns>
+ /// <exception cref="ArgumentException">The provided source and target bitmaps are the same</exception>
+ 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;
+ }
+
+ /// <summary>
+ /// Clears the given bitmap with the given color
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clear</param>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public static void ClearBitmap(Bitmap bitmap, Color color)
+ {
+ ClearBitmap(bitmap, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears the given bitmap with the given color
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clear</param>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public static void ClearBitmap(Bitmap bitmap, int color)
+ {
+ using (var fb = bitmap.FastLock())
+ {
+ fb.Clear(color);
+ }
+ }
+
+ /// <summary>
+ /// Copies a region of the source bitmap to a target bitmap
+ /// </summary>
+ /// <param name="source">The source image to copy</param>
+ /// <param name="target">The target image to be altered</param>
+ /// <param name="srcRect">The region on the source bitmap that will be copied over</param>
+ /// <param name="destRect">The region on the target bitmap that will be changed</param>
+ /// <exception cref="ArgumentException">The provided source and target bitmaps are the same bitmap</exception>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="source">The source bitmap to slice</param>
+ /// <param name="region">The region of the source bitmap to slice</param>
+ /// <returns>A Bitmap that represents the rectangle region slice of the source bitmap</returns>
+ /// <exception cref="ArgumentException">The provided bimap is not 32bpp</exception>
+ /// <exception cref="ArgumentException">The provided region is invalid</exception>
+ 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
+ /// <summary>
+ /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count);
+
+ /// <summary>
+ /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memcpy(void* dest, void* src, ulong count);
+
+ /// <summary>
+ /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memset(void* dest, int value, ulong count);
+#endif
+
+ /// <summary>
+ /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls
+ /// </summary>
+ public struct FastBitmapLocker : IDisposable
+ {
+ /// <summary>
+ /// Gets the fast bitmap instance attached to this locker
+ /// </summary>
+ public FastBitmap FastBitmap { get; }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="fastBitmap">A fast bitmap to attach to this locker which will be released after a call to Dispose</param>
+ public FastBitmapLocker(FastBitmap fastBitmap)
+ {
+ FastBitmap = fastBitmap;
+ }
+
+ /// <summary>
+ /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap
+ /// </summary>
+ public void Dispose()
+ {
+ if (FastBitmap.Locked)
+ FastBitmap.Unlock();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Describes a pixel format to use when locking a bitmap using <see cref="FastBitmap"/>.
+ /// </summary>
+ public enum FastBitmapLockFormat
+ {
+ /// <summary>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.</summary>
+ Format32bppRgb = 139273,
+ /// <summary>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.</summary>
+ Format32bppPArgb = 925707,
+ /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.</summary>
+ Format32bppArgb = 2498570,
+ }
+
+ /// <summary>
+ /// Static class that contains fast bitmap extension methdos for the Bitmap class
+ /// </summary>
+ public static class FastBitmapExtensions
+ {
+ /// <summary>
+ /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels
+ /// </summary>
+ /// <param name="bitmap">The bitmap to lock</param>
+ /// <returns>A locked FastBitmap</returns>
+ public static FastBitmap FastLock(this Bitmap bitmap)
+ {
+ var fast = new FastBitmap(bitmap);
+ fast.Lock();
+
+ return fast;
+ }
+
+ /// <summary>
+ /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels
+ /// </summary>
+ /// <param name="bitmap">The bitmap to lock</param>
+ /// <param name="lockFormat">The underlying pixel format to use when locking the bitmap</param>
+ /// <returns>A locked FastBitmap</returns>
+ public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat)
+ {
+ var fast = new FastBitmap(bitmap);
+ fast.Lock(lockFormat);
+
+ return fast;
+ }
+
+ /// <summary>
+ /// 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
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clone</param>
+ /// <returns>A deep clone of this Bitmap object, with all the data copied over</returns>
+ 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<T> : 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<Bitmap>
+ {
+ 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<ScreenCaptureFrameReceivedEventArgs> 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.ScreenCapture</RootNamespace>
+ <AssemblyName>Tango.ScreenCapture</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <Deterministic>true</Deterministic>
+ <NuGetPackageImportStamp>
+ </NuGetPackageImportStamp>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ <Reference Include="SharpDX">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.Direct3D11">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Direct3D11.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.DXGI">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.DXGI.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.Mathematics">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Mathematics.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Windows" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="BitBltScreenCapture.cs" />
+ <Compile Include="CaptureRegion.cs" />
+ <Compile Include="CursorUtils.cs" />
+ <Compile Include="DirectBitmap.cs" />
+ <Compile Include="DirectXScreenCapture.cs" />
+ <Compile Include="DXScreenCapture.cs" />
+ <Compile Include="FastBitmap.cs" />
+ <Compile Include="GdiScreenCapture.cs" />
+ <Compile Include="ImageComparer.cs" />
+ <Compile Include="ImageComparerResult.cs" />
+ <Compile Include="IScreenCaptureMethod.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ <Compile Include="ScreenCaptureFrame.cs" />
+ <Compile Include="ScreenCaptureEngine.cs" />
+ <Compile Include="ScreenCaptureMethod.cs" />
+ <Compile Include="ScreenCaptureFrameReceivedEventArgs.cs" />
+ <Compile Include="VectorDifferenceImage.cs" />
+ <Compile Include="VectorImagePixel.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="app.config" />
+ </ItemGroup>
+ <ItemGroup />
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ 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<VectorImagePixel> Pixels { get; private set; }
+
+ public VectorDifferenceImage(int width, int height)
+ {
+ Width = width;
+ Height = height;
+ Pixels = new List<VectorImagePixel>();
+ }
+
+ 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<VectorDifferenceImage>
+ {
+ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+ </startup>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="ZedGraph" publicKeyToken="02a83cbd123fcd60" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-5.1.7.430" newVersion="5.1.7.430" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration> \ 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 @@
+<Application x:Class="WpfApp1.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:WpfApp1"
+ StartupUri="MainWindow.xaml">
+ <Application.Resources>
+
+ </Application.Resources>
+</Application>
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
+{
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ 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 @@
+<Window x:Class="WpfApp1.MainWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:local="clr-namespace:WpfApp1"
+ mc:Ignorable="d"
+ Title="MainWindow" Height="450" Width="800">
+ <Grid>
+ <Image Stretch="Fill" x:Name="img" />
+ </Grid>
+</Window>
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
+{
+ /// <summary>
+ /// Interaction logic for MainWindow.xaml
+ /// </summary>
+ 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
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace WpfApp1.Properties
+{
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+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 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{8F28CF3A-9B97-4463-B245-709D11B0AD7F}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <RootNamespace>WpfApp1</RootNamespace>
+ <AssemblyName>WpfApp1</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <ApplicationDefinition Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </ApplicationDefinition>
+ <Page Include="MainWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="MainWindow.xaml.cs">
+ <DependentUpon>MainWindow.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Tango.RemoteDesktop\Tango.RemoteDesktop.csproj">
+ <Project>{a78068d4-2061-4376-8ede-583d8d880dec}</Project>
+ <Name>Tango.RemoteDesktop</Name>
+ </ProjectReference>
+ <ProjectReference Include="..\Tango.ScreenCapture\Tango.ScreenCapture.csproj">
+ <Project>{b87ca1de-ed08-42b6-8d9c-a1c1a602d03b}</Project>
+ <Name>Tango.ScreenCapture</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="ZedGraph" publicKeyToken="02a83cbd123fcd60" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-5.1.7.430" newVersion="5.1.7.430" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+ </startup>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="ZedGraph" publicKeyToken="02a83cbd123fcd60" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-5.1.7.430" newVersion="5.1.7.430" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration> \ 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 @@
+<Application x:Class="WpfApp1.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:WpfApp1"
+ StartupUri="MainWindow.xaml">
+ <Application.Resources>
+
+ </Application.Resources>
+</Application>
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
+{
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ 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 @@
+<Window x:Class="WpfApp1.MainWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:local="clr-namespace:WpfApp1"
+ mc:Ignorable="d"
+ Title="MainWindow" Height="450" Width="800">
+ <Grid>
+ <Image Stretch="Fill" x:Name="img" />
+ </Grid>
+</Window>
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
+{
+ /// <summary>
+ /// Interaction logic for MainWindow.xaml
+ /// </summary>
+ 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<RasterFrame> e)
+ {
+ BitmapSource preview = null;
+
+ int size = 0;
+
+ if (e.Frame.DifferenceAvailable)
+ {
+ var diffFrame = e.Frame.ToDifference();
+
+ size = diffFrame.Encode<PngEncoder>().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<PngEncoder>().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
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace WpfApp1.Properties
+{
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+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 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{8F28CF3A-9B97-4463-B245-709D11B0AD7F}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <RootNamespace>WpfApp1</RootNamespace>
+ <AssemblyName>WpfApp1</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <ApplicationDefinition Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </ApplicationDefinition>
+ <Page Include="MainWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="MainWindow.xaml.cs">
+ <DependentUpon>MainWindow.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Tango.RemoteDesktop\Tango.RemoteDesktop.csproj">
+ <Project>{a78068d4-2061-4376-8ede-583d8d880dec}</Project>
+ <Name>Tango.RemoteDesktop</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+ <startup>
+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
+ </startup>
+ <runtime>
+ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+ <dependentAssembly>
+ <assemblyIdentity name="ZedGraph" publicKeyToken="02a83cbd123fcd60" culture="neutral" />
+ <bindingRedirect oldVersion="0.0.0.0-5.1.7.430" newVersion="5.1.7.430" />
+ </dependentAssembly>
+ </assemblyBinding>
+ </runtime>
+</configuration> \ 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 @@
+<Application x:Class="WpfApp1.App"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:local="clr-namespace:WpfApp1"
+ StartupUri="MainWindow.xaml">
+ <Application.Resources>
+
+ </Application.Resources>
+</Application>
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
+{
+ /// <summary>
+ /// Interaction logic for App.xaml
+ /// </summary>
+ 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 @@
+<Window x:Class="WpfApp1.MainWindow"
+ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:local="clr-namespace:WpfApp1"
+ mc:Ignorable="d"
+ Title="MainWindow" Height="450" Width="800">
+ <Grid>
+ <Image Stretch="Fill" x:Name="img" />
+ </Grid>
+</Window>
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
+{
+ /// <summary>
+ /// Interaction logic for MainWindow.xaml
+ /// </summary>
+ 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
+//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+//inside a <PropertyGroup>. For example, if you are using US english
+//in your source files, set the <UICulture> 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace WpfApp1.Properties
+{
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // 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()
+ {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [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;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+</root> \ 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 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// 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.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+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 @@
+<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
+ <Profiles>
+ <Profile Name="(Default)" />
+ </Profiles>
+ <Settings />
+</SettingsFile> \ 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 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{24009DF8-1CBB-4A38-9A12-3DB32516C204}</ProjectGuid>
+ <OutputType>WinExe</OutputType>
+ <RootNamespace>WpfApp1</RootNamespace>
+ <AssemblyName>WpfApp1</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+ <WarningLevel>4</WarningLevel>
+ <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <PlatformTarget>AnyCPU</PlatformTarget>
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="System" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Xml" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xaml">
+ <RequiredTargetFramework>4.0</RequiredTargetFramework>
+ </Reference>
+ <Reference Include="WindowsBase" />
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ </ItemGroup>
+ <ItemGroup>
+ <ApplicationDefinition Include="App.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </ApplicationDefinition>
+ <Page Include="MainWindow.xaml">
+ <Generator>MSBuild:Compile</Generator>
+ <SubType>Designer</SubType>
+ </Page>
+ <Compile Include="App.xaml.cs">
+ <DependentUpon>App.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="MainWindow.xaml.cs">
+ <DependentUpon>MainWindow.xaml</DependentUpon>
+ <SubType>Code</SubType>
+ </Compile>
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="Properties\AssemblyInfo.cs">
+ <SubType>Code</SubType>
+ </Compile>
+ <Compile Include="Properties\Resources.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DesignTime>True</DesignTime>
+ <DependentUpon>Resources.resx</DependentUpon>
+ </Compile>
+ <Compile Include="Properties\Settings.Designer.cs">
+ <AutoGen>True</AutoGen>
+ <DependentUpon>Settings.settings</DependentUpon>
+ <DesignTimeSharedInput>True</DesignTimeSharedInput>
+ </Compile>
+ <EmbeddedResource Include="Properties\Resources.resx">
+ <Generator>ResXFileCodeGenerator</Generator>
+ <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+ </EmbeddedResource>
+ <None Include="Properties\Settings.settings">
+ <Generator>SettingsSingleFileGenerator</Generator>
+ <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+ </None>
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="App.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\Tango.ScreenCapture\Tango.ScreenCapture.csproj">
+ <Project>{b87ca1de-ed08-42b6-8d9c-a1c1a602d03b}</Project>
+ <Name>Tango.ScreenCapture</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file