aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoy Ben Shabat <Roy.mail.net@gmail.com>2020-03-02 23:30:34 +0200
committerRoy Ben Shabat <Roy.mail.net@gmail.com>2020-03-02 23:30:34 +0200
commit0dcd742a3c35527386a93e1b1ef761c2aeff8308 (patch)
treed5adb3fee35e73af95fa5d68b5316d25522471de
parent1a7fb274158f8a0e279aef26206a65fefac8c4c3 (diff)
downloadTango-0dcd742a3c35527386a93e1b1ef761c2aeff8308.tar.gz
Tango-0dcd742a3c35527386a93e1b1ef761c2aeff8308.zip
Implemented Tango.RemoteDesktop.
Implemented png 8 bit quantization. Implemented RasterFrame bounds clipping. Refactored VectorFrame to use indexed colors.
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs25
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs12
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs23
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs40
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs144
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/ClipResult.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/IScreenCaptureMethod.cs)7
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs30
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs54
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs12
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs101
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs24
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs37
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs12
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs8
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs5
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs37
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs31
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs96
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs139
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs93
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs27
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs14
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs9
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs10
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs16
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs59
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs1851
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs569
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs72
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs335
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs88
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs2531
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs113
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs53
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs8
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj13
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs16
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs28
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml2
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs50
-rw-r--r--Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs1
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/BitmapComparerResult.cs25
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/BitBltScreenCapture.cs)16
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/DirectXScreenCapture.cs)36
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/GdiScreenCapture.cs)18
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureRegion.cs92
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Clipping/BitmapCliper.cs144
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Clipping/ClipResult.cs15
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs120
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs129
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs36
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Encoders/GifEncoder.cs101
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Encoders/JpegEncoder.cs68
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs37
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Encoders/PngEncoder.cs36
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs26
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs23
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Frame.cs79
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/FrameEncoder.cs61
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Frames/RasterFrame.cs134
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrame.cs227
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrameColor.cs93
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFramePixel.cs34
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/IBitmapComparer.cs24
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/ICaptureMethod.cs23
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/IFrame.cs53
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/IFrameEncoder.cs32
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/IScreenCaptureEngine.cs73
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Properties/AssemblyInfo.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/Properties/AssemblyInfo.cs)6
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Quantization/ColorBgra.cs1851
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs569
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Quantization/PaletteTable.cs72
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs335
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Scanline.cs88
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Utility.cs2531
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs185
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrame.cs91
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs21
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj (renamed from Software/Visual_Studio/Tango.ScreenCapture/Tango.ScreenCapture.csproj)65
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/CursorUtils.cs)18
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/DirectBitmap.cs)30
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs (renamed from Software/Visual_Studio/Tango.ScreenCapture/FastBitmap.cs)2
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/CaptureRegion.cs52
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/DXScreenCapture.cs436
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/ImageComparer.cs162
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/ImageComparerResult.cs25
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureEngine.cs119
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrame.cs120
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs13
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureMethod.cs14
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/VectorDifferenceImage.cs114
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/VectorImagePixel.cs17
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.config14
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml9
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml.cs17
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml12
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs85
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs55
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs71
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx117
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs30
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings7
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj109
-rw-r--r--Software/Visual_Studio/Tango.ScreenCapture/app.config11
-rw-r--r--Software/Visual_Studio/Tango.sln54
106 files changed, 14332 insertions, 1759 deletions
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs
new file mode 100644
index 000000000..66a2f8660
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents an <see cref="IBitmapComparer{TFrame}.CreateDifference(System.Drawing.Bitmap, System.Drawing.Bitmap)"/> result.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ public class BitmapComparerResult<TFrame> where TFrame : IFrame
+ {
+ /// <summary>
+ /// Gets or sets the difference frame.
+ /// </summary>
+ public TFrame Frame { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the <see cref="Frame"/> contains any differences.
+ /// </summary>
+ public bool ContainsDifference { get; set; }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
index c41ccbe03..d8f1e214c 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
@@ -8,6 +8,10 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.CaptureMethods
{
+ /// <summary>
+ /// Represents a BitBlt screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
public class BitBltScreenCapture : ICaptureMethod
{
#region Win32 API Screen shot calls
@@ -25,6 +29,11 @@ namespace Tango.RemoteDesktop.CaptureMethods
#endregion
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
public Bitmap GetDesktopBitmap(CaptureRegion region)
{
const int SRCCOPY = 13369376;
@@ -48,6 +57,9 @@ namespace Tango.RemoteDesktop.CaptureMethods
return bitmap;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
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
index 50cb9dc76..1a661b27e 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs
@@ -7,11 +7,16 @@ using System.Threading.Tasks;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
+using Tango.RemoteDesktop.Utils;
using Device = SharpDX.Direct3D11.Device;
using MapFlags = SharpDX.Direct3D11.MapFlags;
namespace Tango.RemoteDesktop.CaptureMethods
{
+ /// <summary>
+ /// Represents a high performance DirectX screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
public class DirectXScreenCapture : ICaptureMethod
{
private static bool _hasInstance;
@@ -22,10 +27,20 @@ namespace Tango.RemoteDesktop.CaptureMethods
private int monitorWidth;
private int monitorHeight;
+ /// <summary>
+ /// Gets or sets the index of the graphics adapter.
+ /// </summary>
public int AdapterIndex { get; set; }
+ /// <summary>
+ /// Gets or sets the index of the monitor within the graphics adapter.
+ /// </summary>
public int MonitorIndex { get; set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DirectXScreenCapture"/> class.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">An instance of the DirectX screen capture method already exists. Please dispose it before attempting to create a new one.</exception>
public DirectXScreenCapture()
{
if (_hasInstance)
@@ -69,6 +84,11 @@ namespace Tango.RemoteDesktop.CaptureMethods
_hasInstance = true;
}
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
public virtual Bitmap GetDesktopBitmap(CaptureRegion region)
{
var screenTexture = new Texture2D(device, textureDesc);
@@ -128,6 +148,9 @@ namespace Tango.RemoteDesktop.CaptureMethods
return bmpImage.Clone(cropArea, bmpImage.PixelFormat);
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public void Dispose()
{
_hasInstance = false;
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
index a9d228e23..92f4403fd 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
@@ -7,11 +7,20 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.CaptureMethods
{
+ /// <summary>
+ /// Represents a standard GDI screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
public class GdiScreenCapture : ICaptureMethod
{
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
public Bitmap GetDesktopBitmap(CaptureRegion region)
{
- var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+ var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bitmap))
{
@@ -21,6 +30,9 @@ namespace Tango.RemoteDesktop.CaptureMethods
return bitmap;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
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
index 18e48de4e..511a61e88 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs
@@ -8,18 +8,43 @@ using System.Windows;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents a capture rectangle region.
+ /// </summary>
public class CaptureRegion
{
+ /// <summary>
+ /// Gets or sets the region left corner.
+ /// </summary>
public int Left { get; set; }
+
+ /// <summary>
+ /// Gets or sets the region top corner.
+ /// </summary>
public int Top { get; set; }
+
+ /// <summary>
+ /// Gets or sets the region width.
+ /// </summary>
public int Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the region height.
+ /// </summary>
public int Height { get; set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class.
+ /// </summary>
public CaptureRegion()
{
}
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class using a <see cref="Rectangle"/> object.
+ /// </summary>
+ /// <param name="rect">The rectangle..</param>
public CaptureRegion(Rectangle rect)
{
Left = rect.Left;
@@ -28,6 +53,10 @@ namespace Tango.RemoteDesktop
Height = rect.Height;
}
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class using a <see cref="Rect"/> object.
+ /// </summary>
+ /// <param name="rect">The rectangle.</param>
public CaptureRegion(Rect rect)
{
Left = (int)rect.Left;
@@ -36,6 +65,13 @@ namespace Tango.RemoteDesktop
Height = (int)rect.Height;
}
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class.
+ /// </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>
public CaptureRegion(int left, int top, int width, int height)
{
Left = left;
@@ -44,6 +80,10 @@ namespace Tango.RemoteDesktop
Height = height;
}
+ /// <summary>
+ /// Creates a capture region from by the bounds of the primary screen.
+ /// </summary>
+ /// <returns></returns>
public static CaptureRegion PrimaryScreenBounds()
{
return new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds);
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs
new file mode 100644
index 000000000..4f93eeb66
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs
@@ -0,0 +1,144 @@
+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.Clipping
+{
+ public class BitmapCliper
+ {
+ public static ClipResult ClipBitmap(Bitmap img)
+ {
+ //get image data
+ BitmapData bd = img.LockBits(new Rectangle(Point.Empty, img.Size),
+ ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ int[] rgbValues = new int[img.Height * img.Width];
+ Marshal.Copy(bd.Scan0, rgbValues, 0, rgbValues.Length);
+ img.UnlockBits(bd);
+
+
+ #region determine bounds
+ int left = bd.Width;
+ int top = bd.Height;
+ int right = 0;
+ int bottom = 0;
+
+ //determine top
+ for (int i = 0; i < rgbValues.Length; i++)
+ {
+ int color = rgbValues[i] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ int r = i / bd.Width;
+ int c = i % bd.Width;
+
+ if (left > c)
+ {
+ left = c;
+ }
+ if (right < c)
+ {
+ right = c;
+ }
+ bottom = r;
+ top = r;
+ break;
+ }
+ }
+
+ //determine bottom
+ for (int i = rgbValues.Length - 1; i >= 0; i--)
+ {
+ int color = rgbValues[i] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ int r = i / bd.Width;
+ int c = i % bd.Width;
+
+ if (left > c)
+ {
+ left = c;
+ }
+ if (right < c)
+ {
+ right = c;
+ }
+ bottom = r;
+ break;
+ }
+ }
+
+ if (bottom > top)
+ {
+ for (int r = top + 1; r < bottom; r++)
+ {
+ //determine left
+ for (int c = 0; c < left; c++)
+ {
+ int color = rgbValues[r * bd.Width + c] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ if (left > c)
+ {
+ left = c;
+ break;
+ }
+ }
+ }
+
+ //determine right
+ for (int c = bd.Width - 1; c > right; c--)
+ {
+ int color = rgbValues[r * bd.Width + c] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ if (right < c)
+ {
+ right = c;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ int width = right - left + 1;
+ int height = bottom - top + 1;
+ #endregion
+
+ if (width < 0 || height < 0)
+ {
+ return new ClipResult()
+ {
+ Bitmap = new Bitmap(1, 1),
+ Bounds = new Rectangle(0, 0, 1, 1)
+ };
+ }
+
+ //copy image data
+ int[] imgData = new int[width * height];
+ for (int r = top; r <= bottom; r++)
+ {
+ Array.Copy(rgbValues, r * bd.Width + left, imgData, (r - top) * width, width);
+ }
+
+ //create new image
+ Bitmap newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+ BitmapData nbd
+ = newImage.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(imgData, 0, nbd.Scan0, imgData.Length);
+ newImage.UnlockBits(nbd);
+
+ return new ClipResult()
+ {
+ Bitmap = newImage,
+ Bounds = new Rectangle(left, top, width, height)
+ };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/IScreenCaptureMethod.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/ClipResult.cs
index 868299ebe..bd0a52679 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/IScreenCaptureMethod.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/ClipResult.cs
@@ -5,10 +5,11 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.Clipping
{
- public interface IScreenCaptureMethod : IDisposable
+ public class ClipResult
{
- Bitmap GetDesktopBitmap(CaptureRegion region);
+ public Bitmap Bitmap { get; set; }
+ public Rectangle Bounds { get; set; }
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs
index fc9de34e1..34250cc59 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs
@@ -9,9 +9,24 @@ using Tango.RemoteDesktop.Frames;
namespace Tango.RemoteDesktop.Comparers
{
+ /// <summary>
+ /// Represents a raster <see cref="IBitmapComparer{TFrame}"/> comparer which will compare and return a <see cref="RasterFrame"/> difference.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IBitmapComparer{Tango.RemoteDesktop.Frames.RasterFrame}" />
public class RasterBitmapComparer : IBitmapComparer<RasterFrame>
{
- unsafe public RasterFrame CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap)
+ /// <summary>
+ /// Creates the difference as <see cref="RasterFrame"/>.
+ /// </summary>
+ /// <param name="previousBitmap">The previous bitmap.</param>
+ /// <param name="currentBitmap">The current bitmap.</param>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException">
+ /// Cannot compare image. They are the same instance
+ /// or
+ /// Cannot compare image of different size.
+ /// </exception>
+ unsafe public BitmapComparerResult<RasterFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap)
{
if (previousBitmap == null | currentBitmap == null)
throw new InvalidOperationException("Cannot compare image. They are the same instance");
@@ -19,6 +34,8 @@ namespace Tango.RemoteDesktop.Comparers
if (previousBitmap.Height != currentBitmap.Height || previousBitmap.Width != currentBitmap.Width)
throw new InvalidOperationException("Cannot compare image of different size.");
+ bool hasDifference = false;
+
Color matchColor = Color.Transparent;
Bitmap diffImage = currentBitmap.Clone() as Bitmap;
@@ -73,6 +90,11 @@ namespace Tango.RemoteDesktop.Comparers
diffPtr[0] = (same == 4) ? swapColor[x] : tmp[x];
diffPtr++; // advance diff image ptr
}
+
+ if (same != 4)
+ {
+ hasDifference = true;
+ }
}
// at the end of each column, skip extra padding
@@ -88,7 +110,11 @@ namespace Tango.RemoteDesktop.Comparers
currentBitmap.UnlockBits(data2);
diffImage.UnlockBits(diffData);
- return new RasterFrame(diffImage);
+ return new BitmapComparerResult<RasterFrame>()
+ {
+ Frame = new RasterFrame(diffImage),
+ ContainsDifference = hasDifference
+ };
}
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs
index 9ffd04c0b..2500e8ac7 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs
@@ -5,16 +5,34 @@ using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
using Tango.RemoteDesktop.Frames;
namespace Tango.RemoteDesktop.Comparers
{
+ /// <summary>
+ /// Represents a vector <see cref="IBitmapComparer{TFrame}"/> comparer which will compare and return a <see cref="VectorFrame"/> difference.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IBitmapComparer{Tango.RemoteDesktop.Frames.VectorFrame}" />
public class VectorBitmapComparer : IBitmapComparer<VectorFrame>
{
- public unsafe VectorFrame CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap)
+ /// <summary>
+ /// Creates the difference as <see cref="VectorFrame"/>.
+ /// </summary>
+ /// <param name="previousBitmap">The previous bitmap.</param>
+ /// <param name="currentBitmap">The current bitmap.</param>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException">
+ /// Cannot compare image. They are the same instance
+ /// or
+ /// Cannot compare image of different size.
+ /// </exception>
+ public unsafe BitmapComparerResult<VectorFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap)
{
VectorFrame vector = new VectorFrame(previousBitmap.Width, previousBitmap.Height);
+ bool hasDifference = false;
+
if (previousBitmap == null | currentBitmap == null)
throw new InvalidOperationException("Cannot compare image. They are the same instance");
@@ -57,14 +75,36 @@ namespace Tango.RemoteDesktop.Comparers
if (same != 4)
{
- vector.Pixels.Add(new VectorFramePixel()
+ int colorIndex = vector.Colors.Count;
+
+ VectorFrameColor color = new VectorFrameColor()
{
- X = (ushort)j,
- Y = (ushort)i,
B = tmp[0],
G = tmp[1],
R = tmp[2],
+ Index = colorIndex
+ };
+
+ VectorFrameColor existingColor;
+
+ if (vector.Colors.TryGetValue(color.ToInt32(), out existingColor))
+ {
+ colorIndex = existingColor.Index;
+ }
+ else
+ {
+ vector.Colors.Add(color.ToInt32(), color);
+ }
+
+ vector.Pixels.Add(new VectorFramePixel()
+ {
+ X = j,
+ Y = i,
+ ColorIndex = colorIndex,
+ Color = color
});
+
+ hasDifference = true;
}
}
@@ -79,7 +119,11 @@ namespace Tango.RemoteDesktop.Comparers
previousBitmap.UnlockBits(data1);
currentBitmap.UnlockBits(data2);
- return vector;
+ return new BitmapComparerResult<VectorFrame>()
+ {
+ Frame = vector,
+ ContainsDifference = hasDifference
+ };
}
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs
index 55fba9d59..18d144518 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs
@@ -8,12 +8,24 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.Encoders
{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> BMP encoder.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" />
public class BitmapEncoder : FrameEncoder
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BitmapEncoder"/> class.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
public BitmapEncoder(IFrame frame) : base(frame)
{
}
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
public override MemoryStream ToStream()
{
MemoryStream ms = new MemoryStream();
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs
new file mode 100644
index 000000000..abf69d9a8
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs
@@ -0,0 +1,101 @@
+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;
+
+namespace Tango.RemoteDesktop.Encoders
+{
+ public class GifEncoder : JpegEncoder
+ {
+ public GifEncoder(IFrame frame) : base(frame)
+ {
+
+ }
+
+ public override MemoryStream ToStream()
+ {
+ return ToStream(100);
+ }
+
+ public override MemoryStream ToStream(long quality)
+ {
+ MemoryStream ms = new MemoryStream();
+ var gif = MakeTransparentGif(Frame.ToBitmap(), Color.Transparent);
+ gif.Save(ms, ImageFormat.Gif);
+ gif.Dispose();
+ ms.Position = 0;
+ return ms;
+ }
+
+ public static Bitmap MakeTransparentGif(Bitmap bitmap, Color color)
+ {
+ byte R = color.R;
+ byte G = color.G;
+ byte B = color.B;
+ MemoryStream fin = new MemoryStream();
+ bitmap.Save(fin, System.Drawing.Imaging.ImageFormat.Gif);
+ MemoryStream fout = new MemoryStream((int)fin.Length);
+ int count = 0;
+ byte[] buf = new byte[256];
+ byte transparentIdx = 0;
+ fin.Seek(0, SeekOrigin.Begin);
+ //header
+ count = fin.Read(buf, 0, 13);
+ if ((buf[0] != 71) || (buf[1] != 73) || (buf[2] != 70)) return null; //GIF
+ fout.Write(buf, 0, 13);
+ int i = 0;
+ if ((buf[10] & 0x80) > 0)
+ {
+ i = 1 << ((buf[10] & 7) + 1) == 256 ? 256 : 0;
+ }
+ for (; i != 0; i--)
+ {
+ fin.Read(buf, 0, 3);
+ if ((buf[0] == R) && (buf[1] == G) && (buf[2] == B))
+ {
+ transparentIdx = (byte)(256 - i);
+ }
+ fout.Write(buf, 0, 3);
+ }
+ bool gcePresent = false;
+ while (true)
+ {
+ fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ if (buf[0] != 0x21) break;
+ fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ gcePresent = (buf[0] == 0xf9);
+ while (true)
+ {
+ fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ if (buf[0] == 0) break;
+ count = buf[0];
+ if (fin.Read(buf, 0, count) != count) return null;
+ if (gcePresent)
+ {
+ if (count == 4)
+ {
+ buf[0] |= 0x01;
+ buf[3] = transparentIdx;
+ }
+ }
+ fout.Write(buf, 0, count);
+ }
+ }
+ while (count > 0)
+ {
+ count = fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ }
+ fin.Close();
+ fout.Flush();
+ return new Bitmap(fout);
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs
index 467e37662..744849977 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs
@@ -8,18 +8,35 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.Encoders
{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> JPEG encoder.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" />
public class JpegEncoder : FrameEncoder
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JpegEncoder"/> class.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
public JpegEncoder(IFrame frame) : base(frame)
{
}
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
public override MemoryStream ToStream()
{
return ToStream(100);
}
- public MemoryStream ToStream(long quality)
+ /// <summary>
+ /// Returns a stream containing the encoded frame with the specified quality.
+ /// </summary>
+ /// <param name="quality">The quality.</param>
+ /// <returns></returns>
+ public virtual MemoryStream ToStream(long quality)
{
var encoderParameters = new EncoderParameters(1);
encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality);
@@ -29,6 +46,11 @@ namespace Tango.RemoteDesktop.Encoders
return ms;
}
+ /// <summary>
+ /// Returns a byte array containing the encoded frame with the specified quality.
+ /// </summary>
+ /// <param name="quality">The quality.</param>
+ /// <returns></returns>
public byte[] ToArray(long quality)
{
using (MemoryStream ms = ToStream(quality))
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs
new file mode 100644
index 000000000..a3993d76d
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs
@@ -0,0 +1,37 @@
+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 Tango.RemoteDesktop.Quantization;
+
+namespace Tango.RemoteDesktop.Encoders
+{
+ public class Png8BitEncoder : FrameEncoder
+ {
+ public Png8BitEncoder(IFrame frame) : base(frame)
+ {
+ }
+
+ public override MemoryStream ToStream()
+ {
+ MemoryStream ms = new MemoryStream();
+ //var quantizer = new OctreeQuantizer(256, true);
+ //var quantized = quantizer.Quantize(Frame.ToBitmap());
+
+ ImageCodecInfo icf = GetEncoder(ImageFormat.Png);
+ EncoderParameters parms = new EncoderParameters(1);
+ EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 8);
+ parms.Param[0] = parm;
+ Frame.ToBitmap().Save(ms, icf, parms);
+
+ ms.Position = 0;
+ //quantized.Dispose();
+
+ return ms;
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs
index dead40302..96b870a77 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs
@@ -8,12 +8,24 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.Encoders
{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> PNG encoder.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" />
public class PngEncoder : FrameEncoder
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PngEncoder"/> class.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
public PngEncoder(IFrame frame) : base(frame)
{
}
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
public override MemoryStream ToStream()
{
MemoryStream ms = new MemoryStream();
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs
index 3cd2a4e84..363cc1a35 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs
@@ -8,8 +8,16 @@ using Tango.RemoteDesktop.Frames;
namespace Tango.RemoteDesktop.Engines
{
+ /// <summary>
+ /// Represents a raster screen capture engine.
+ /// The difference <see cref="IFrame"/> delivered by the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event is of type <see cref="RasterFrame"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ScreenCaptureEngine{Tango.RemoteDesktop.Frames.RasterFrame}" />
public class RasterScreenCaptureEngine : ScreenCaptureEngine<RasterFrame>
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RasterScreenCaptureEngine"/> class.
+ /// </summary>
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
index e065cc6e5..7e14f220f 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs
@@ -8,6 +8,11 @@ using Tango.RemoteDesktop.Frames;
namespace Tango.RemoteDesktop.Engines
{
+ /// <summary>
+ /// Represents a vector screen capture engine.
+ /// The difference <see cref="IFrame"/> delivered by the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event is of type <see cref="VectorFrame"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ScreenCaptureEngine{Tango.RemoteDesktop.Frames.VectorFrame}" />
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
index 95e244a39..93f38bac0 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs
@@ -10,21 +10,44 @@ using System.Windows.Media.Imaging;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents an image frame base class.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IFrame" />
public abstract class Frame : IFrame
{
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
public abstract int Width { get; }
+
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
public abstract int Height { get; }
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
public abstract Bitmap ToBitmap();
- public void Apply(Bitmap bitmap)
+ /// <summary>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public virtual void Apply(Bitmap bitmap)
{
using (Graphics g = Graphics.FromImage(bitmap))
{
- g.DrawImage(ToBitmap(), new Point(0, 0));
+ g.DrawImage(ToBitmap(), new Rectangle(0, 0, bitmap.Width, bitmap.Height));
}
}
+ /// <summary>
+ /// Returns a WPF BitmapSource representing the frame.
+ /// </summary>
+ /// <returns></returns>
public BitmapSource ToBitmapSource()
{
MemoryStream ms = new MemoryStream();
@@ -38,11 +61,19 @@ namespace Tango.RemoteDesktop
return bitmapImage;
}
- public IFrameEncoder Encode<T>() where T : IFrameEncoder
+ /// <summary>
+ /// Returns an instance of <see cref="IFrameEncoder" /> ready to encode this frame.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ public IFrameEncoder ToEncoder<T>() where T : IFrameEncoder
{
return Activator.CreateInstance(typeof(T), new object[] { this }) as IFrameEncoder;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public abstract void Dispose();
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs
index 183dcd1e8..a1413d9c9 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Drawing;
+using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
@@ -8,17 +9,36 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents a base <see cref="IFrameEncoder"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IFrameEncoder" />
public abstract class FrameEncoder : IFrameEncoder
{
+ /// <summary>
+ /// Gets the frame instance.
+ /// </summary>
public IFrame Frame { get; private set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameEncoder"/> class using an existing <see cref="IFrame"/>.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
public FrameEncoder(IFrame frame)
{
Frame = frame;
}
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
public abstract MemoryStream ToStream();
+ /// <summary>
+ /// Returns a byte array containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
public byte[] ToArray()
{
using (var ms = ToStream())
@@ -26,5 +46,16 @@ namespace Tango.RemoteDesktop
return ms.ToArray();
}
}
+
+ /// <summary>
+ /// Gets the image encoder by the specified image format.
+ /// </summary>
+ /// <param name="format">The format.</param>
+ /// <returns></returns>
+ protected 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/Frames/RasterFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs
index 45b84d1ef..bcb372bd3 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs
@@ -7,36 +7,128 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
+using Tango.RemoteDesktop.Clipping;
namespace Tango.RemoteDesktop.Frames
{
+ /// <summary>
+ /// Represents a raster frame encapsulating a standard GDI bitmap.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.Frame" />
public class RasterFrame : Frame
{
private Bitmap _bitmap;
+ /// <summary>
+ /// Gets the left position of this frame.
+ /// Will always be 0 unless <see cref="OptimizeBounds"/> was called.
+ /// </summary>
+ public int Left { get; private set; }
+
+ /// <summary>
+ /// Gets the top position of this frame.
+ /// Will always be 0 unless <see cref="OptimizeBounds"/> was called.
+ /// </summary>
+ public int Top { get; private set; }
+
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
public override int Width
{
get { return _bitmap.Width; }
}
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
public override int Height
{
get { return _bitmap.Height; }
}
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RasterFrame"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
public RasterFrame(Bitmap bitmap)
{
_bitmap = bitmap;
}
- public override void Dispose()
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RasterFrame"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="left">The left position of the frame.</param>
+ /// <param name="top">The top position of the frame.</param>
+ public RasterFrame(Bitmap bitmap, int left, int top) : this(bitmap)
{
- _bitmap.Dispose();
+ Left = left;
+ Top = top;
}
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
public override Bitmap ToBitmap()
{
return _bitmap;
}
+
+ /// <summary>
+ /// Scales the frame by the specified coefficient.
+ /// </summary>
+ /// <param name="coefficient">The coefficient.</param>
+ /// <returns></returns>
+ public RasterFrame Scale(float coefficient)
+ {
+ var scaled = new Bitmap(_bitmap, new Size((int)(Width * coefficient), (int)(Height * coefficient)));
+ _bitmap.Dispose();
+ _bitmap = scaled;
+ return this;
+ }
+
+ /// <summary>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public override void Apply(Bitmap bitmap)
+ {
+ if (Top == 0 && Left == 0)
+ {
+ base.Apply(bitmap);
+ }
+ else
+ {
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.DrawImage(_bitmap, new Rectangle(Left, Top, Width, Height));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Optimizes the bounds of this frame by removing unnecessary margins.
+ /// </summary>
+ /// <returns></returns>
+ public RasterFrame OptimizeBounds()
+ {
+ var result = BitmapCliper.ClipBitmap(_bitmap);
+ _bitmap.Dispose();
+ Left = result.Bounds.Left;
+ Top = result.Bounds.Top;
+ _bitmap = result.Bitmap;
+ return this;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public override void Dispose()
+ {
+ _bitmap.Dispose();
+ }
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs
index 72bcf53ea..89287ad42 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs
@@ -9,70 +9,117 @@ using Tango.RemoteDesktop.Utils;
namespace Tango.RemoteDesktop.Frames
{
+ /// <summary>
+ /// Represents a vector frame encapsulating a set of <see cref="VectorFramePixel"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.Frame" />
public class VectorFrame : Frame
{
private int _width;
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
public override int Width
{
get { return _width; }
}
private int _height;
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
public override int Height
{
get { return _height; }
}
+ /// <summary>
+ /// Gets or sets the colors dictionary.
+ /// </summary>
+ public Dictionary<int, VectorFrameColor> Colors { get; set; }
+
+ /// <summary>
+ /// Gets the pixels.
+ /// </summary>
+ /// <value>
+ /// The pixels.
+ /// </value>
public List<VectorFramePixel> Pixels { get; private set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="VectorFrame"/> class.
+ /// </summary>
+ /// <param name="width">The frame width.</param>
+ /// <param name="height">The frame height.</param>
public VectorFrame(int width, int height)
{
_width = width;
_height = height;
Pixels = new List<VectorFramePixel>();
+ Colors = new Dictionary<int, VectorFrameColor>();
}
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
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.SetPixel((int)pixel.X, (int)pixel.Y, pixel.Color.ToColor());
}
directBitmap.Dispose();
return directBitmap.Bitmap;
}
- public void ApplyUsingFastBitmap(Bitmap bitmap)
+ /// <summary>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public override void Apply(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));
+ fast.SetPixel((int)pixel.X, (int)pixel.Y, pixel.Color.ToColor());
}
}
}
+ /// <summary>
+ /// Returns a byte array containing the binary serialized version of this frame.
+ /// </summary>
+ /// <returns></returns>
public byte[] Serialize()
{
using (MemoryStream ms = new MemoryStream())
{
using (BinaryWriter writer = new BinaryWriter(ms))
{
- writer.Write(Width);
- writer.Write(Height);
- writer.Write(Pixels.Count);
+ writer.Write((UInt16)Width);
+ writer.Write((UInt16)Height);
+ writer.Write((UInt16)Colors.Count);
- foreach (var pixel in Pixels)
+ if (Colors.Count > UInt16.MaxValue)
{
- writer.Write(pixel.X);
- writer.Write(pixel.Y);
+ throw new InvalidOperationException("Number of color exceeded the maximum colors allowed.");
+ }
- writer.Write(pixel.R);
- writer.Write(pixel.G);
- writer.Write(pixel.B);
+ foreach (var color in Colors)
+ {
+ writer.Write(color.Value.R);
+ writer.Write(color.Value.G);
+ writer.Write(color.Value.B);
+ }
+
+ foreach (var pixel in Pixels)
+ {
+ writer.Write((UInt16)pixel.X);
+ writer.Write((UInt16)pixel.ColorIndex);
}
ms.Position = 0;
@@ -81,6 +128,10 @@ namespace Tango.RemoteDesktop.Frames
}
}
+ /// <summary>
+ /// Calculates the size of <see cref="Serialize"/> byte output.
+ /// </summary>
+ /// <returns></returns>
public int CalculateSize()
{
int size = 0;
@@ -92,6 +143,11 @@ namespace Tango.RemoteDesktop.Frames
return size;
}
+ /// <summary>
+ /// Creates a new instance of <see cref="VectorFrame"/> from the specified serialized byte array.
+ /// </summary>
+ /// <param name="data">The byte array.</param>
+ /// <returns></returns>
public static VectorFrame Deserialize(byte[] data)
{
using (MemoryStream ms = new MemoryStream(data))
@@ -99,20 +155,30 @@ namespace Tango.RemoteDesktop.Frames
using (BinaryReader reader = new BinaryReader(ms))
{
ms.Position = 0;
- var vector = new VectorFrame(reader.ReadInt32(), reader.ReadInt32());
+ var vector = new VectorFrame(reader.ReadUInt16(), reader.ReadUInt16());
- int count = reader.ReadInt32();
+ int colorCount = reader.ReadUInt16(); //Number of colors in color table.
- for (int i = 0; i < count; i++)
+ for (int i = 0; i < colorCount; i++)
{
- VectorFramePixel pixel = new VectorFramePixel();
- pixel.X = reader.ReadUInt16();
- pixel.Y = reader.ReadUInt16();
+ VectorFrameColor color = new VectorFrameColor()
+ {
+ R = reader.ReadByte(),
+ G = reader.ReadByte(),
+ B = reader.ReadByte(),
+ Index = i,
+ };
- pixel.R = reader.ReadByte();
- pixel.G = reader.ReadByte();
- pixel.B = reader.ReadByte();
+ vector.Colors.Add(color.ToInt32(), color);
+ }
+ for (int i = 0; i < vector.Height; i++)
+ {
+ VectorFramePixel pixel = new VectorFramePixel();
+ pixel.X = reader.ReadUInt16();
+ pixel.Y = i;
+ pixel.ColorIndex = reader.ReadUInt16();
+ pixel.Color = vector.Colors.ElementAt(pixel.ColorIndex).Value;
vector.Pixels.Add(pixel);
}
@@ -121,10 +187,41 @@ namespace Tango.RemoteDesktop.Frames
}
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public override void Dispose()
{
Pixels.Clear();
Pixels = null;
}
+
+ #region Color Helpers
+
+ /// <summary>
+ /// Converts the specified color to integer.
+ /// </summary>
+ /// <param name="color">The color.</param>
+ /// <returns></returns>
+ public static int ColorToInteger(byte r, byte g, byte b)
+ {
+ return (int)((255 << 24) | (r << 16) | (g << 8) | (b << 0));
+ }
+
+ /// <summary>
+ /// Converts the specified integer to color.
+ /// </summary>
+ /// <param name="integer">The integer.</param>
+ /// <returns></returns>
+ public static Color IntegerToColor(int integer)
+ {
+ byte a = (byte)(integer >> 24);
+ byte r = (byte)(integer >> 16);
+ byte g = (byte)(integer >> 8);
+ byte b = (byte)(integer >> 0);
+ return Color.FromArgb(a, r, g, b);
+ }
+
+ #endregion
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs
new file mode 100644
index 000000000..4bdb9ab68
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Frames
+{
+ public struct VectorFrameColor
+ {
+ /// <summary>
+ /// Gets or sets the pixel color red component.
+ /// </summary>
+ public byte R { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pixel color green component.
+ /// </summary>
+ public byte G { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pixel color blue component.
+ /// </summary>
+ public byte B { get; set; }
+
+ /// <summary>
+ /// Gets or sets the color index within the dictionary.
+ /// </summary>
+ public int Index { get; set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
+ /// </summary>
+ /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
+ /// <returns>
+ /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
+ /// </returns>
+ public override bool Equals(object obj)
+ {
+ var other = (VectorFrameColor)obj;
+ return R == other.R && G == other.G && B == other.B;
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ /// </returns>
+ public override int GetHashCode()
+ {
+ var hashCode = -1520100960;
+ hashCode = hashCode * -1521134295 + R.GetHashCode();
+ hashCode = hashCode * -1521134295 + G.GetHashCode();
+ hashCode = hashCode * -1521134295 + B.GetHashCode();
+ return hashCode;
+ }
+
+ /// <summary>
+ /// Returns a 32 bit integer representing this color.
+ /// </summary>
+ /// <returns></returns>
+ public int ToInt32()
+ {
+ return (int)((255 << 24) | (R << 16) | (G << 8) | (B << 0));
+ }
+
+ /// <summary>
+ /// Creates a color from the specified 32 bit integer.
+ /// </summary>
+ /// <param name="integer">The integer.</param>
+ /// <returns></returns>
+ public static VectorFrameColor FromInt32(int integer)
+ {
+ byte a = (byte)(integer >> 24);
+ byte r = (byte)(integer >> 16);
+ byte g = (byte)(integer >> 8);
+ byte b = (byte)(integer >> 0);
+ return new VectorFrameColor()
+ {
+ R = r,
+ G = g,
+ B = g,
+ };
+ }
+
+ public Color ToColor()
+ {
+ return Color.FromArgb(R, G, B);
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs
index 048b7b94b..df1a71232 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs
@@ -1,17 +1,34 @@
using System;
using System.Collections.Generic;
+using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Tango.RemoteDesktop.Frames
{
+ /// <summary>
+ /// Represents a <see cref="VectorFrame"/> pixel.
+ /// </summary>
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; }
+ /// <summary>
+ /// Gets or sets the x position.
+ /// </summary>
+ public int X { get; set; }
+ /// <summary>
+ /// Gets or sets the y position.
+ /// </summary>
+ public int Y { get; set; }
+
+ /// <summary>
+ /// Gets or sets the index of the color.
+ /// </summary>
+ public int ColorIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pixel color (not for serialization!).
+ /// </summary>
+ public VectorFrameColor Color { get; set; }
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs
index f20edac98..6e1f8e999 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs
@@ -7,8 +7,18 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop
{
- public interface IBitmapComparer<T> where T : IFrame
+ /// <summary>
+ /// Represents a bitmap comparison engine which returns a difference object of type <see cref="TFrame"/> between a previous and current bitmaps.
+ /// </summary>
+ /// <typeparam name="TFrame">Type of frame</typeparam>
+ public interface IBitmapComparer<TFrame> where TFrame : IFrame
{
- T CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap);
+ /// <summary>
+ /// Creates the difference result from two bitmaps.
+ /// </summary>
+ /// <param name="previousBitmap">The previous bitmap.</param>
+ /// <param name="currentBitmap">The current bitmap.</param>
+ /// <returns></returns>
+ BitmapComparerResult<TFrame> 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
index b93df2109..0ac8add89 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs
@@ -7,8 +7,17 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents a screen capture method.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
public interface ICaptureMethod : IDisposable
{
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
Bitmap GetDesktopBitmap(CaptureRegion region);
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs
index a217d7189..f5a69218d 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs
@@ -38,10 +38,16 @@ namespace Tango.RemoteDesktop
BitmapSource ToBitmapSource();
/// <summary>
- /// Returns an instance of <see cref="IFrameEncoder"/> ready encode this frame.
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ void Apply(Bitmap bitmap);
+
+ /// <summary>
+ /// Returns an instance of <see cref="IFrameEncoder"/> ready to encode this frame.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
- IFrameEncoder Encode<T>() where T : IFrameEncoder;
+ IFrameEncoder ToEncoder<T>() where T : IFrameEncoder;
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs
index 946c90479..2df616ab8 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs
@@ -7,10 +7,26 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> encoder.
+ /// </summary>
public interface IFrameEncoder
{
+ /// <summary>
+ /// Gets the frame instance.
+ /// </summary>
IFrame Frame { get; }
+
+ /// <summary>
+ /// Returns a byte array containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
byte[] ToArray();
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
MemoryStream ToStream();
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs
index 17623180c..6b5db2fbe 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs
@@ -6,17 +6,68 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop
{
- public interface IScreenCaptureEngine<TFrame> : IDisposable where TFrame : IFrame
+ /// <summary>
+ /// Represents a screen capture engine.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public interface IScreenCaptureEngine : IDisposable
{
- event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived;
+ /// <summary>
+ /// Gets or sets the screen capture method.
+ /// </summary>
ICaptureMethod CaptureMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the screen capture region.
+ /// </summary>
CaptureRegion CaptureRegion { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is currently capturing.
+ /// </summary>
bool IsStarted { get; }
- TimeSpan Interval { get; set; }
+
+ /// <summary>
+ /// Gets or sets the frame rate per second.
+ /// </summary>
+ int FrameRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to include the cursor when capturing.
+ /// </summary>
bool CaptureCursor { get; set; }
- IBitmapComparer<TFrame> Comparer { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable image comparison.
+ /// </summary>
bool EnableComparer { get; set; }
+
+ /// <summary>
+ /// Start capturing.
+ /// </summary>
void Start();
+
+ /// <summary>
+ /// Stop capturing.
+ /// </summary>
void Stop();
}
+
+ /// <summary>
+ /// Represents a screen capture engine.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ /// <seealso cref="System.IDisposable" />
+ public interface IScreenCaptureEngine<TFrame> : IScreenCaptureEngine where TFrame : IFrame
+ {
+ /// <summary>
+ /// Occurs when a new screen frame is available.
+ /// </summary>
+ event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived;
+
+ /// <summary>
+ /// Gets or sets the bitmap comparer.
+ /// </summary>
+ IBitmapComparer<TFrame> Comparer { get; set; }
+ }
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs
new file mode 100644
index 000000000..87c9e9637
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs
@@ -0,0 +1,1851 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ /// <summary>
+ /// This is our pixel format that we will work with. It is always 32-bits / 4-bytes and is
+ /// always laid out in BGRA order.
+ /// Generally used with the Surface class.
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct ColorBgra
+ {
+ [FieldOffset(0)]
+ public byte B;
+
+ [FieldOffset(1)]
+ public byte G;
+
+ [FieldOffset(2)]
+ public byte R;
+
+ [FieldOffset(3)]
+ public byte A;
+
+ /// <summary>
+ /// Lets you change B, G, R, and A at the same time.
+ /// </summary>
+ [NonSerialized]
+ [FieldOffset(0)]
+ public uint Bgra;
+
+ public const int BlueChannel = 0;
+ public const int GreenChannel = 1;
+ public const int RedChannel = 2;
+ public const int AlphaChannel = 3;
+
+ public const int SizeOf = 4;
+
+ public static ColorBgra ParseHexString(string hexString)
+ {
+ uint value = Convert.ToUInt32(hexString, 16);
+ return ColorBgra.FromUInt32(value);
+ }
+
+ public string ToHexString()
+ {
+ int rgbNumber = (this.R << 16) | (this.G << 8) | this.B;
+ string colorString = Convert.ToString(rgbNumber, 16);
+
+ while (colorString.Length < 6)
+ {
+ colorString = "0" + colorString;
+ }
+
+ string alphaString = System.Convert.ToString(this.A, 16);
+
+ while (alphaString.Length < 2)
+ {
+ alphaString = "0" + alphaString;
+ }
+
+ colorString = alphaString + colorString;
+
+ return colorString.ToUpper();
+ }
+
+ /// <summary>
+ /// Gets or sets the byte value of the specified color channel.
+ /// </summary>
+ public unsafe byte this[int channel]
+ {
+ get
+ {
+ if (channel < 0 || channel > 3)
+ {
+ throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]");
+ }
+
+ fixed (byte *p = &B)
+ {
+ return p[channel];
+ }
+ }
+
+ set
+ {
+ if (channel < 0 || channel > 3)
+ {
+ throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]");
+ }
+
+ fixed (byte *p = &B)
+ {
+ p[channel] = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored.
+ /// </summary>
+ /// <returns>A value in the range 0 to 1 inclusive.</returns>
+ public double GetIntensity()
+ {
+ return ((0.114 * (double)B) + (0.587 * (double)G) + (0.299 * (double)R)) / 255.0;
+ }
+
+ /// <summary>
+ /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored.
+ /// </summary>
+ /// <returns>A value in the range 0 to 255 inclusive.</returns>
+ public byte GetIntensityByte()
+ {
+ return (byte)((7471 * B + 38470 * G + 19595 * R) >> 16);
+ }
+
+ /// <summary>
+ /// Returns the maximum value out of the B, G, and R values. Alpha is ignored.
+ /// </summary>
+ /// <returns></returns>
+ public byte GetMaxColorChannelValue()
+ {
+ return Math.Max(this.B, Math.Max(this.G, this.R));
+ }
+
+ /// <summary>
+ /// Returns the average of the B, G, and R values. Alpha is ignored.
+ /// </summary>
+ /// <returns></returns>
+ public byte GetAverageColorChannelValue()
+ {
+ return (byte)((this.B + this.G + this.R) / 3);
+ }
+
+ /// <summary>
+ /// Compares two ColorBgra instance to determine if they are equal.
+ /// </summary>
+ public static bool operator == (ColorBgra lhs, ColorBgra rhs)
+ {
+ return lhs.Bgra == rhs.Bgra;
+ }
+
+ /// <summary>
+ /// Compares two ColorBgra instance to determine if they are not equal.
+ /// </summary>
+ public static bool operator != (ColorBgra lhs, ColorBgra rhs)
+ {
+ return lhs.Bgra != rhs.Bgra;
+ }
+
+ /// <summary>
+ /// Compares two ColorBgra instance to determine if they are equal.
+ /// </summary>
+ public override bool Equals(object obj)
+ {
+
+ if (obj != null && obj is ColorBgra && ((ColorBgra)obj).Bgra == this.Bgra)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns a hash code for this color value.
+ /// </summary>
+ /// <returns></returns>
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (int)Bgra;
+ }
+ }
+
+ /// <summary>
+ /// Gets the equivalent GDI+ PixelFormat.
+ /// </summary>
+ /// <remarks>
+ /// This property always returns PixelFormat.Format32bppArgb.
+ /// </remarks>
+ public static PixelFormat PixelFormat
+ {
+ get
+ {
+ return PixelFormat.Format32bppArgb;
+ }
+ }
+
+ /// <summary>
+ /// Returns a new ColorBgra with the same color values but with a new alpha component value.
+ /// </summary>
+ public ColorBgra NewAlpha(byte newA)
+ {
+ return ColorBgra.FromBgra(B, G, R, newA);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ [Obsolete ("Use FromBgra() instead (make sure to swap the order of your b and r parameters)")]
+ public static ColorBgra FromRgba(byte r, byte g, byte b, byte a)
+ {
+ return FromBgra(b, g, r, a);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color values, and 255 for alpha.
+ /// </summary>
+ [Obsolete ("Use FromBgr() instead (make sure to swap the order of your b and r parameters)")]
+ public static ColorBgra FromRgb(byte r, byte g, byte b)
+ {
+ return FromBgr(b, g, r);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ public static ColorBgra FromBgra(byte b, byte g, byte r, byte a)
+ {
+ ColorBgra color = new ColorBgra();
+ color.Bgra = BgraToUInt32(b, g, r, a);
+ return color;
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ public static ColorBgra FromBgraClamped(int b, int g, int r, int a)
+ {
+ return FromBgra(
+ Utility.ClampToByte(b),
+ Utility.ClampToByte(g),
+ Utility.ClampToByte(r),
+ Utility.ClampToByte(a));
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ public static ColorBgra FromBgraClamped(float b, float g, float r, float a)
+ {
+ return FromBgra(
+ Utility.ClampToByte(b),
+ Utility.ClampToByte(g),
+ Utility.ClampToByte(r),
+ Utility.ClampToByte(a));
+ }
+
+ /// <summary>
+ /// Packs color and alpha values into a 32-bit integer.
+ /// </summary>
+ public static UInt32 BgraToUInt32(byte b, byte g, byte r, byte a)
+ {
+ return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24);
+ }
+
+ /// <summary>
+ /// Packs color and alpha values into a 32-bit integer.
+ /// </summary>
+ public static UInt32 BgraToUInt32(int b, int g, int r, int a)
+ {
+ return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color values, and 255 for alpha.
+ /// </summary>
+ public static ColorBgra FromBgr(byte b, byte g, byte r)
+ {
+ return FromBgra(b, g, r, 255);
+ }
+
+ /// <summary>
+ /// Constructs a new ColorBgra instance with the given 32-bit value.
+ /// </summary>
+ public static ColorBgra FromUInt32(UInt32 bgra)
+ {
+ ColorBgra color = new ColorBgra();
+ color.Bgra = bgra;
+ return color;
+ }
+
+ /// <summary>
+ /// Constructs a new ColorBgra instance given a 32-bit signed integer that represents an R,G,B triple.
+ /// Alpha will be initialized to 255.
+ /// </summary>
+ public static ColorBgra FromOpaqueInt32(Int32 bgr)
+ {
+ if (bgr < 0 || bgr > 0xffffff)
+ {
+ throw new ArgumentOutOfRangeException("bgr", "must be in the range [0, 0xffffff]");
+ }
+
+ ColorBgra color = new ColorBgra();
+ color.Bgra = (uint)bgr;
+ color.A = 255;
+
+ return color;
+ }
+
+ public static int ToOpaqueInt32(ColorBgra color)
+ {
+ if (color.A != 255)
+ {
+ throw new InvalidOperationException("Alpha value must be 255 for this to work");
+ }
+
+ return (int)(color.Bgra & 0xffffff);
+ }
+
+ /// <summary>
+ /// Constructs a new ColorBgra instance from the values in the given Color instance.
+ /// </summary>
+ public static ColorBgra FromColor(Color c)
+ {
+ return FromBgra(c.B, c.G, c.R, c.A);
+ }
+
+ /// <summary>
+ /// Converts this ColorBgra instance to a Color instance.
+ /// </summary>
+ public Color ToColor()
+ {
+ return Color.FromArgb(A, R, G, B);
+ }
+
+ /// <summary>
+ /// Smoothly blends between two colors.
+ /// </summary>
+ public static ColorBgra Blend(ColorBgra ca, ColorBgra cb, byte cbAlpha)
+ {
+ uint caA = (uint)Utility.FastScaleByteByByte((byte)(255 - cbAlpha), ca.A);
+ uint cbA = (uint)Utility.FastScaleByteByByte(cbAlpha, cb.A);
+ uint cbAT = caA + cbA;
+
+ uint r;
+ uint g;
+ uint b;
+
+ if (cbAT == 0)
+ {
+ r = 0;
+ g = 0;
+ b = 0;
+ }
+ else
+ {
+ r = ((ca.R * caA) + (cb.R * cbA)) / cbAT;
+ g = ((ca.G * caA) + (cb.G * cbA)) / cbAT;
+ b = ((ca.B * caA) + (cb.B * cbA)) / cbAT;
+ }
+
+ return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)cbAT);
+ }
+
+ /// <summary>
+ /// Linearly interpolates between two color values.
+ /// </summary>
+ /// <param name="from">The color value that represents 0 on the lerp number line.</param>
+ /// <param name="to">The color value that represents 1 on the lerp number line.</param>
+ /// <param name="frac">A value in the range [0, 1].</param>
+ /// <remarks>
+ /// This method does a simple lerp on each color value and on the alpha channel. It does
+ /// not properly take into account the alpha channel's effect on color blending.
+ /// </remarks>
+ public static ColorBgra Lerp(ColorBgra from, ColorBgra to, float frac)
+ {
+ ColorBgra ret = new ColorBgra();
+
+ ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac));
+ ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac));
+ ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac));
+ ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac));
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Linearly interpolates between two color values.
+ /// </summary>
+ /// <param name="from">The color value that represents 0 on the lerp number line.</param>
+ /// <param name="to">The color value that represents 1 on the lerp number line.</param>
+ /// <param name="frac">A value in the range [0, 1].</param>
+ /// <remarks>
+ /// This method does a simple lerp on each color value and on the alpha channel. It does
+ /// not properly take into account the alpha channel's effect on color blending.
+ /// </remarks>
+ public static ColorBgra Lerp(ColorBgra from, ColorBgra to, double frac)
+ {
+ ColorBgra ret = new ColorBgra();
+
+ ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac));
+ ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac));
+ ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac));
+ ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac));
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Blends four colors together based on the given weight values.
+ /// </summary>
+ /// <returns>The blended color.</returns>
+ /// <remarks>
+ /// The weights should be 16-bit fixed point numbers that add up to 65536 ("1.0").
+ /// 4W16IP means "4 colors, weights, 16-bit integer precision"
+ /// </remarks>
+ public static ColorBgra BlendColors4W16IP(ColorBgra c1, uint w1, ColorBgra c2, uint w2, ColorBgra c3, uint w3, ColorBgra c4, uint w4)
+ {
+#if DEBUG
+ /*
+ if ((w1 + w2 + w3 + w4) != 65536)
+ {
+ throw new ArgumentOutOfRangeException("w1 + w2 + w3 + w4 must equal 65536!");
+ }
+ * */
+#endif
+
+ const uint ww = 32768;
+ uint af = (c1.A * w1) + (c2.A * w2) + (c3.A * w3) + (c4.A * w4);
+ uint a = (af + ww) >> 16;
+
+ uint b;
+ uint g;
+ uint r;
+
+ if (a == 0)
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+ }
+ else
+ {
+ b = (uint)((((long)c1.A * c1.B * w1) + ((long)c2.A * c2.B * w2) + ((long)c3.A * c3.B * w3) + ((long)c4.A * c4.B * w4)) / af);
+ g = (uint)((((long)c1.A * c1.G * w1) + ((long)c2.A * c2.G * w2) + ((long)c3.A * c3.G * w3) + ((long)c4.A * c4.G * w4)) / af);
+ r = (uint)((((long)c1.A * c1.R * w1) + ((long)c2.A * c2.R * w2) + ((long)c3.A * c3.R * w3) + ((long)c4.A * c4.R * w4)) / af);
+ }
+
+ return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a);
+ }
+
+ /// <summary>
+ /// Blends the colors based on the given weight values.
+ /// </summary>
+ /// <param name="c">The array of color values.</param>
+ /// <param name="w">The array of weight values.</param>
+ /// <returns>
+ /// The weights should be fixed point numbers.
+ /// The total summation of the weight values will be treated as "1.0".
+ /// Each color will be blended in proportionally to its weight value respective to
+ /// the total summation of the weight values.
+ /// </returns>
+ /// <remarks>
+ /// "WAIP" stands for "weights, arbitrary integer precision"</remarks>
+ public static ColorBgra BlendColorsWAIP(ColorBgra[] c, uint[] w)
+ {
+ if (c.Length != w.Length)
+ {
+ throw new ArgumentException("c.Length != w.Length");
+ }
+
+ if (c.Length == 0)
+ {
+ return ColorBgra.FromUInt32(0);
+ }
+
+ long wsum = 0;
+ long asum = 0;
+
+ for (int i = 0; i < w.Length; ++i)
+ {
+ wsum += w[i];
+ asum += c[i].A * w[i];
+ }
+
+ uint a = (uint)((asum + (wsum >> 1)) / wsum);
+
+ long b;
+ long g;
+ long r;
+
+ if (a == 0)
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+ }
+ else
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+
+ for (int i = 0; i < c.Length; ++i)
+ {
+ b += (long)c[i].A * c[i].B * w[i];
+ g += (long)c[i].A * c[i].G * w[i];
+ r += (long)c[i].A * c[i].R * w[i];
+ }
+
+ b /= asum;
+ g /= asum;
+ r /= asum;
+ }
+
+ return ColorBgra.FromUInt32((uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24));
+ }
+
+ /// <summary>
+ /// Blends the colors based on the given weight values.
+ /// </summary>
+ /// <param name="c">The array of color values.</param>
+ /// <param name="w">The array of weight values.</param>
+ /// <returns>
+ /// Each color will be blended in proportionally to its weight value respective to
+ /// the total summation of the weight values.
+ /// </returns>
+ /// <remarks>
+ /// "WAIP" stands for "weights, floating-point"</remarks>
+ public static ColorBgra BlendColorsWFP(ColorBgra[] c, double[] w)
+ {
+ if (c.Length != w.Length)
+ {
+ throw new ArgumentException("c.Length != w.Length");
+ }
+
+ if (c.Length == 0)
+ {
+ return ColorBgra.Transparent;
+ }
+
+ double wsum = 0;
+ double asum = 0;
+
+ for (int i = 0; i < w.Length; ++i)
+ {
+ wsum += w[i];
+ asum += (double)c[i].A * w[i];
+ }
+
+ double a = asum / wsum;
+ double aMultWsum = a * wsum;
+
+ double b;
+ double g;
+ double r;
+
+ if (asum == 0)
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+ }
+ else
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+
+ for (int i = 0; i < c.Length; ++i)
+ {
+ b += (double)c[i].A * c[i].B * w[i];
+ g += (double)c[i].A * c[i].G * w[i];
+ r += (double)c[i].A * c[i].R * w[i];
+ }
+
+ b /= aMultWsum;
+ g /= aMultWsum;
+ r /= aMultWsum;
+ }
+
+ return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a);
+ }
+
+ public static ColorBgra Blend(ColorBgra[] colors)
+ {
+ unsafe
+ {
+ fixed (ColorBgra* pColors = colors)
+ {
+ return Blend(pColors, colors.Length);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Smoothly blends the given colors together, assuming equal weighting for each one.
+ /// </summary>
+ /// <param name="colors"></param>
+ /// <param name="colorCount"></param>
+ /// <returns></returns>
+ public unsafe static ColorBgra Blend(ColorBgra* colors, int count)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("count must be 0 or greater");
+ }
+
+ if (count == 0)
+ {
+ return ColorBgra.Transparent;
+ }
+
+ ulong aSum = 0;
+
+ for (int i = 0; i < count; ++i)
+ {
+ aSum += (ulong)colors[i].A;
+ }
+
+ byte b = 0;
+ byte g = 0;
+ byte r = 0;
+ byte a = (byte)(aSum / (ulong)count);
+
+ if (aSum != 0)
+ {
+ ulong bSum = 0;
+ ulong gSum = 0;
+ ulong rSum = 0;
+
+ for (int i = 0; i < count; ++i)
+ {
+ bSum += (ulong)(colors[i].A * colors[i].B);
+ gSum += (ulong)(colors[i].A * colors[i].G);
+ rSum += (ulong)(colors[i].A * colors[i].R);
+ }
+
+ b = (byte)(bSum / aSum);
+ g = (byte)(gSum / aSum);
+ r = (byte)(rSum / aSum);
+ }
+
+ return ColorBgra.FromBgra(b, g, r, a);
+ }
+
+ public override string ToString()
+ {
+ return "B: " + B + ", G: " + G + ", R: " + R + ", A: " + A;
+ }
+
+ /// <summary>
+ /// Casts a ColorBgra to a UInt32.
+ /// </summary>
+ public static explicit operator UInt32(ColorBgra color)
+ {
+ return color.Bgra;
+ }
+
+ /// <summary>
+ /// Casts a UInt32 to a ColorBgra.
+ /// </summary>
+ public static explicit operator ColorBgra(UInt32 uint32)
+ {
+ return ColorBgra.FromUInt32(uint32);
+ }
+
+ // Colors: copied from System.Drawing.Color's list (don't worry I didn't type it in
+ // manually, I used a code generator w/ reflection ...)
+
+ public static ColorBgra Transparent
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 255, 0);
+ }
+ }
+
+ public static ColorBgra AliceBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 248, 240, 255);
+ }
+ }
+
+ public static ColorBgra AntiqueWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(215, 235, 250, 255);
+ }
+ }
+
+ public static ColorBgra Aqua
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra Aquamarine
+ {
+ get
+ {
+ return ColorBgra.FromBgra(212, 255, 127, 255);
+ }
+ }
+
+ public static ColorBgra Azure
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 240, 255);
+ }
+ }
+
+ public static ColorBgra Beige
+ {
+ get
+ {
+ return ColorBgra.FromBgra(220, 245, 245, 255);
+ }
+ }
+
+ public static ColorBgra Bisque
+ {
+ get
+ {
+ return ColorBgra.FromBgra(196, 228, 255, 255);
+ }
+ }
+
+ public static ColorBgra Black
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra BlanchedAlmond
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 235, 255, 255);
+ }
+ }
+
+ public static ColorBgra Blue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra BlueViolet
+ {
+ get
+ {
+ return ColorBgra.FromBgra(226, 43, 138, 255);
+ }
+ }
+
+ public static ColorBgra Brown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(42, 42, 165, 255);
+ }
+ }
+
+ public static ColorBgra BurlyWood
+ {
+ get
+ {
+ return ColorBgra.FromBgra(135, 184, 222, 255);
+ }
+ }
+
+ public static ColorBgra CadetBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(160, 158, 95, 255);
+ }
+ }
+
+ public static ColorBgra Chartreuse
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 255, 127, 255);
+ }
+ }
+
+ public static ColorBgra Chocolate
+ {
+ get
+ {
+ return ColorBgra.FromBgra(30, 105, 210, 255);
+ }
+ }
+
+ public static ColorBgra Coral
+ {
+ get
+ {
+ return ColorBgra.FromBgra(80, 127, 255, 255);
+ }
+ }
+
+ public static ColorBgra CornflowerBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(237, 149, 100, 255);
+ }
+ }
+
+ public static ColorBgra Cornsilk
+ {
+ get
+ {
+ return ColorBgra.FromBgra(220, 248, 255, 255);
+ }
+ }
+
+ public static ColorBgra Crimson
+ {
+ get
+ {
+ return ColorBgra.FromBgra(60, 20, 220, 255);
+ }
+ }
+
+ public static ColorBgra Cyan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkCyan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 139, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkGoldenrod
+ {
+ get
+ {
+ return ColorBgra.FromBgra(11, 134, 184, 255);
+ }
+ }
+
+ public static ColorBgra DarkGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(169, 169, 169, 255);
+ }
+ }
+
+ public static ColorBgra DarkGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 100, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkKhaki
+ {
+ get
+ {
+ return ColorBgra.FromBgra(107, 183, 189, 255);
+ }
+ }
+
+ public static ColorBgra DarkMagenta
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 0, 139, 255);
+ }
+ }
+
+ public static ColorBgra DarkOliveGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(47, 107, 85, 255);
+ }
+ }
+
+ public static ColorBgra DarkOrange
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 140, 255, 255);
+ }
+ }
+
+ public static ColorBgra DarkOrchid
+ {
+ get
+ {
+ return ColorBgra.FromBgra(204, 50, 153, 255);
+ }
+ }
+
+ public static ColorBgra DarkRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 139, 255);
+ }
+ }
+
+ public static ColorBgra DarkSalmon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(122, 150, 233, 255);
+ }
+ }
+
+ public static ColorBgra DarkSeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 188, 143, 255);
+ }
+ }
+
+ public static ColorBgra DarkSlateBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 61, 72, 255);
+ }
+ }
+
+ public static ColorBgra DarkSlateGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(79, 79, 47, 255);
+ }
+ }
+
+ public static ColorBgra DarkTurquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(209, 206, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkViolet
+ {
+ get
+ {
+ return ColorBgra.FromBgra(211, 0, 148, 255);
+ }
+ }
+
+ public static ColorBgra DeepPink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(147, 20, 255, 255);
+ }
+ }
+
+ public static ColorBgra DeepSkyBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 191, 0, 255);
+ }
+ }
+
+ public static ColorBgra DimGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(105, 105, 105, 255);
+ }
+ }
+
+ public static ColorBgra DodgerBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 144, 30, 255);
+ }
+ }
+
+ public static ColorBgra Firebrick
+ {
+ get
+ {
+ return ColorBgra.FromBgra(34, 34, 178, 255);
+ }
+ }
+
+ public static ColorBgra FloralWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(240, 250, 255, 255);
+ }
+ }
+
+ public static ColorBgra ForestGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(34, 139, 34, 255);
+ }
+ }
+
+ public static ColorBgra Fuchsia
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 0, 255, 255);
+ }
+ }
+
+ public static ColorBgra Gainsboro
+ {
+ get
+ {
+ return ColorBgra.FromBgra(220, 220, 220, 255);
+ }
+ }
+
+ public static ColorBgra GhostWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 248, 248, 255);
+ }
+ }
+
+ public static ColorBgra Gold
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 215, 255, 255);
+ }
+ }
+
+ public static ColorBgra Goldenrod
+ {
+ get
+ {
+ return ColorBgra.FromBgra(32, 165, 218, 255);
+ }
+ }
+
+ public static ColorBgra Gray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 128, 128, 255);
+ }
+ }
+
+ public static ColorBgra Green
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 128, 0, 255);
+ }
+ }
+
+ public static ColorBgra GreenYellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(47, 255, 173, 255);
+ }
+ }
+
+ public static ColorBgra Honeydew
+ {
+ get
+ {
+ return ColorBgra.FromBgra(240, 255, 240, 255);
+ }
+ }
+
+ public static ColorBgra HotPink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(180, 105, 255, 255);
+ }
+ }
+
+ public static ColorBgra IndianRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(92, 92, 205, 255);
+ }
+ }
+
+ public static ColorBgra Indigo
+ {
+ get
+ {
+ return ColorBgra.FromBgra(130, 0, 75, 255);
+ }
+ }
+
+ public static ColorBgra Ivory
+ {
+ get
+ {
+ return ColorBgra.FromBgra(240, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra Khaki
+ {
+ get
+ {
+ return ColorBgra.FromBgra(140, 230, 240, 255);
+ }
+ }
+
+ public static ColorBgra Lavender
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 230, 230, 255);
+ }
+ }
+
+ public static ColorBgra LavenderBlush
+ {
+ get
+ {
+ return ColorBgra.FromBgra(245, 240, 255, 255);
+ }
+ }
+
+ public static ColorBgra LawnGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 252, 124, 255);
+ }
+ }
+
+ public static ColorBgra LemonChiffon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 250, 255, 255);
+ }
+ }
+
+ public static ColorBgra LightBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 216, 173, 255);
+ }
+ }
+
+ public static ColorBgra LightCoral
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 128, 240, 255);
+ }
+ }
+
+ public static ColorBgra LightCyan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 224, 255);
+ }
+ }
+
+ public static ColorBgra LightGoldenrodYellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(210, 250, 250, 255);
+ }
+ }
+
+ public static ColorBgra LightGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(144, 238, 144, 255);
+ }
+ }
+
+ public static ColorBgra LightGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(211, 211, 211, 255);
+ }
+ }
+
+ public static ColorBgra LightPink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(193, 182, 255, 255);
+ }
+ }
+
+ public static ColorBgra LightSalmon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(122, 160, 255, 255);
+ }
+ }
+
+ public static ColorBgra LightSeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(170, 178, 32, 255);
+ }
+ }
+
+ public static ColorBgra LightSkyBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 206, 135, 255);
+ }
+ }
+
+ public static ColorBgra LightSlateGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(153, 136, 119, 255);
+ }
+ }
+
+ public static ColorBgra LightSteelBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(222, 196, 176, 255);
+ }
+ }
+
+ public static ColorBgra LightYellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(224, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra Lime
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra LimeGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(50, 205, 50, 255);
+ }
+ }
+
+ public static ColorBgra Linen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 240, 250, 255);
+ }
+ }
+
+ public static ColorBgra Magenta
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 0, 255, 255);
+ }
+ }
+
+ public static ColorBgra Maroon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 128, 255);
+ }
+ }
+
+ public static ColorBgra MediumAquamarine
+ {
+ get
+ {
+ return ColorBgra.FromBgra(170, 205, 102, 255);
+ }
+ }
+
+ public static ColorBgra MediumBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra MediumOrchid
+ {
+ get
+ {
+ return ColorBgra.FromBgra(211, 85, 186, 255);
+ }
+ }
+
+ public static ColorBgra MediumPurple
+ {
+ get
+ {
+ return ColorBgra.FromBgra(219, 112, 147, 255);
+ }
+ }
+
+ public static ColorBgra MediumSeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(113, 179, 60, 255);
+ }
+ }
+
+ public static ColorBgra MediumSlateBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 104, 123, 255);
+ }
+ }
+
+ public static ColorBgra MediumSpringGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(154, 250, 0, 255);
+ }
+ }
+
+ public static ColorBgra MediumTurquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(204, 209, 72, 255);
+ }
+ }
+
+ public static ColorBgra MediumVioletRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(133, 21, 199, 255);
+ }
+ }
+
+ public static ColorBgra MidnightBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(112, 25, 25, 255);
+ }
+ }
+
+ public static ColorBgra MintCream
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 255, 245, 255);
+ }
+ }
+
+ public static ColorBgra MistyRose
+ {
+ get
+ {
+ return ColorBgra.FromBgra(225, 228, 255, 255);
+ }
+ }
+
+ public static ColorBgra Moccasin
+ {
+ get
+ {
+ return ColorBgra.FromBgra(181, 228, 255, 255);
+ }
+ }
+
+ public static ColorBgra NavajoWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(173, 222, 255, 255);
+ }
+ }
+
+ public static ColorBgra Navy
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra OldLace
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 245, 253, 255);
+ }
+ }
+
+ public static ColorBgra Olive
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 128, 128, 255);
+ }
+ }
+
+ public static ColorBgra OliveDrab
+ {
+ get
+ {
+ return ColorBgra.FromBgra(35, 142, 107, 255);
+ }
+ }
+
+ public static ColorBgra Orange
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 165, 255, 255);
+ }
+ }
+
+ public static ColorBgra OrangeRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 69, 255, 255);
+ }
+ }
+
+ public static ColorBgra Orchid
+ {
+ get
+ {
+ return ColorBgra.FromBgra(214, 112, 218, 255);
+ }
+ }
+
+ public static ColorBgra PaleGoldenrod
+ {
+ get
+ {
+ return ColorBgra.FromBgra(170, 232, 238, 255);
+ }
+ }
+
+ public static ColorBgra PaleGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(152, 251, 152, 255);
+ }
+ }
+
+ public static ColorBgra PaleTurquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 238, 175, 255);
+ }
+ }
+
+ public static ColorBgra PaleVioletRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(147, 112, 219, 255);
+ }
+ }
+
+ public static ColorBgra PapayaWhip
+ {
+ get
+ {
+ return ColorBgra.FromBgra(213, 239, 255, 255);
+ }
+ }
+
+ public static ColorBgra PeachPuff
+ {
+ get
+ {
+ return ColorBgra.FromBgra(185, 218, 255, 255);
+ }
+ }
+
+ public static ColorBgra Peru
+ {
+ get
+ {
+ return ColorBgra.FromBgra(63, 133, 205, 255);
+ }
+ }
+
+ public static ColorBgra Pink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(203, 192, 255, 255);
+ }
+ }
+
+ public static ColorBgra Plum
+ {
+ get
+ {
+ return ColorBgra.FromBgra(221, 160, 221, 255);
+ }
+ }
+
+ public static ColorBgra PowderBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 224, 176, 255);
+ }
+ }
+
+ public static ColorBgra Purple
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 0, 128, 255);
+ }
+ }
+
+ public static ColorBgra Red
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 255, 255);
+ }
+ }
+
+ public static ColorBgra RosyBrown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(143, 143, 188, 255);
+ }
+ }
+
+ public static ColorBgra RoyalBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(225, 105, 65, 255);
+ }
+ }
+
+ public static ColorBgra SaddleBrown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(19, 69, 139, 255);
+ }
+ }
+
+ public static ColorBgra Salmon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(114, 128, 250, 255);
+ }
+ }
+
+ public static ColorBgra SandyBrown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(96, 164, 244, 255);
+ }
+ }
+
+ public static ColorBgra SeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(87, 139, 46, 255);
+ }
+ }
+
+ public static ColorBgra SeaShell
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 245, 255, 255);
+ }
+ }
+
+ public static ColorBgra Sienna
+ {
+ get
+ {
+ return ColorBgra.FromBgra(45, 82, 160, 255);
+ }
+ }
+
+ public static ColorBgra Silver
+ {
+ get
+ {
+ return ColorBgra.FromBgra(192, 192, 192, 255);
+ }
+ }
+
+ public static ColorBgra SkyBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(235, 206, 135, 255);
+ }
+ }
+
+ public static ColorBgra SlateBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 90, 106, 255);
+ }
+ }
+
+ public static ColorBgra SlateGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(144, 128, 112, 255);
+ }
+ }
+
+ public static ColorBgra Snow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 250, 255, 255);
+ }
+ }
+
+ public static ColorBgra SpringGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(127, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra SteelBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(180, 130, 70, 255);
+ }
+ }
+
+ public static ColorBgra Tan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(140, 180, 210, 255);
+ }
+ }
+
+ public static ColorBgra Teal
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 128, 0, 255);
+ }
+ }
+
+ public static ColorBgra Thistle
+ {
+ get
+ {
+ return ColorBgra.FromBgra(216, 191, 216, 255);
+ }
+ }
+
+ public static ColorBgra Tomato
+ {
+ get
+ {
+ return ColorBgra.FromBgra(71, 99, 255, 255);
+ }
+ }
+
+ public static ColorBgra Turquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(208, 224, 64, 255);
+ }
+ }
+
+ public static ColorBgra Violet
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 130, 238, 255);
+ }
+ }
+
+ public static ColorBgra Wheat
+ {
+ get
+ {
+ return ColorBgra.FromBgra(179, 222, 245, 255);
+ }
+ }
+
+ public static ColorBgra White
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra WhiteSmoke
+ {
+ get
+ {
+ return ColorBgra.FromBgra(245, 245, 245, 255);
+ }
+ }
+
+ public static ColorBgra Yellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra YellowGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(50, 205, 154, 255);
+ }
+ }
+
+ public static ColorBgra Zero
+ {
+ get
+ {
+ return (ColorBgra)0;
+ }
+ }
+
+ private static Dictionary<string, ColorBgra> predefinedColors;
+
+ /// <summary>
+ /// Gets a hashtable that contains a list of all the predefined colors.
+ /// These are the same color values that are defined as public static properties
+ /// in System.Drawing.Color. The hashtable uses strings for the keys, and
+ /// ColorBgras for the values.
+ /// </summary>
+ public static Dictionary<string, ColorBgra> PredefinedColors
+ {
+ get
+ {
+ if (predefinedColors != null)
+ {
+ Type colorBgraType = typeof(ColorBgra);
+ PropertyInfo[] propInfos = colorBgraType.GetProperties(BindingFlags.Static | BindingFlags.Public);
+ Hashtable colors = new Hashtable();
+
+ foreach (PropertyInfo pi in propInfos)
+ {
+ if (pi.PropertyType == colorBgraType)
+ {
+ colors.Add(pi.Name, (ColorBgra)pi.GetValue(null, null));
+ }
+ }
+ }
+
+ return new Dictionary<string, ColorBgra>(predefinedColors);
+ }
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs
new file mode 100644
index 000000000..a9629f2d8
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs
@@ -0,0 +1,569 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp
+
+using Tango.RemoteDesktop.Quantization;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal unsafe class OctreeQuantizer
+ : Quantizer
+ {
+ private bool enableTransparency;
+ private Octree octree;
+ private int maxColors;
+
+ /// <summary>
+ /// Construct the octree quantizer
+ /// </summary>
+ /// <remarks>
+ /// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree,
+ /// the second pass quantizes a color based on the nodes in the tree
+ /// </remarks>
+ /// <param name="maxColors">The maximum number of colors to return</param>
+ /// <param name="maxColorBits">The number of significant bits</param>
+ /// <param name="enableTransparency">If true, then one color slot in the palette will be reserved for transparency.
+ /// Any color passed through QuantizePixel which does not have an alpha of 255 will use this color slot.
+ /// If false, then all colors should have an alpha of 255. Otherwise the results may be unpredictable.</param>
+ public OctreeQuantizer(int maxColors, bool enableTransparency)
+ : base(false)
+ {
+ if (maxColors > 256)
+ {
+ throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be 256 or less");
+ }
+
+ if (maxColors < 2)
+ {
+ throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors must be 2 or more");
+ }
+
+ this.octree = new Octree(8); // 8-bits per color
+ this.enableTransparency = enableTransparency;
+ this.maxColors = maxColors - (this.enableTransparency ? 1 : 0); // subtract 1 if enableTransparency is true
+ }
+
+ /// <summary>
+ /// Process the pixel in the first pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <remarks>
+ /// This function need only be overridden if your quantize algorithm needs two passes,
+ /// such as an Octree quantizer.
+ /// </remarks>
+ protected override void InitialQuantizePixel(ColorBgra *pixel)
+ {
+ this.octree.AddColor(pixel);
+ }
+
+ /// <summary>
+ /// Override this to process the pixel in the second pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <returns>The quantized value</returns>
+ protected override byte QuantizePixel(ColorBgra *pixel)
+ {
+ byte paletteIndex = 0;
+
+ if (!this.enableTransparency || pixel->A == 255)
+ {
+ paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
+ }
+ else
+ {
+ paletteIndex = (byte)this.maxColors; // maxColors will have a maximum value of 255 is enableTransparency is true
+ }
+
+ return paletteIndex;
+ }
+
+ /// <summary>
+ /// Retrieve the palette for the quantized image
+ /// </summary>
+ /// <param name="original">Any old palette, this is overwritten</param>
+ /// <returns>The new color palette</returns>
+ protected override ColorPalette GetPalette(ColorPalette original)
+ {
+ // First off convert the octree to _maxColors colors
+ List<Color> palette = this.octree.Palletize(maxColors);
+
+ // Then convert the palette based on those colors
+ for (int index = 0; index < palette.Count; index++)
+ {
+ original.Entries[index] = palette[index];
+ }
+
+ // Fill the rest of the palette with transparent
+ for (int i = palette.Count; i < original.Entries.Length; ++i)
+ {
+ original.Entries[i] = Color.FromArgb(255, 0, 0, 0);
+ }
+
+ // Add the transparent color
+ if (this.enableTransparency)
+ {
+ original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0);
+ }
+
+ return original;
+ }
+
+ /// <summary>
+ /// Class which does the actual quantization
+ /// </summary>
+ private class Octree
+ {
+ /// <summary>
+ /// Construct the octree
+ /// </summary>
+ /// <param name="maxColorBits">The maximum number of significant bits in the image</param>
+ public Octree(int maxColorBits)
+ {
+ _maxColorBits = maxColorBits;
+ _leafCount = 0;
+ _reducibleNodes = new OctreeNode[9];
+ _root = new OctreeNode(0, _maxColorBits, this);
+ _previousColor = 0;
+ _previousNode = null;
+ }
+
+ /// <summary>
+ /// Add a given color value to the octree
+ /// </summary>
+ /// <param name="pixel"></param>
+ public void AddColor(ColorBgra *pixel)
+ {
+ // Check if this request is for the same color as the last
+ if (_previousColor == pixel->Bgra)
+ {
+ // If so, check if I have a previous node setup. This will only ocurr if the first color in the image
+ // happens to be black, with an alpha component of zero.
+ if (null == _previousNode)
+ {
+ _previousColor = pixel->Bgra;
+ _root.AddColor(pixel, _maxColorBits, 0, this);
+ }
+ else
+ {
+ // Just update the previous node
+ _previousNode.Increment(pixel);
+ }
+ }
+ else
+ {
+ _previousColor = pixel->Bgra;
+ _root.AddColor(pixel, _maxColorBits, 0, this);
+ }
+ }
+
+ /// <summary>
+ /// Reduce the depth of the tree
+ /// </summary>
+ public void Reduce()
+ {
+ int index;
+
+ // Find the deepest level containing at least one reducible node
+ for (index = _maxColorBits - 1;
+ (index > 0) && (null == _reducibleNodes[index]);
+ index--)
+ {
+ // intentionally blank
+ }
+
+ // Reduce the node most recently added to the list at level 'index'
+ OctreeNode node = _reducibleNodes[index];
+ _reducibleNodes[index] = node.NextReducible;
+
+ // Decrement the leaf count after reducing the node
+ _leafCount -= node.Reduce();
+
+ // And just in case I've reduced the last color to be added, and the next color to
+ // be added is the same, invalidate the previousNode...
+ _previousNode = null;
+ }
+
+ /// <summary>
+ /// Get/Set the number of leaves in the tree
+ /// </summary>
+ public int Leaves
+ {
+ get
+ {
+ return _leafCount;
+ }
+
+ set
+ {
+ _leafCount = value;
+ }
+ }
+
+ /// <summary>
+ /// Return the array of reducible nodes
+ /// </summary>
+ protected OctreeNode[] ReducibleNodes
+ {
+ get
+ {
+ return _reducibleNodes;
+ }
+ }
+
+ /// <summary>
+ /// Keep track of the previous node that was quantized
+ /// </summary>
+ /// <param name="node">The node last quantized</param>
+ protected void TrackPrevious(OctreeNode node)
+ {
+ _previousNode = node;
+ }
+
+ private Color[] _palette;
+ private PaletteTable paletteTable;
+
+ /// <summary>
+ /// Convert the nodes in the octree to a palette with a maximum of colorCount colors
+ /// </summary>
+ /// <param name="colorCount">The maximum number of colors</param>
+ /// <returns>A list with the palettized colors</returns>
+ public List<Color> Palletize(int colorCount)
+ {
+ while (Leaves > colorCount)
+ {
+ Reduce();
+ }
+
+ // Now palettize the nodes
+ List<Color> palette = new List<Color>(Leaves);
+ int paletteIndex = 0;
+
+ _root.ConstructPalette(palette, ref paletteIndex);
+
+ // And return the palette
+ this._palette = palette.ToArray();
+ this.paletteTable = null;
+
+ return palette;
+ }
+
+ /// <summary>
+ /// Get the palette index for the passed color
+ /// </summary>
+ /// <param name="pixel"></param>
+ /// <returns></returns>
+ public int GetPaletteIndex(ColorBgra *pixel)
+ {
+ int ret = -1;
+
+ ret = _root.GetPaletteIndex(pixel, 0);
+
+ if (ret < 0)
+ {
+ if (this.paletteTable == null)
+ {
+ this.paletteTable = new PaletteTable(this._palette);
+ }
+
+ ret = this.paletteTable.FindClosestPaletteIndex(pixel->ToColor());
+ }
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Mask used when getting the appropriate pixels for a given node
+ /// </summary>
+ private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
+
+ /// <summary>
+ /// The root of the octree
+ /// </summary>
+ private OctreeNode _root;
+
+ /// <summary>
+ /// Number of leaves in the tree
+ /// </summary>
+ private int _leafCount;
+
+ /// <summary>
+ /// Array of reducible nodes
+ /// </summary>
+ private OctreeNode[] _reducibleNodes;
+
+ /// <summary>
+ /// Maximum number of significant bits in the image
+ /// </summary>
+ private int _maxColorBits;
+
+ /// <summary>
+ /// Store the last node quantized
+ /// </summary>
+ private OctreeNode _previousNode;
+
+ /// <summary>
+ /// Cache the previous color quantized
+ /// </summary>
+ private uint _previousColor;
+
+ /// <summary>
+ /// Class which encapsulates each node in the tree
+ /// </summary>
+ protected class OctreeNode
+ {
+ /// <summary>
+ /// Construct the node
+ /// </summary>
+ /// <param name="level">The level in the tree = 0 - 7</param>
+ /// <param name="colorBits">The number of significant color bits in the image</param>
+ /// <param name="octree">The tree to which this node belongs</param>
+ public OctreeNode(int level, int colorBits, Octree octree)
+ {
+ // Construct the new node
+ _leaf = (level == colorBits);
+
+ _red = 0;
+ _green = 0;
+ _blue = 0;
+ _pixelCount = 0;
+
+ // If a leaf, increment the leaf count
+ if (_leaf)
+ {
+ octree.Leaves++;
+ _nextReducible = null;
+ _children = null;
+ }
+ else
+ {
+ // Otherwise add this to the reducible nodes
+ _nextReducible = octree.ReducibleNodes[level];
+ octree.ReducibleNodes[level] = this;
+ _children = new OctreeNode[8];
+ }
+ }
+
+ /// <summary>
+ /// Add a color into the tree
+ /// </summary>
+ /// <param name="pixel">The color</param>
+ /// <param name="colorBits">The number of significant color bits</param>
+ /// <param name="level">The level in the tree</param>
+ /// <param name="octree">The tree to which this node belongs</param>
+ public void AddColor(ColorBgra *pixel, int colorBits, int level, Octree octree)
+ {
+ // Update the color information if this is a leaf
+ if (_leaf)
+ {
+ Increment(pixel);
+
+ // Setup the previous node
+ octree.TrackPrevious(this);
+ }
+ else
+ {
+ // Go to the next level down in the tree
+ int shift = 7 - level;
+ int index = ((pixel->R & mask[level]) >> (shift - 2)) |
+ ((pixel->G & mask[level]) >> (shift - 1)) |
+ ((pixel->B & mask[level]) >> (shift));
+
+ OctreeNode child = _children[index];
+
+ if (null == child)
+ {
+ // Create a new child node & store in the array
+ child = new OctreeNode(level + 1, colorBits, octree);
+ _children[index] = child;
+ }
+
+ // Add the color to the child node
+ child.AddColor(pixel, colorBits, level + 1, octree);
+ }
+
+ }
+
+ /// <summary>
+ /// Get/Set the next reducible node
+ /// </summary>
+ public OctreeNode NextReducible
+ {
+ get
+ {
+ return _nextReducible;
+ }
+
+ set
+ {
+ _nextReducible = value;
+ }
+ }
+
+ /// <summary>
+ /// Return the child nodes
+ /// </summary>
+ public OctreeNode[] Children
+ {
+ get
+ {
+ return _children;
+ }
+ }
+
+ /// <summary>
+ /// Reduce this node by removing all of its children
+ /// </summary>
+ /// <returns>The number of leaves removed</returns>
+ public int Reduce()
+ {
+ int children = 0;
+ _red = 0;
+ _green = 0;
+ _blue = 0;
+
+ // Loop through all children and add their information to this node
+ for (int index = 0; index < 8; index++)
+ {
+ if (null != _children[index])
+ {
+ _red += _children[index]._red;
+ _green += _children[index]._green;
+ _blue += _children[index]._blue;
+ _pixelCount += _children[index]._pixelCount;
+ ++children;
+ _children[index] = null;
+ }
+ }
+
+ // Now change this to a leaf node
+ _leaf = true;
+
+ // Return the number of nodes to decrement the leaf count by
+ return(children - 1);
+ }
+
+ /// <summary>
+ /// Traverse the tree, building up the color palette
+ /// </summary>
+ /// <param name="palette">The palette</param>
+ /// <param name="paletteIndex">The current palette index</param>
+ public void ConstructPalette(List<Color> palette, ref int paletteIndex)
+ {
+ if (_leaf)
+ {
+ // Consume the next palette index
+ _paletteIndex = paletteIndex++;
+
+ // And set the color of the palette entry
+ int r = _red / _pixelCount;
+ int g = _green / _pixelCount;
+ int b = _blue / _pixelCount;
+
+ palette.Add(Color.FromArgb(r, g, b));
+ }
+ else
+ {
+ // Loop through children looking for leaves
+ for (int index = 0; index < 8; index++)
+ {
+ if (null != _children[index])
+ {
+ _children[index].ConstructPalette(palette, ref paletteIndex);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Return the palette index for the passed color
+ /// </summary>
+ public int GetPaletteIndex(ColorBgra *pixel, int level)
+ {
+ int paletteIndex = _paletteIndex;
+
+ if (!_leaf)
+ {
+ int shift = 7 - level;
+ int index = ((pixel->R & mask[level]) >> (shift - 2)) |
+ ((pixel->G & mask[level]) >> (shift - 1)) |
+ ((pixel->B & mask[level]) >> (shift));
+
+ if (null != _children[index])
+ {
+ paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1);
+ }
+ else
+ {
+ paletteIndex = -1;
+ }
+ }
+
+ return paletteIndex;
+ }
+
+ /// <summary>
+ /// Increment the pixel count and add to the color information
+ /// </summary>
+ public void Increment(ColorBgra *pixel)
+ {
+ ++_pixelCount;
+ _red += pixel->R;
+ _green += pixel->G;
+ _blue += pixel->B;
+ }
+
+ /// <summary>
+ /// Flag indicating that this is a leaf node
+ /// </summary>
+ private bool _leaf;
+
+ /// <summary>
+ /// Number of pixels in this node
+ /// </summary>
+ private int _pixelCount;
+
+ /// <summary>
+ /// Red component
+ /// </summary>
+ private int _red;
+
+ /// <summary>
+ /// Green Component
+ /// </summary>
+ private int _green;
+
+ /// <summary>
+ /// Blue component
+ /// </summary>
+ private int _blue;
+
+ /// <summary>
+ /// Pointers to any child nodes
+ /// </summary>
+ private OctreeNode[] _children;
+
+ /// <summary>
+ /// Pointer to next reducible node
+ /// </summary>
+ private OctreeNode _nextReducible;
+
+ /// <summary>
+ /// The index of this node in the palette
+ /// </summary>
+ private int _paletteIndex;
+ }
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs
new file mode 100644
index 000000000..5c24fe80f
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs
@@ -0,0 +1,72 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections;
+using System.Drawing;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal sealed class PaletteTable
+ {
+ private Color[] palette;
+
+ public Color this[int index]
+ {
+ get
+ {
+ return this.palette[index];
+ }
+
+ set
+ {
+ this.palette[index] = value;
+ }
+ }
+
+ private int GetDistanceSquared(Color a, Color b)
+ {
+ int dsq = 0; // delta squared
+ int v;
+
+ v = a.B - b.B;
+ dsq += v * v;
+ v = a.G - b.G;
+ dsq += v * v;
+ v = a.R - b.R;
+ dsq += v * v;
+
+ return dsq;
+ }
+
+ public int FindClosestPaletteIndex(Color pixel)
+ {
+ int dsqBest = int.MaxValue;
+ int ret = 0;
+
+ for (int i = 0; i < this.palette.Length; ++i)
+ {
+ int dsq = GetDistanceSquared(this.palette[i], pixel);
+
+ if (dsq < dsqBest)
+ {
+ dsqBest = dsq;
+ ret = i;
+ }
+ }
+
+ return ret;
+ }
+
+ public PaletteTable(Color[] palette)
+ {
+ this.palette = (Color[])palette.Clone();
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs
new file mode 100644
index 000000000..e63dc1ad8
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs
@@ -0,0 +1,335 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp
+
+using Tango.RemoteDesktop.Quantization;
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal unsafe abstract class Quantizer
+ {
+ /// <summary>
+ /// Flag used to indicate whether a single pass or two passes are needed for quantization.
+ /// </summary>
+ private bool singlePass;
+
+ protected int ditherLevel;
+ public int DitherLevel
+ {
+ get
+ {
+ return this.ditherLevel;
+ }
+
+ set
+ {
+ this.ditherLevel = value;
+ }
+ }
+
+ /// <summary>
+ /// Construct the quantizer
+ /// </summary>
+ /// <param name="singlePass">If true, the quantization only needs to loop through the source pixels once</param>
+ /// <remarks>
+ /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image,
+ /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage'
+ /// and then 'QuantizeImage'.
+ /// </remarks>
+ public Quantizer(bool singlePass)
+ {
+ this.singlePass = singlePass;
+ }
+
+ /// <summary>
+ /// Quantize an image and return the resulting output bitmap
+ /// </summary>
+ /// <param name="source">The image to quantize</param>
+ /// <returns>A quantized version of the image</returns>
+ public Bitmap Quantize(Image source)
+ {
+ // Get the size of the source image
+ int height = source.Height;
+ int width = source.Width;
+
+ // And construct a rectangle from these dimensions
+ Rectangle bounds = new Rectangle(0, 0, width, height);
+
+ // First off take a 32bpp version of the image
+ Bitmap img32bpp;
+
+ if (source is Bitmap && source.PixelFormat == PixelFormat.Format32bppArgb)
+ {
+ img32bpp = (Bitmap)source;
+ }
+ else
+ {
+ img32bpp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+
+ // Now lock the bitmap into memory
+ using (Graphics g = Graphics.FromImage(img32bpp))
+ {
+ g.PageUnit = GraphicsUnit.Pixel;
+
+ // Draw the source image onto the copy bitmap,
+ // which will effect a widening as appropriate.
+ g.DrawImage(source, 0, 0, bounds.Width, bounds.Height);
+ }
+ }
+
+ // And construct an 8bpp version
+ Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
+
+ // Define a pointer to the bitmap data
+ BitmapData sourceData = null;
+
+ try
+ {
+ // Get the source image bits and lock into memory
+ sourceData = img32bpp.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+
+ // Call the FirstPass function if not a single pass algorithm.
+ // For something like an octree quantizer, this will run through
+ // all image pixels, build a data structure, and create a palette.
+ if (!singlePass)
+ {
+ FirstPass(sourceData, width, height);
+ }
+
+ // Then set the color palette on the output bitmap. I'm passing in the current palette
+ // as there's no way to construct a new, empty palette.
+ output.Palette = this.GetPalette(output.Palette);
+
+ // Then call the second pass which actually does the conversion
+ SecondPass(sourceData, output, width, height, bounds);
+ }
+
+ finally
+ {
+ // Ensure that the bits are unlocked
+ img32bpp.UnlockBits(sourceData);
+ }
+
+ if (img32bpp != source)
+ {
+ img32bpp.Dispose();
+ img32bpp = null;
+ }
+
+ // Last but not least, return the output bitmap
+ return output;
+ }
+
+ /// <summary>
+ /// Execute the first pass through the pixels in the image
+ /// </summary>
+ /// <param name="sourceData">The source data</param>
+ /// <param name="width">The width in pixels of the image</param>
+ /// <param name="height">The height in pixels of the image</param>
+ protected virtual void FirstPass(BitmapData sourceData, int width, int height)
+ {
+ // Define the source data pointers. The source row is a byte to
+ // keep addition of the stride value easier (as this is in bytes)
+ byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();
+ Int32* pSourcePixel;
+
+ // Loop through each row
+ for (int row = 0; row < height; row++)
+ {
+ // Set the source pixel to the first pixel in this row
+ pSourcePixel = (Int32*)pSourceRow;
+
+ // And loop through each column
+ for (int col = 0; col < width; col++, pSourcePixel++)
+ {
+ InitialQuantizePixel((ColorBgra *)pSourcePixel);
+ }
+
+ // Add the stride to the source row
+ pSourceRow += sourceData.Stride;
+ }
+ }
+
+ /// <summary>
+ /// Execute a second pass through the bitmap
+ /// </summary>
+ /// <param name="sourceData">The source bitmap, locked into memory</param>
+ /// <param name="output">The output bitmap</param>
+ /// <param name="width">The width in pixels of the image</param>
+ /// <param name="height">The height in pixels of the image</param>
+ /// <param name="bounds">The bounding rectangle</param>
+ protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
+ {
+ BitmapData outputData = null;
+ Color[] pallete = output.Palette.Entries;
+ int weight = ditherLevel;
+
+ try
+ {
+ // Lock the output bitmap into memory
+ outputData = output.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
+
+ // Define the source data pointers. The source row is a byte to
+ // keep addition of the stride value easier (as this is in bytes)
+ byte* pSourceRow = (byte *)sourceData.Scan0.ToPointer();
+ Int32* pSourcePixel = (Int32 *)pSourceRow;
+
+ // Now define the destination data pointers
+ byte* pDestinationRow = (byte *)outputData.Scan0.ToPointer();
+ byte* pDestinationPixel = pDestinationRow;
+
+ int[] errorThisRowR = new int[width + 1];
+ int[] errorThisRowG = new int[width + 1];
+ int[] errorThisRowB = new int[width + 1];
+
+ for (int row = 0; row < height; row++)
+ {
+ int[] errorNextRowR = new int[width + 1];
+ int[] errorNextRowG = new int[width + 1];
+ int[] errorNextRowB = new int[width + 1];
+
+ int ptrInc;
+
+ if ((row & 1) == 0)
+ {
+ pSourcePixel = (Int32*)pSourceRow;
+ pDestinationPixel = pDestinationRow;
+ ptrInc = +1;
+ }
+ else
+ {
+ pSourcePixel = (Int32*)pSourceRow + width - 1;
+ pDestinationPixel = pDestinationRow + width - 1;
+ ptrInc = -1;
+ }
+
+ // Loop through each pixel on this scan line
+ for (int col = 0; col < width; ++col)
+ {
+ // Quantize the pixel
+ ColorBgra srcPixel = *(ColorBgra *)pSourcePixel;
+ ColorBgra target = new ColorBgra();
+
+ target.B = Utility.ClampToByte(srcPixel.B - ((errorThisRowB[col] * weight) / 8));
+ target.G = Utility.ClampToByte(srcPixel.G - ((errorThisRowG[col] * weight) / 8));
+ target.R = Utility.ClampToByte(srcPixel.R - ((errorThisRowR[col] * weight) / 8));
+ target.A = srcPixel.A;
+
+ byte pixelValue = QuantizePixel(&target);
+ *pDestinationPixel = pixelValue;
+
+ ColorBgra actual = ColorBgra.FromColor(pallete[pixelValue]);
+
+ int errorR = actual.R - target.R;
+ int errorG = actual.G - target.G;
+ int errorB = actual.B - target.B;
+
+ // Floyd-Steinberg Error Diffusion:
+ // a) 7/16 error goes to x+1
+ // b) 5/16 error goes to y+1
+ // c) 3/16 error goes to x-1,y+1
+ // d) 1/16 error goes to x+1,y+1
+
+ const int a = 7;
+ const int b = 5;
+ const int c = 3;
+
+ int errorRa = (errorR * a) / 16;
+ int errorRb = (errorR * b) / 16;
+ int errorRc = (errorR * c) / 16;
+ int errorRd = errorR - errorRa - errorRb - errorRc;
+
+ int errorGa = (errorG * a) / 16;
+ int errorGb = (errorG * b) / 16;
+ int errorGc = (errorG * c) / 16;
+ int errorGd = errorG - errorGa - errorGb - errorGc;
+
+ int errorBa = (errorB * a) / 16;
+ int errorBb = (errorB * b) / 16;
+ int errorBc = (errorB * c) / 16;
+ int errorBd = errorB - errorBa - errorBb - errorBc;
+
+ errorThisRowR[col + 1] += errorRa;
+ errorThisRowG[col + 1] += errorGa;
+ errorThisRowB[col + 1] += errorBa;
+
+ errorNextRowR[width - col] += errorRb;
+ errorNextRowG[width - col] += errorGb;
+ errorNextRowB[width - col] += errorBb;
+
+ if (col != 0)
+ {
+ errorNextRowR[width - (col - 1)] += errorRc;
+ errorNextRowG[width - (col - 1)] += errorGc;
+ errorNextRowB[width - (col - 1)] += errorBc;
+ }
+
+ errorNextRowR[width - (col + 1)] += errorRd;
+ errorNextRowG[width - (col + 1)] += errorGd;
+ errorNextRowB[width - (col + 1)] += errorBd;
+
+ // unchecked is necessary because otherwise it throws a fit if ptrInc is negative.
+ unchecked
+ {
+ pSourcePixel += ptrInc;
+ pDestinationPixel += ptrInc;
+ }
+ }
+
+ // Add the stride to the source row
+ pSourceRow += sourceData.Stride;
+
+ // And to the destination row
+ pDestinationRow += outputData.Stride;
+
+ errorThisRowB = errorNextRowB;
+ errorThisRowG = errorNextRowG;
+ errorThisRowR = errorNextRowR;
+ }
+ }
+
+ finally
+ {
+ // Ensure that I unlock the output bits
+ output.UnlockBits(outputData);
+ }
+ }
+
+ /// <summary>
+ /// Override this to process the pixel in the first pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <remarks>
+ /// This function need only be overridden if your quantize algorithm needs two passes,
+ /// such as an Octree quantizer.
+ /// </remarks>
+ protected virtual void InitialQuantizePixel(ColorBgra *pixel)
+ {
+ }
+
+ /// <summary>
+ /// Override this to process the pixel in the second pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <returns>The quantized value</returns>
+ protected abstract byte QuantizePixel(ColorBgra *pixel);
+
+ /// <summary>
+ /// Retrieve the palette for the quantized image
+ /// </summary>
+ /// <param name="original">Any old palette, this is overrwritten</param>
+ /// <returns>The new color palette</returns>
+ protected abstract ColorPalette GetPalette(ColorPalette original);
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs
new file mode 100644
index 000000000..4ce8ac958
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs
@@ -0,0 +1,88 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Drawing;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal struct Scanline
+ {
+ private int x;
+ private int y;
+ private int length;
+
+ public int X
+ {
+ get
+ {
+ return x;
+ }
+ }
+
+ public int Y
+ {
+ get
+ {
+ return y;
+ }
+ }
+
+ public int Length
+ {
+ get
+ {
+ return length;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return length.GetHashCode() + x.GetHashCode() + y.GetHashCode();
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Scanline)
+ {
+ Scanline rhs = (Scanline)obj;
+ return x == rhs.x && y == rhs.y && length == rhs.length;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public static bool operator== (Scanline lhs, Scanline rhs)
+ {
+ return lhs.x == rhs.x && lhs.y == rhs.y && lhs.length == rhs.length;
+ }
+
+ public static bool operator!= (Scanline lhs, Scanline rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+ public override string ToString()
+ {
+ return "(" + x + "," + y + "):[" + length.ToString() + "]";
+ }
+
+ public Scanline(int x, int y, int length)
+ {
+ this.x = x;
+ this.y = y;
+ this.length = length;
+ }
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs
new file mode 100644
index 000000000..74119e9c5
--- /dev/null
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs
@@ -0,0 +1,2531 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using Microsoft.Win32;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ /// <summary>
+ /// Defines miscellaneous constants and static functions.
+ /// </summary>
+ /// // TODO: refactor into mini static classes
+ internal sealed class Utility
+ {
+ private Utility()
+ {
+ }
+
+ internal static bool IsNumber(float x)
+ {
+ return x >= float.MinValue && x <= float.MaxValue;
+ }
+
+ internal static bool IsNumber(double x)
+ {
+ return x >= double.MinValue && x <= double.MaxValue;
+ }
+
+ internal static int Min(int val0, params int[] vals)
+ {
+ int min = val0;
+
+ for (int i = 0; i < vals.Length; ++i)
+ {
+ if (vals[i] < min)
+ {
+ min = vals[i];
+ }
+ }
+
+ return min;
+ }
+
+ internal static int Max(int val0, params int[] vals)
+ {
+ int max = val0;
+
+ for (int i = 0; i < vals.Length; ++i)
+ {
+ if (vals[i] > max)
+ {
+ max = vals[i];
+ }
+ }
+
+ return max;
+ }
+
+ public static PointF[] GetRgssOffsets(int quality)
+ {
+ unsafe
+ {
+ int sampleCount = quality * quality;
+ PointF[] samplesArray = new PointF[sampleCount];
+
+ fixed (PointF* pSamplesArray = samplesArray)
+ {
+ GetRgssOffsets(pSamplesArray, sampleCount, quality);
+ }
+
+ return samplesArray;
+ }
+ }
+
+ public static unsafe void GetRgssOffsets(PointF* samplesArray, int sampleCount, int quality)
+ {
+ if (sampleCount < 1)
+ {
+ throw new ArgumentOutOfRangeException("sampleCount", "sampleCount must be [0, int.MaxValue]");
+ }
+
+ if (sampleCount != quality * quality)
+ {
+ throw new ArgumentOutOfRangeException("sampleCount != (quality * quality)");
+ }
+
+ if (sampleCount == 1)
+ {
+ samplesArray[0] = new PointF(0.0f, 0.0f);
+ }
+ else
+ {
+ for (int i = 0; i < sampleCount; ++i)
+ {
+ double y = (i + 1d) / (sampleCount + 1d);
+ double x = y * quality;
+
+ x -= (int)x;
+
+ samplesArray[i] = new PointF((float)(x - 0.5d), (float)(y - 0.5d));
+ }
+ }
+ }
+
+ public static bool IsObsolete(Type type, bool inherit)
+ {
+ object[] attrs = type.GetCustomAttributes(typeof(ObsoleteAttribute), inherit);
+ return (attrs.Length != 0);
+ }
+
+ public static void DrawDropShadow1px(Graphics g, Rectangle rect)
+ {
+ Brush b0 = new SolidBrush(Color.FromArgb(15, Color.Black));
+ Brush b1 = new SolidBrush(Color.FromArgb(47, Color.Black));
+ Pen p2 = new Pen(Color.FromArgb(63, Color.Black));
+
+ g.FillRectangle(b0, rect.Left, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Left + 1, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Left, rect.Top + 1, 1, 1);
+
+ g.FillRectangle(b0, rect.Right - 1, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Right - 2, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Right - 1, rect.Top + 1, 1, 1);
+
+ g.FillRectangle(b0, rect.Left, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Left + 1, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Left, rect.Bottom - 2, 1, 1);
+
+ g.FillRectangle(b0, rect.Right - 1, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Right - 2, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Right - 1, rect.Bottom - 2, 1, 1);
+
+ g.DrawLine(p2, rect.Left + 2, rect.Top, rect.Right - 3, rect.Top);
+ g.DrawLine(p2, rect.Left, rect.Top + 2, rect.Left, rect.Bottom - 3);
+ g.DrawLine(p2, rect.Left + 2, rect.Bottom - 1, rect.Right - 3, rect.Bottom - 1);
+ g.DrawLine(p2, rect.Right - 1, rect.Top + 2, rect.Right - 1, rect.Bottom - 3);
+
+ b0.Dispose();
+ b0 = null;
+ b1.Dispose();
+ b1 = null;
+ p2.Dispose();
+ p2 = null;
+ }
+
+ public static void DrawColorRectangle(Graphics g, Rectangle rect, Color color, bool drawBorder)
+ {
+ int inflateAmt = drawBorder ? -2 : 0;
+ Rectangle colorRectangle = Rectangle.Inflate(rect, inflateAmt, inflateAmt);
+ Brush colorBrush = new LinearGradientBrush(colorRectangle, Color.FromArgb(255, color), color, 90.0f, false);
+ HatchBrush backgroundBrush = new HatchBrush(HatchStyle.LargeCheckerBoard, Color.FromArgb(191, 191, 191), Color.FromArgb(255, 255, 255));
+
+ if (drawBorder)
+ {
+ g.DrawRectangle(Pens.Black, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1);
+ g.DrawRectangle(Pens.White, rect.Left + 1, rect.Top + 1, rect.Width - 3, rect.Height - 3);
+ }
+
+ PixelOffsetMode oldPOM = g.PixelOffsetMode;
+ g.PixelOffsetMode = PixelOffsetMode.Half;
+ g.FillRectangle(backgroundBrush, colorRectangle);
+ g.FillRectangle(colorBrush, colorRectangle);
+ g.PixelOffsetMode = oldPOM;
+
+ backgroundBrush.Dispose();
+ colorBrush.Dispose();
+ }
+
+ public static Size ComputeThumbnailSize(Size originalSize, int maxEdgeLength)
+ {
+ Size thumbSize;
+
+ if (originalSize.Width > originalSize.Height)
+ {
+ int longSide = Math.Min(originalSize.Width, maxEdgeLength);
+ thumbSize = new Size(longSide, Math.Max(1, (originalSize.Height * longSide) / originalSize.Width));
+ }
+ else if (originalSize.Height > originalSize.Width)
+ {
+ int longSide = Math.Min(originalSize.Height, maxEdgeLength);
+ thumbSize = new Size(Math.Max(1, (originalSize.Width * longSide) / originalSize.Height), longSide);
+ }
+ else // if (docSize.Width == docSize.Height)
+ {
+ int longSide = Math.Min(originalSize.Width, maxEdgeLength);
+ thumbSize = new Size(longSide, longSide);
+ }
+
+ return thumbSize;
+ }
+
+ public static Font CreateFont(string name, float size, FontStyle style)
+ {
+ Font returnFont;
+
+ try
+ {
+ returnFont = new Font(name, size, style);
+ }
+
+ catch (Exception)
+ {
+ returnFont = new Font(FontFamily.GenericSansSerif, size);
+ }
+
+ return returnFont;
+ }
+
+ public static Font CreateFont(string name, float size, string backupName, float backupSize, FontStyle style)
+ {
+ Font returnFont;
+
+ try
+ {
+ returnFont = new Font(name, size, style);
+ }
+
+ catch (Exception)
+ {
+ returnFont = CreateFont(backupName, backupSize, style);
+ }
+
+ return returnFont;
+ }
+
+ public static readonly Color TransparentKey = Color.FromArgb(192, 192, 192);
+
+ private static bool allowGCFullCollect = true;
+ public static bool AllowGCFullCollect
+ {
+ get
+ {
+ return allowGCFullCollect;
+ }
+
+ set
+ {
+ allowGCFullCollect = value;
+ }
+ }
+
+ public static void GCFullCollect()
+ {
+ if (AllowGCFullCollect)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+ }
+
+ private static int defaultSimplificationFactor = 50;
+ public static int DefaultSimplificationFactor
+ {
+ get
+ {
+ return defaultSimplificationFactor;
+ }
+
+ set
+ {
+ defaultSimplificationFactor = value;
+ }
+ }
+
+ public static void SplitRectangle(Rectangle rect, Rectangle[] rects)
+ {
+ int height = rect.Height;
+
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ Rectangle newRect = Rectangle.FromLTRB(rect.Left,
+ rect.Top + ((height * i) / rects.Length),
+ rect.Right,
+ rect.Top + ((height * (i + 1)) / rects.Length));
+
+ rects[i] = newRect;
+ }
+ }
+
+ public static long TicksToMs(long ticks)
+ {
+ return ticks / 10000;
+ }
+
+ public static string GetStaticName(Type type)
+ {
+ PropertyInfo pi = type.GetProperty("StaticName", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty);
+ return (string)pi.GetValue(null, null);
+ }
+
+ public static readonly float[][] Identity5x5F = new float[][] {
+ new float[] { 1, 0, 0, 0, 0 },
+ new float[] { 0, 1, 0, 0, 0 },
+ new float[] { 0, 0, 1, 0, 0 },
+ new float[] { 0, 0, 0, 1, 0 },
+ new float[] { 0, 0, 0, 0, 1 }
+ };
+
+ public static readonly ColorMatrix IdentityColorMatrix = new ColorMatrix(Identity5x5F);
+
+ [ThreadStatic]
+ private static Matrix identityMatrix = null;
+ public static Matrix IdentityMatrix
+ {
+ get
+ {
+ if (identityMatrix == null)
+ {
+ identityMatrix = new Matrix();
+ identityMatrix.Reset();
+ }
+
+ return identityMatrix;
+ }
+ }
+
+ /// <summary>
+ /// Rounds an integer to the smallest power of 2 that is greater
+ /// than or equal to it.
+ /// </summary>
+ public static int Log2RoundUp(int x)
+ {
+ if (x == 0)
+ {
+ return 1;
+ }
+
+ if (x == 1)
+ {
+ return 1;
+ }
+
+ return 1 << (1 + HighestBit(x - 1));
+ }
+
+ private static int HighestBit(int x)
+ {
+ if (x == 0)
+ {
+ return 0;
+ }
+
+ int b = 0;
+ int hi = 0;
+
+ while (b <= 30)
+ {
+ if ((x & (1 << b)) != 0)
+ {
+ hi = b;
+ }
+
+ ++b;
+ }
+
+ return hi;
+ }
+
+ private int CountBits(int x)
+ {
+ uint y = (uint)x;
+ int count = 0;
+
+ for (int bit = 0; bit < 32; ++bit)
+ {
+ if ((y & ((uint)1 << bit)) != 0)
+ {
+ ++count;
+ }
+ }
+
+ return count;
+ }
+
+ public static string RemoveSpaces(string s)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ foreach (char c in s)
+ {
+ if (!char.IsWhiteSpace(c))
+ {
+ sb.Append(c);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public static int Max(int[,] array)
+ {
+ int max = int.MinValue;
+
+ for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i)
+ {
+ for (int j = array.GetLowerBound(1); j <= array.GetUpperBound(1); ++j)
+ {
+ if (array[i,j] > max)
+ {
+ max = array[i,j];
+ }
+ }
+ }
+
+ return max;
+ }
+
+ public static int Sum(int[][] array)
+ {
+ int sum = 0;
+
+ for (int i = 0; i < array.Length; ++i)
+ {
+ int[] row = array[i];
+
+ for (int j = 0; j < row.Length; ++j)
+ {
+ sum += row[j];
+ }
+ }
+
+ return sum;
+ }
+
+ public static Icon ImageToIcon(Image image)
+ {
+ return ImageToIcon(image, Utility.TransparentKey);
+ }
+
+ public static Icon ImageToIcon(Image image, bool disposeImage)
+ {
+ return ImageToIcon(image, Utility.TransparentKey, disposeImage);
+ }
+
+ public static Icon ImageToIcon(Image image, Color seeThru)
+ {
+ return ImageToIcon(image, seeThru, false);
+ }
+
+ /// <summary>
+ /// Converts an Image to an Icon.
+ /// </summary>
+ /// <param name="image">The Image to convert to an icon. Must be an appropriate icon size (32x32, 16x16, etc).</param>
+ /// <param name="seeThru">The color that will be treated as transparent in the icon.</param>
+ /// <param name="disposeImage">Whether or not to dispose the passed-in Image.</param>
+ /// <returns>An Icon representation of the Image.</returns>
+ public static Icon ImageToIcon(Image image, Color seeThru, bool disposeImage)
+ {
+ Bitmap bitmap = new Bitmap(image);
+
+ for (int y = 0; y < bitmap.Height; ++y)
+ {
+ for (int x = 0; x < bitmap.Width; ++x)
+ {
+ if (bitmap.GetPixel(x, y) == seeThru)
+ {
+ bitmap.SetPixel(x, y, Color.FromArgb(0));
+ }
+ }
+ }
+
+ Icon icon = Icon.FromHandle(bitmap.GetHicon());
+ bitmap.Dispose();
+
+ if (disposeImage)
+ {
+ image.Dispose();
+ }
+
+ return icon;
+ }
+
+ public static Icon BitmapToIcon(Bitmap bitmap, bool disposeBitmap)
+ {
+ Icon icon = Icon.FromHandle(bitmap.GetHicon());
+
+ if (disposeBitmap)
+ {
+ bitmap.Dispose();
+ }
+
+ return icon;
+ }
+
+ public static Point GetRectangleCenter(Rectangle rect)
+ {
+ return new Point((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2);
+ }
+
+ public static PointF GetRectangleCenter(RectangleF rect)
+ {
+ return new PointF((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2);
+ }
+
+ public static Scanline[] GetRectangleScans(Rectangle rect)
+ {
+ Scanline[] scans = new Scanline[rect.Height];
+
+ for (int y = 0; y < rect.Height; ++y)
+ {
+ scans[y] = new Scanline(rect.X, rect.Y + y, rect.Width);
+ }
+
+ return scans;
+ }
+
+ public static Scanline[] GetRegionScans(Rectangle[] region)
+ {
+ int scanCount = 0;
+
+ for (int i = 0; i < region.Length; ++i)
+ {
+ scanCount += region[i].Height;
+ }
+
+ Scanline[] scans = new Scanline[scanCount];
+ int scanIndex = 0;
+
+ foreach (Rectangle rect in region)
+ {
+ for (int y = 0; y < rect.Height; ++y)
+ {
+ scans[scanIndex] = new Scanline(rect.X, rect.Y + y, rect.Width);
+ ++scanIndex;
+ }
+ }
+
+ return scans;
+ }
+
+ public static Rectangle[] ScanlinesToRectangles(Scanline[] scans)
+ {
+ return ScanlinesToRectangles(scans, 0, scans.Length);
+ }
+
+ public static Rectangle[] ScanlinesToRectangles(Scanline[] scans, int startIndex, int length)
+ {
+ Rectangle[] rects = new Rectangle[length];
+
+ for (int i = 0; i < length; ++i)
+ {
+ Scanline scan = scans[i + startIndex];
+ rects[i] = new Rectangle(scan.X, scan.Y, scan.Length, 1);
+ }
+
+ return rects;
+ }
+
+ public static RectangleF RectangleFromCenter(PointF center, float halfSize)
+ {
+ RectangleF ret = new RectangleF(center.X, center.Y, 0, 0);
+ ret.Inflate(halfSize, halfSize);
+ return ret;
+ }
+
+ public static List<PointF> PointListToPointFList(List<Point> ptList)
+ {
+ List<PointF> ret = new List<PointF>(ptList.Count);
+
+ for (int i = 0; i < ptList.Count; ++i)
+ {
+ ret.Add((PointF)ptList[i]);
+ }
+
+ return ret;
+ }
+
+ public static PointF[] PointArrayToPointFArray(Point[] ptArray)
+ {
+ PointF[] ret = new PointF[ptArray.Length];
+
+ for (int i = 0; i < ret.Length; ++i)
+ {
+ ret[i] = (PointF)ptArray[i];
+ }
+
+ return ret;
+ }
+
+ public static Rectangle[] InflateRectangles(Rectangle[] rects, int amount)
+ {
+ Rectangle[] inflated = new Rectangle[rects.Length];
+
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ inflated[i] = Rectangle.Inflate(rects[i], amount, amount);
+ }
+
+ return inflated;
+ }
+
+ public static void InflateRectanglesInPlace(Rectangle[] rects, int amount)
+ {
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ rects[i].Inflate(amount, amount);
+ }
+ }
+
+ public static RectangleF[] InflateRectangles(RectangleF[] rectsF, int amount)
+ {
+ RectangleF[] inflated = new RectangleF[rectsF.Length];
+
+ for (int i = 0; i < rectsF.Length; ++i)
+ {
+ inflated[i] = RectangleF.Inflate(rectsF[i], amount, amount);
+ }
+
+ return inflated;
+ }
+
+ public static void InflateRectanglesInPlace(RectangleF[] rectsF, float amount)
+ {
+ for (int i = 0; i < rectsF.Length; ++i)
+ {
+ rectsF[i].Inflate(amount, amount);
+ }
+ }
+
+ public static Rectangle PointsToConstrainedRectangle(Point a, Point b)
+ {
+ Rectangle rect = Utility.PointsToRectangle(a, b);
+ int minWH = Math.Min(rect.Width, rect.Height);
+
+ rect.Width = minWH;
+ rect.Height = minWH;
+
+ if (rect.Y != a.Y)
+ {
+ rect.Location = new Point(rect.X, a.Y - minWH);
+ }
+
+ if (rect.X != a.X)
+ {
+ rect.Location = new Point(a.X - minWH, rect.Y);
+ }
+
+ return rect;
+ }
+
+ public static RectangleF PointsToConstrainedRectangle(PointF a, PointF b)
+ {
+ RectangleF rect = Utility.PointsToRectangle(a, b);
+ float minWH = Math.Min(rect.Width, rect.Height);
+
+ rect.Width = minWH;
+ rect.Height = minWH;
+
+ if (rect.Y != a.Y)
+ {
+ rect.Location = new PointF(rect.X, a.Y - minWH);
+ }
+
+ if (rect.X != a.X)
+ {
+ rect.Location = new PointF(a.X - minWH, rect.Y);
+ }
+
+ return rect;
+ }
+
+ /// <summary>
+ /// Takes two points and creates a bounding rectangle from them.
+ /// </summary>
+ /// <param name="a">One corner of the rectangle.</param>
+ /// <param name="b">The other corner of the rectangle.</param>
+ /// <returns>A Rectangle instance that bounds the two points.</returns>
+ public static Rectangle PointsToRectangle(Point a, Point b)
+ {
+ int x = Math.Min(a.X, b.X);
+ int y = Math.Min(a.Y, b.Y);
+ int width = Math.Abs(a.X - b.X) + 1;
+ int height = Math.Abs(a.Y - b.Y) + 1;
+
+ return new Rectangle(x, y, width, height);
+ }
+
+ public static RectangleF PointsToRectangle(PointF a, PointF b)
+ {
+ float x = Math.Min(a.X, b.X);
+ float y = Math.Min(a.Y, b.Y);
+ float width = Math.Abs(a.X - b.X) + 1;
+ float height = Math.Abs(a.Y - b.Y) + 1;
+
+ return new RectangleF(x, y, width, height);
+ }
+
+ public static Rectangle PointsToRectangleExclusive(Point a, Point b)
+ {
+ int x = Math.Min(a.X, b.X);
+ int y = Math.Min(a.Y, b.Y);
+ int width = Math.Abs(a.X - b.X);
+ int height = Math.Abs(a.Y - b.Y);
+
+ return new Rectangle(x, y, width, height);
+ }
+
+ public static RectangleF PointsToRectangleExclusive(PointF a, PointF b)
+ {
+ float x = Math.Min(a.X, b.X);
+ float y = Math.Min(a.Y, b.Y);
+ float width = Math.Abs(a.X - b.X);
+ float height = Math.Abs(a.Y - b.Y);
+
+ return new RectangleF(x, y, width, height);
+ }
+
+ public static RectangleF[] PointsToRectangles(PointF[] pointsF)
+ {
+ if (pointsF.Length == 0)
+ {
+ return new RectangleF[] { };
+ }
+
+ if (pointsF.Length == 1)
+ {
+ return new RectangleF[] { new RectangleF(pointsF[0].X, pointsF[0].Y, 1, 1) };
+ }
+
+ RectangleF[] rectsF = new RectangleF[pointsF.Length - 1];
+
+ for (int i = 0; i < pointsF.Length - 1; ++i)
+ {
+ rectsF[i] = PointsToRectangle(pointsF[i], pointsF[i + 1]);
+ }
+
+ return rectsF;
+ }
+
+ public static Rectangle[] PointsToRectangles(Point[] points)
+ {
+ if (points.Length == 0)
+ {
+ return new Rectangle[] { };
+ }
+
+ if (points.Length == 1)
+ {
+ return new Rectangle[] { new Rectangle(points[0].X, points[0].Y, 1, 1) };
+ }
+
+ Rectangle[] rects = new Rectangle[points.Length - 1];
+
+ for (int i = 0; i < points.Length - 1; ++i)
+ {
+ rects[i] = PointsToRectangle(points[i], points[i + 1]);
+ }
+
+ return rects;
+ }
+
+ /// <summary>
+ /// Converts a RectangleF to RectangleF by rounding down the Location and rounding
+ /// up the Size.
+ /// </summary>
+ public static Rectangle RoundRectangle(RectangleF rectF)
+ {
+ float left = (float)Math.Floor(rectF.Left);
+ float top = (float)Math.Floor(rectF.Top);
+ float right = (float)Math.Ceiling(rectF.Right);
+ float bottom = (float)Math.Ceiling(rectF.Bottom);
+
+ return Rectangle.Truncate(RectangleF.FromLTRB(left, top, right, bottom));
+ }
+
+ public static Stack Reverse(Stack reverseMe)
+ {
+ Stack reversed = new Stack();
+
+ foreach (object o in reverseMe)
+ {
+ reversed.Push(o);
+ }
+
+ return reversed;
+ }
+
+ public static void SerializeObjectToStream(object graph, Stream stream)
+ {
+ new BinaryFormatter().Serialize(stream, graph);
+ }
+
+ public static object DeserializeObjectFromStream(Stream stream)
+ {
+ return new BinaryFormatter().Deserialize(stream);
+ }
+
+ [Obsolete("Use rect.Contains() instead", true)]
+ public static bool IsPointInRectangle(Point pt, Rectangle rect)
+ {
+ return rect.Contains(pt);
+ }
+
+ [Obsolete("Use rect.Contains() instead", true)]
+ public static bool IsPointInRectangle(int x, int y, Rectangle rect)
+ {
+ return rect.Contains(x, y);
+ }
+
+ public static Bitmap FullCloneBitmap(Bitmap cloneMe)
+ {
+ Bitmap bitmap = new Bitmap(cloneMe.Width, cloneMe.Height, cloneMe.PixelFormat);
+
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.DrawImage(cloneMe, 0, 0, cloneMe.Width, cloneMe.Height);
+ }
+
+ return bitmap;
+ }
+
+ /// <summary>
+ /// Allows you to find the bounding box for a "region" that is described as an
+ /// array of bounding boxes.
+ /// </summary>
+ /// <param name="rectsF">The "region" you want to find a bounding box for.</param>
+ /// <returns>A RectangleF structure that surrounds the Region.</returns>
+ public static RectangleF GetRegionBounds(RectangleF[] rectsF, int startIndex, int length)
+ {
+ if (rectsF.Length == 0)
+ {
+ return RectangleF.Empty;
+ }
+
+ float left = rectsF[startIndex].Left;
+ float top = rectsF[startIndex].Top;
+ float right = rectsF[startIndex].Right;
+ float bottom = rectsF[startIndex].Bottom;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ RectangleF rectF = rectsF[i];
+
+ if (rectF.Left < left)
+ {
+ left = rectF.Left;
+ }
+
+ if (rectF.Top < top)
+ {
+ top = rectF.Top;
+ }
+
+ if (rectF.Right > right)
+ {
+ right = rectF.Right;
+ }
+
+ if (rectF.Bottom > bottom)
+ {
+ bottom = rectF.Bottom;
+ }
+ }
+
+ return RectangleF.FromLTRB(left, top, right, bottom);
+ }
+
+ public static RectangleF GetTraceBounds(PointF[] pointsF, int startIndex, int length)
+ {
+ if (pointsF.Length == 0)
+ {
+ return RectangleF.Empty;
+ }
+
+ float left = pointsF[startIndex].X;
+ float top = pointsF[startIndex].Y;
+ float right = 1 + pointsF[startIndex].X;
+ float bottom = 1 + pointsF[startIndex].Y;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ PointF pointF = pointsF[i];
+
+ if (pointF.X < left)
+ {
+ left = pointF.X;
+ }
+
+ if (pointF.Y < top)
+ {
+ top = pointF.Y;
+ }
+
+ if (pointF.X > right)
+ {
+ right = pointF.X;
+ }
+
+ if (pointF.Y > bottom)
+ {
+ bottom = pointF.Y;
+ }
+ }
+
+ return RectangleF.FromLTRB(left, top, right, bottom);
+ }
+
+ public static Rectangle GetTraceBounds(Point[] points, int startIndex, int length)
+ {
+ if (points.Length == 0)
+ {
+ return Rectangle.Empty;
+ }
+
+ int left = points[startIndex].X;
+ int top = points[startIndex].Y;
+ int right = 1 + points[startIndex].X;
+ int bottom = 1 + points[startIndex].Y;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ Point point = points[i];
+
+ if (point.X < left)
+ {
+ left = point.X;
+ }
+
+ if (point.Y < top)
+ {
+ top = point.Y;
+ }
+
+ if (point.X > right)
+ {
+ right = point.X;
+ }
+
+ if (point.Y > bottom)
+ {
+ bottom = point.Y;
+ }
+ }
+
+ return Rectangle.FromLTRB(left, top, right, bottom);
+ }
+
+ /// <summary>
+ /// Allows you to find the bounding box for a "region" that is described as an
+ /// array of bounding boxes.
+ /// </summary>
+ /// <param name="rectsF">The "region" you want to find a bounding box for.</param>
+ /// <returns>A RectangleF structure that surrounds the Region.</returns>
+ public static Rectangle GetRegionBounds(Rectangle[] rects, int startIndex, int length)
+ {
+ if (rects.Length == 0)
+ {
+ return Rectangle.Empty;
+ }
+
+ int left = rects[startIndex].Left;
+ int top = rects[startIndex].Top;
+ int right = rects[startIndex].Right;
+ int bottom = rects[startIndex].Bottom;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ Rectangle rect = rects[i];
+
+ if (rect.Left < left)
+ {
+ left = rect.Left;
+ }
+
+ if (rect.Top < top)
+ {
+ top = rect.Top;
+ }
+
+ if (rect.Right > right)
+ {
+ right = rect.Right;
+ }
+
+ if (rect.Bottom > bottom)
+ {
+ bottom = rect.Bottom;
+ }
+ }
+
+ return Rectangle.FromLTRB(left, top, right, bottom);
+ }
+
+ public static RectangleF GetRegionBounds(RectangleF[] rectsF)
+ {
+ return GetRegionBounds(rectsF, 0, rectsF.Length);
+ }
+
+ public static Rectangle GetRegionBounds(Rectangle[] rects)
+ {
+ return GetRegionBounds(rects, 0, rects.Length);
+ }
+
+ private static float DistanceSquared(RectangleF[] rectsF, int indexA, int indexB)
+ {
+ PointF centerA = new PointF(rectsF[indexA].Left + (rectsF[indexA].Width / 2), rectsF[indexA].Top + (rectsF[indexA].Height / 2));
+ PointF centerB = new PointF(rectsF[indexB].Left + (rectsF[indexB].Width / 2), rectsF[indexB].Top + (rectsF[indexB].Height / 2));
+
+ return ((centerA.X - centerB.X) * (centerA.X - centerB.X)) +
+ ((centerA.Y - centerB.Y) * (centerA.Y - centerB.Y));
+ }
+
+ public static Rectangle[] SimplifyRegion(Rectangle[] rects, int complexity)
+ {
+ if (complexity == 0 || rects.Length < complexity)
+ {
+ return (Rectangle[])rects.Clone();
+ }
+
+ Rectangle[] boxes = new Rectangle[complexity];
+
+ for (int i = 0; i < complexity; ++i)
+ {
+ int startIndex = (i * rects.Length) / complexity;
+ int length = Math.Min(rects.Length, ((i + 1) * rects.Length) / complexity) - startIndex;
+ boxes[i] = GetRegionBounds(rects, startIndex, length);
+ }
+
+ return boxes;
+ }
+
+
+ public static RectangleF[] SimplifyTrace(PointF[] pointsF, int complexity)
+ {
+ if (complexity == 0 ||
+ (pointsF.Length - 1) < complexity)
+ {
+ return PointsToRectangles(pointsF);
+ }
+
+ RectangleF[] boxes = new RectangleF[complexity];
+ int parLength = pointsF.Length - 1; // "(points as Rectangles).Length"
+
+ for (int i = 0; i < complexity; ++i)
+ {
+ int startIndex = (i * parLength) / complexity;
+ int length = Math.Min(parLength, ((i + 1) * parLength) / complexity) - startIndex;
+ boxes[i] = GetTraceBounds(pointsF, startIndex, length + 1);
+ }
+
+ return boxes;
+ }
+
+ public static RectangleF[] SimplifyTrace(PointF[] pointsF)
+ {
+ return SimplifyTrace(pointsF, defaultSimplificationFactor);
+ }
+
+ public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects, int complexity, int inflationAmount)
+ {
+ Rectangle[] simplified = SimplifyRegion(rects, complexity);
+
+ for (int i = 0; i < simplified.Length; ++i)
+ {
+ simplified[i].Inflate(inflationAmount, inflationAmount);
+ }
+
+ return simplified;
+ }
+
+ public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects)
+ {
+ return SimplifyAndInflateRegion(rects, defaultSimplificationFactor, 1);
+ }
+
+ public static RectangleF[] TranslateRectangles(RectangleF[] rectsF, PointF offset)
+ {
+ RectangleF[] retRectsF = new RectangleF[rectsF.Length];
+ int i = 0;
+
+ foreach (RectangleF rectF in rectsF)
+ {
+ retRectsF[i] = new RectangleF(rectF.X + offset.X, rectF.Y + offset.Y, rectF.Width, rectF.Height);
+ ++i;
+ }
+
+ return retRectsF;
+ }
+
+ public static Rectangle[] TranslateRectangles(Rectangle[] rects, int dx, int dy)
+ {
+ Rectangle[] retRects = new Rectangle[rects.Length];
+
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ retRects[i] = new Rectangle(rects[i].X + dx, rects[i].Y + dy, rects[i].Width, rects[i].Height);
+ }
+
+ return retRects;
+ }
+
+ public static void TranslatePointsInPlace(PointF[] ptsF, float dx, float dy)
+ {
+ for (int i = 0; i < ptsF.Length; ++i)
+ {
+ ptsF[i].X += dx;
+ ptsF[i].Y += dy;
+ }
+ }
+
+ public static void TranslatePointsInPlace(Point[] pts, int dx, int dy)
+ {
+ for (int i = 0; i < pts.Length; ++i)
+ {
+ pts[i].X += dx;
+ pts[i].Y += dy;
+ }
+ }
+
+ public static Rectangle[] TruncateRectangles(RectangleF[] rectsF)
+ {
+ Rectangle[] rects = new Rectangle[rectsF.Length];
+
+ for (int i = 0; i < rectsF.Length; ++i)
+ {
+ rects[i] = Rectangle.Truncate(rectsF[i]);
+ }
+
+ return rects;
+ }
+
+ public static Point[] TruncatePoints(PointF[] pointsF)
+ {
+ Point[] points = new Point[pointsF.Length];
+
+ for (int i = 0; i < pointsF.Length; ++i)
+ {
+ points[i] = Point.Truncate(pointsF[i]);
+ }
+
+ return points;
+ }
+
+ public static Point[] RoundPoints(PointF[] pointsF)
+ {
+ Point[] points = new Point[pointsF.Length];
+
+ for (int i = 0; i < pointsF.Length; ++i)
+ {
+ points[i] = Point.Round(pointsF[i]);
+ }
+
+ return points;
+ }
+
+ /// <summary>
+ /// The Sutherland-Hodgman clipping alrogithm.
+ /// http://ezekiel.vancouver.wsu.edu/~cs442/lectures/clip/clip/index.html
+ ///
+ /// # Clipping a convex polygon to a convex region (e.g., rectangle) will always produce a convex polygon (or no polygon if completely outside the clipping region).
+ /// # Clipping a concave polygon to a rectangle may produce several polygons (see figure above) or, as the following algorithm does, produce a single, possibly degenerate, polygon.
+ /// # Divide and conquer: Clip entire polygon against a single edge (i.e., half-plane). Repeat for each edge in the clipping region.
+ ///
+ /// The input is a sequence of vertices: {v0, v1, ... vn} given as an array of Points
+ /// the result is a sequence of vertices, given as an array of Points. This result may have
+ /// less than, equal, more than, or 0 vertices.
+ /// </summary>
+ /// <param name="vertices"></param>
+ /// <returns></returns>
+ public static List<PointF> SutherlandHodgman(RectangleF bounds, List<PointF> v)
+ {
+ List<PointF> p1 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Left, v);
+ List<PointF> p2 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Right, p1);
+ List<PointF> p3 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Top, p2);
+ List<PointF> p4 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Bottom, p3);
+
+ return p4;
+ }
+
+ private enum RectangleEdge
+ {
+ Left,
+ Right,
+ Top,
+ Bottom
+ }
+
+ private static List<PointF> SutherlandHodgmanOneAxis(RectangleF bounds, RectangleEdge edge, List<PointF> v)
+ {
+ if (v.Count == 0)
+ {
+ return new List<PointF>();
+ }
+
+ List<PointF> polygon = new List<PointF>();
+
+ PointF s = v[v.Count - 1];
+
+ for (int i = 0; i < v.Count; ++i)
+ {
+ PointF p = v[i];
+ bool pIn = IsInside(bounds, edge, p);
+ bool sIn = IsInside(bounds, edge, s);
+
+ if (sIn && pIn)
+ {
+ // case 1: inside -> inside
+ polygon.Add(p);
+ }
+ else if (sIn && !pIn)
+ {
+ // case 2: inside -> outside
+ polygon.Add(LineIntercept(bounds, edge, s, p));
+ }
+ else if (!sIn && !pIn)
+ {
+ // case 3: outside -> outside
+ // emit nothing
+ }
+ else if (!sIn && pIn)
+ {
+ // case 4: outside -> inside
+ polygon.Add(LineIntercept(bounds, edge, s, p));
+ polygon.Add(p);
+ }
+
+ s = p;
+ }
+
+ return polygon;
+ }
+
+ private static bool IsInside(RectangleF bounds, RectangleEdge edge, PointF p)
+ {
+ switch (edge)
+ {
+ case RectangleEdge.Left:
+ return !(p.X < bounds.Left);
+
+ case RectangleEdge.Right:
+ return !(p.X >= bounds.Right);
+
+ case RectangleEdge.Top:
+ return !(p.Y < bounds.Top);
+
+ case RectangleEdge.Bottom:
+ return !(p.Y >= bounds.Bottom);
+
+ default:
+ throw new InvalidEnumArgumentException("edge");
+ }
+ }
+
+ private static Point LineIntercept(Rectangle bounds, RectangleEdge edge, Point a, Point b)
+ {
+ if (a == b)
+ {
+ return a;
+ }
+
+ switch (edge)
+ {
+ case RectangleEdge.Bottom:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom);
+
+ case RectangleEdge.Left:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Right:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Top:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top);
+ }
+
+ throw new ArgumentException("no intercept found");
+ }
+
+ private static PointF LineIntercept(RectangleF bounds, RectangleEdge edge, PointF a, PointF b)
+ {
+ if (a == b)
+ {
+ return a;
+ }
+
+ switch (edge)
+ {
+ case RectangleEdge.Bottom:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom);
+
+ case RectangleEdge.Left:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Right:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Top:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top);
+ }
+
+ throw new ArgumentException("no intercept found");
+ }
+
+ public static Point[] GetLinePoints(Point first, Point second)
+ {
+ Point[] coords = null;
+
+ int x1 = first.X;
+ int y1 = first.Y;
+ int x2 = second.X;
+ int y2 = second.Y;
+ int dx = x2 - x1;
+ int dy = y2 - y1;
+ int dxabs = Math.Abs(dx);
+ int dyabs = Math.Abs(dy);
+ int px = x1;
+ int py = y1;
+ int sdx = Math.Sign(dx);
+ int sdy = Math.Sign(dy);
+ int x = 0;
+ int y = 0;
+
+ if (dxabs > dyabs)
+ {
+ coords = new Point[dxabs + 1];
+
+ for (int i = 0; i <= dxabs; i++)
+ {
+ y += dyabs;
+
+ if (y >= dxabs)
+ {
+ y -= dxabs;
+ py += sdy;
+ }
+
+ coords[i] = new Point(px, py);
+ px += sdx;
+ }
+ }
+ else
+ // had to add in this cludge for slopes of 1 ... wasn't drawing half the line
+ if (dxabs == dyabs)
+ {
+ coords = new Point[dxabs + 1];
+
+ for (int i = 0; i <= dxabs; i++)
+ {
+ coords[i] = new Point(px, py);
+ px += sdx;
+ py += sdy;
+ }
+ }
+ else
+ {
+ coords = new Point[dyabs + 1];
+
+ for (int i = 0; i <= dyabs; i++)
+ {
+ x += dxabs;
+
+ if (x >= dyabs)
+ {
+ x -= dyabs;
+ px += sdx;
+ }
+
+ coords[i] = new Point(px, py);
+ py += sdy;
+ }
+ }
+
+ return coords;
+ }
+
+ public static long GetTimeMs()
+ {
+ return Utility.TicksToMs(DateTime.Now.Ticks);
+ }
+
+ /// <summary>
+ /// Returns the Distance between two points
+ /// </summary>
+ public static float Distance(PointF a, PointF b)
+ {
+ return Magnitude(new PointF(a.X - b.X, a.Y - b.Y));
+ }
+
+ /// <summary>
+ /// Returns the Magnitude (distance to origin) of a point
+ /// </summary>
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static float Magnitude(PointF p)
+ {
+ return (float)Math.Sqrt(p.X * p.X + p.Y * p.Y);
+ }
+
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static double Clamp(double x, double min, double max)
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static float Clamp(float x, float min, float max)
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static int Clamp(int x, int min, int max)
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ public static byte ClampToByte(double x)
+ {
+ if (x > 255)
+ {
+ return 255;
+ }
+ else if (x < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)x;
+ }
+ }
+
+ public static byte ClampToByte(float x)
+ {
+ if (x > 255)
+ {
+ return 255;
+ }
+ else if (x < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)x;
+ }
+ }
+
+ public static byte ClampToByte(int x)
+ {
+ if (x > 255)
+ {
+ return 255;
+ }
+ else if (x < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)x;
+ }
+ }
+
+ public static float Lerp(float from, float to, float frac)
+ {
+ return (from + frac * (to - from));
+ }
+
+ public static double Lerp(double from, double to, double frac)
+ {
+ return (from + frac * (to - from));
+ }
+
+ public static PointF Lerp(PointF from, PointF to, float frac)
+ {
+ return new PointF(Lerp(from.X, to.X, frac), Lerp(from.Y, to.Y, frac));
+ }
+
+ public static int ColorDifference(ColorBgra a, ColorBgra b)
+ {
+ return (int)Math.Ceiling(Math.Sqrt(ColorDifferenceSquared(a, b)));
+ }
+
+ public static int ColorDifferenceSquared(ColorBgra a, ColorBgra b)
+ {
+ int diffSq = 0, tmp;
+
+ tmp = a.R - b.R;
+ diffSq += tmp * tmp;
+ tmp = a.G - b.G;
+ diffSq += tmp * tmp;
+ tmp = a.B - b.B;
+ diffSq += tmp * tmp;
+
+ return diffSq / 3;
+ }
+
+ /// <summary>
+ /// Reads a 16-bit unsigned integer from a Stream in little-endian format.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <returns>-1 on failure, else the 16-bit unsigned integer that was read.</returns>
+ public static int ReadUInt16(Stream stream)
+ {
+ int byte1 = stream.ReadByte();
+
+ if (byte1 == -1)
+ {
+ return -1;
+ }
+
+ int byte2 = stream.ReadByte();
+
+ if (byte2 == -1)
+ {
+ return -1;
+ }
+
+ return byte1 + (byte2 << 8);
+ }
+
+ public static void WriteUInt16(Stream stream, UInt16 word)
+ {
+ stream.WriteByte((byte)(word & 0xff));
+ stream.WriteByte((byte)(word >> 8));
+ }
+
+ public static void WriteUInt24(Stream stream, int uint24)
+ {
+ stream.WriteByte((byte)(uint24 & 0xff));
+ stream.WriteByte((byte)((uint24 >> 8) & 0xff));
+ stream.WriteByte((byte)((uint24 >> 16) & 0xff));
+ }
+
+ public static void WriteUInt32(Stream stream, UInt32 uint32)
+ {
+ stream.WriteByte((byte)(uint32 & 0xff));
+ stream.WriteByte((byte)((uint32 >> 8) & 0xff));
+ stream.WriteByte((byte)((uint32 >> 16) & 0xff));
+ stream.WriteByte((byte)((uint32 >> 24) & 0xff));
+ }
+
+ /// <summary>
+ /// Reads a 24-bit unsigned integer from a Stream in little-endian format.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <returns>-1 on failure, else the 24-bit unsigned integer that was read.</returns>
+ public static int ReadUInt24(Stream stream)
+ {
+ int byte1 = stream.ReadByte();
+
+ if (byte1 == -1)
+ {
+ return -1;
+ }
+
+ int byte2 = stream.ReadByte();
+
+ if (byte2 == -1)
+ {
+ return -1;
+ }
+
+ int byte3 = stream.ReadByte();
+
+ if (byte3 == -1)
+ {
+ return -1;
+ }
+
+ return byte1 + (byte2 << 8) + (byte3 << 16);
+ }
+
+ /// <summary>
+ /// Reads a 32-bit unsigned integer from a Stream in little-endian format.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <returns>-1 on failure, else the 32-bit unsigned integer that was read.</returns>
+ public static long ReadUInt32(Stream stream)
+ {
+ int byte1 = stream.ReadByte();
+
+ if (byte1 == -1)
+ {
+ return -1;
+ }
+
+ int byte2 = stream.ReadByte();
+
+ if (byte2 == -1)
+ {
+ return -1;
+ }
+
+ int byte3 = stream.ReadByte();
+
+ if (byte3 == -1)
+ {
+ return -1;
+ }
+
+ int byte4 = stream.ReadByte();
+
+ if (byte4 == -1)
+ {
+ return -1;
+ }
+
+ return unchecked((long)((uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24))));
+ }
+
+ public static int ReadFromStream(Stream input, byte[] buffer, int offset, int count)
+ {
+ int totalBytesRead = 0;
+
+ while (totalBytesRead < count)
+ {
+ int bytesRead = input.Read(buffer, offset + totalBytesRead, count - totalBytesRead);
+
+ if (bytesRead == 0)
+ {
+ throw new IOException("ran out of data");
+ }
+
+ totalBytesRead += bytesRead;
+ }
+
+ return totalBytesRead;
+ }
+
+ public static long CopyStream(Stream input, Stream output, long maxBytes)
+ {
+ long bytesCopied = 0;
+ byte[] buffer = new byte[4096];
+
+ while (true)
+ {
+ int bytesRead = input.Read(buffer, 0, buffer.Length);
+
+ if (bytesRead == 0)
+ {
+ break;
+ }
+ else
+ {
+ int bytesToCopy;
+
+ if (maxBytes != -1 && (bytesCopied + bytesRead) > maxBytes)
+ {
+ bytesToCopy = (int)(maxBytes - bytesCopied);
+ }
+ else
+ {
+ bytesToCopy = bytesRead;
+ }
+
+ output.Write(buffer, 0, bytesRead);
+ bytesCopied += bytesToCopy;
+
+ if (bytesToCopy != bytesRead)
+ {
+ break;
+ }
+ }
+ }
+
+ return bytesCopied;
+ }
+
+ public static long CopyStream(Stream input, Stream output)
+ {
+ return CopyStream(input, output, -1);
+ }
+
+ private struct Edge
+ {
+ public int miny; // int
+ public int maxy; // int
+ public int x; // fixed point: 24.8
+ public int dxdy; // fixed point: 24.8
+
+ public Edge(int miny, int maxy, int x, int dxdy)
+ {
+ this.miny = miny;
+ this.maxy = maxy;
+ this.x = x;
+ this.dxdy = dxdy;
+ }
+ }
+
+ public static Scanline[] GetScans(Point[] vertices)
+ {
+ return GetScans(vertices, 0, vertices.Length);
+ }
+
+ public static Scanline[] GetScans(Point[] vertices, int startIndex, int length)
+ {
+ if (length > vertices.Length - startIndex)
+ {
+ throw new ArgumentException("out of bounds: length > vertices.Length - startIndex");
+ }
+
+ int ymax = 0;
+
+ // Build edge table
+ Edge[] edgeTable = new Edge[length];
+ int edgeCount = 0;
+
+ for (int i = startIndex; i < startIndex + length; ++i)
+ {
+ Point top = vertices[i];
+ Point bottom = vertices[(((i + 1) - startIndex) % length) + startIndex];
+ int dy;
+
+ if (top.Y > bottom.Y)
+ {
+ Point temp = top;
+ top = bottom;
+ bottom = temp;
+ }
+
+ dy = bottom.Y - top.Y;
+
+ if (dy != 0)
+ {
+ edgeTable[edgeCount] = new Edge(top.Y, bottom.Y, top.X << 8, (((bottom.X - top.X) << 8) / dy));
+ ymax = Math.Max(ymax, bottom.Y);
+ ++edgeCount;
+ }
+ }
+
+ // Sort edge table by miny
+ for (int i = 0; i < edgeCount - 1; ++i)
+ {
+ int min = i;
+
+ for (int j = i + 1; j < edgeCount; ++j)
+ {
+ if (edgeTable[j].miny < edgeTable[min].miny)
+ {
+ min = j;
+ }
+ }
+
+ if (min != i)
+ {
+ Edge temp = edgeTable[min];
+ edgeTable[min] = edgeTable[i];
+ edgeTable[i] = temp;
+ }
+ }
+
+ // Compute how many scanlines we will be emitting
+ int scanCount = 0;
+ int activeLow = 0;
+ int activeHigh = 0;
+ int yscan1 = edgeTable[0].miny;
+
+ // we assume that edgeTable[0].miny == yscan
+ while (activeHigh < edgeCount - 1 &&
+ edgeTable[activeHigh + 1].miny == yscan1)
+ {
+ ++activeHigh;
+ }
+
+ while (yscan1 <= ymax)
+ {
+ // Find new edges where yscan == miny
+ while (activeHigh < edgeCount - 1 &&
+ edgeTable[activeHigh + 1].miny == yscan1)
+ {
+ ++activeHigh;
+ }
+
+ int count = 0;
+ for (int i = activeLow; i <= activeHigh; ++i)
+ {
+ if (edgeTable[i].maxy > yscan1)
+ {
+ ++count;
+ }
+ }
+
+ scanCount += count / 2;
+ ++yscan1;
+
+ // Remove edges where yscan == maxy
+ while (activeLow < edgeCount - 1 &&
+ edgeTable[activeLow].maxy <= yscan1)
+ {
+ ++activeLow;
+ }
+
+ if (activeLow > activeHigh)
+ {
+ activeHigh = activeLow;
+ }
+ }
+
+ // Allocate scanlines that we'll return
+ Scanline[] scans = new Scanline[scanCount];
+
+ // Active Edge Table (AET): it is indices into the Edge Table (ET)
+ int[] active = new int[edgeCount];
+ int activeCount = 0;
+ int yscan2 = edgeTable[0].miny;
+ int scansIndex = 0;
+
+ // Repeat until both the ET and AET are empty
+ while (yscan2 <= ymax)
+ {
+ // Move any edges from the ET to the AET where yscan == miny
+ for (int i = 0; i < edgeCount; ++i)
+ {
+ if (edgeTable[i].miny == yscan2)
+ {
+ active[activeCount] = i;
+ ++activeCount;
+ }
+ }
+
+ // Sort the AET on x
+ for (int i = 0; i < activeCount - 1; ++i)
+ {
+ int min = i;
+
+ for (int j = i + 1; j < activeCount; ++j)
+ {
+ if (edgeTable[active[j]].x < edgeTable[active[min]].x)
+ {
+ min = j;
+ }
+ }
+
+ if (min != i)
+ {
+ int temp = active[min];
+ active[min] = active[i];
+ active[i] = temp;
+ }
+ }
+
+ // For each pair of entries in the AET, fill in pixels between their info
+ for (int i = 0; i < activeCount; i += 2)
+ {
+ Edge el = edgeTable[active[i]];
+ Edge er = edgeTable[active[i + 1]];
+ int startx = (el.x + 0xff) >> 8; // ceil(x)
+ int endx = er.x >> 8; // floor(x)
+
+ scans[scansIndex] = new Scanline(startx, yscan2, endx - startx);
+ ++scansIndex;
+ }
+
+ ++yscan2;
+
+ // Remove from the AET any edge where yscan == maxy
+ int k = 0;
+ while (k < activeCount && activeCount > 0)
+ {
+ if (edgeTable[active[k]].maxy == yscan2)
+ {
+ // remove by shifting everything down one
+ for (int j = k + 1; j < activeCount; ++j)
+ {
+ active[j - 1] = active[j];
+ }
+
+ --activeCount;
+ }
+ else
+ {
+ ++k;
+ }
+ }
+
+ // Update x for each entry in AET
+ for (int i = 0; i < activeCount; ++i)
+ {
+ edgeTable[active[i]].x += edgeTable[active[i]].dxdy;
+ }
+ }
+
+ return scans;
+ }
+
+ public static PointF TransformOnePoint(Matrix matrix, PointF ptF)
+ {
+ PointF[] ptFs = new PointF[1] { ptF };
+ matrix.TransformPoints(ptFs);
+ return ptFs[0];
+ }
+
+ public static PointF TransformOneVector(Matrix matrix, PointF ptF)
+ {
+ PointF[] ptFs = new PointF[1] { ptF };
+ matrix.TransformVectors(ptFs);
+ return ptFs[0];
+ }
+
+ public static PointF NormalizeVector(PointF vecF)
+ {
+ float magnitude = Magnitude(vecF);
+ vecF.X /= magnitude;
+ vecF.Y /= magnitude;
+ return vecF;
+ }
+
+ public static PointF NormalizeVector2(PointF vecF)
+ {
+ float magnitude = Magnitude(vecF);
+
+ if (magnitude == 0)
+ {
+ vecF.X = 0;
+ vecF.Y = 0;
+ }
+ else
+ {
+ vecF.X /= magnitude;
+ vecF.Y /= magnitude;
+ }
+
+ return vecF;
+ }
+
+ public static void NormalizeVectors(PointF[] vecsF)
+ {
+ for (int i = 0; i < vecsF.Length; ++i)
+ {
+ vecsF[i] = NormalizeVector(vecsF[i]);
+ }
+ }
+
+ public static PointF RotateVector(PointF vecF, float angleDelta)
+ {
+ angleDelta *= (float)( Math.PI / 180.0);
+ float vecFLen = Magnitude(vecF);
+ float vecFAngle = angleDelta + (float)Math.Atan2(vecF.Y, vecF.X);
+ vecF.X = (float)Math.Cos(vecFAngle);
+ vecF.Y = (float)Math.Sin(vecFAngle);
+ return vecF;
+ }
+
+ public static void RotateVectors(PointF[] vecFs, float angleDelta)
+ {
+ for (int i = 0; i < vecFs.Length; ++i)
+ {
+ vecFs[i] = RotateVector(vecFs[i], angleDelta);
+ }
+ }
+
+ public static PointF MultiplyVector(PointF vecF, float scalar)
+ {
+ return new PointF(vecF.X * scalar, vecF.Y * scalar);
+ }
+
+ public static PointF AddVectors(PointF a, PointF b)
+ {
+ return new PointF(a.X + b.X, a.Y + b.Y);
+ }
+
+ public static PointF SubtractVectors(PointF lhs, PointF rhs)
+ {
+ return new PointF(lhs.X - rhs.X, lhs.Y - rhs.Y);
+ }
+
+ public static PointF NegateVector(PointF v)
+ {
+ return new PointF(-v.X, -v.Y);
+ }
+
+ public static float GetAngleOfTransform(Matrix matrix)
+ {
+ PointF[] pts = new PointF[] { new PointF(1.0f, 0.0f) };
+ matrix.TransformVectors(pts);
+ double atan2 = Math.Atan2(pts[0].Y, pts[0].X);
+ double angle = atan2 * (180.0f / Math.PI);
+
+ return (float)angle;
+ }
+
+ public static bool IsTransformFlipped(Matrix matrix)
+ {
+ PointF ptX = new PointF(1.0f, 0.0f);
+ PointF ptXT = Utility.TransformOneVector(matrix, ptX);
+ double atan2X = Math.Atan2(ptXT.Y, ptXT.X);
+ double angleX = atan2X * (180.0 / Math.PI);
+
+ PointF ptY = new PointF(0.0f, 1.0f);
+ PointF ptYT = Utility.TransformOneVector(matrix, ptY);
+ double atan2Y = Math.Atan2(ptYT.Y, ptYT.X);
+ double angleY = (atan2Y * (180.0 / Math.PI)) - 90.0;
+
+ while (angleX < 0)
+ {
+ angleX += 360;
+ }
+
+ while (angleY < 0)
+ {
+ angleY += 360;
+ }
+
+ double angleDelta = Math.Abs(angleX - angleY);
+
+ return angleDelta > 1.0 && angleDelta < 359.0;
+ }
+
+ /// <summary>
+ /// Calculates the dot product of two vectors.
+ /// </summary>
+ public static float DotProduct(PointF lhs, PointF rhs)
+ {
+ return lhs.X * rhs.X + lhs.Y * rhs.Y;
+ }
+
+ /// <summary>
+ /// Calculates the orthogonal projection of y on to u.
+ /// yhat = u * ((y dot u) / (u dot u))
+ /// z = y - yhat
+ /// Section 6.2 (pg. 381) of Linear Algebra and its Applications, Second Edition, by David C. Lay
+ /// </summary>
+ /// <param name="y">The vector to decompose</param>
+ /// <param name="u">The non-zero vector to project y on to</param>
+ /// <param name="yhat">The orthogonal projection of y onto u</param>
+ /// <param name="yhatLen">The length of yhat such that yhat = yhatLen * u</param>
+ /// <param name="z">The component of y orthogonal to u</param>
+ /// <remarks>
+ /// As a special case, if u=(0,0) the results are all zero.
+ /// </remarks>
+ public static void GetProjection(PointF y, PointF u, out PointF yhat, out float yhatLen, out PointF z)
+ {
+ if (u.X == 0 && u.Y == 0)
+ {
+ yhat = new PointF(0, 0);
+ yhatLen = 0;
+ z = new PointF(0, 0);
+ }
+ else
+ {
+ float yDotU = DotProduct(y, u);
+ float uDotU = DotProduct(u, u);
+ yhatLen = yDotU / uDotU;
+ yhat = MultiplyVector(u, yhatLen);
+ z = SubtractVectors(y, yhat);
+ }
+ }
+
+ public static int GreatestCommonDivisor(int a, int b)
+ {
+ int r;
+
+ if (a < b)
+ {
+ r = a;
+ a = b;
+ b = r;
+ }
+
+ do
+ {
+ r = a % b;
+ a = b;
+ b = r;
+ } while (r != 0);
+
+ return a;
+ }
+
+ public static void Swap(ref int a, ref int b)
+ {
+ int t;
+
+ t = a;
+ a = b;
+ b = t;
+ }
+
+ public static void Swap<T>(ref T a, ref T b)
+ {
+ T t;
+
+ t = a;
+ a = b;
+ b = t;
+ }
+
+ private static byte[] DownloadSmallFile(Uri uri, WebProxy proxy)
+ {
+ WebRequest request = WebRequest.Create(uri);
+
+ if (proxy != null)
+ {
+ request.Proxy = proxy;
+ }
+
+ request.Timeout = 5000;
+ WebResponse response = request.GetResponse();
+ Stream stream = response.GetResponseStream();
+
+ try
+ {
+ byte[] buffer = new byte[8192];
+ int offset = 0;
+
+ while (offset < buffer.Length)
+ {
+ int bytesRead = stream.Read(buffer, offset, buffer.Length - offset);
+
+ if (bytesRead == 0)
+ {
+ byte[] smallerBuffer = new byte[offset + bytesRead];
+
+ for (int i = 0; i < offset + bytesRead; ++i)
+ {
+ smallerBuffer[i] = buffer[i];
+ }
+
+ buffer = smallerBuffer;
+ }
+
+ offset += bytesRead;
+ }
+
+ return buffer;
+ }
+
+ finally
+ {
+ if (stream != null)
+ {
+ stream.Close();
+ stream = null;
+ }
+
+ if (response != null)
+ {
+ response.Close();
+ response = null;
+ }
+ }
+ }
+
+ public static T[] RepeatArray<T>(T[] array, int repeatCount)
+ {
+ T[] returnArray = new T[repeatCount * array.Length];
+
+ for (int i = 0; i < repeatCount; ++i)
+ {
+ for (int j = 0; j < array.Length; ++j)
+ {
+ int index = (i * array.Length) + j;
+ returnArray[index] = array[j];
+ }
+ }
+
+ return returnArray;
+ }
+
+ public static byte FastScaleByteByByte(byte a, byte b)
+ {
+ int r1 = a * b + 0x80;
+ int r2 = ((r1 >> 8) + r1) >> 8;
+ return (byte)r2;
+ }
+
+ public static int FastDivideShortByByte(ushort n, byte d)
+ {
+ int i = d * 3;
+ uint m = masTable[i];
+ uint a = masTable[i + 1];
+ uint s = masTable[i + 2];
+
+ uint nTimesMPlusA = unchecked((n * m) + a);
+ uint shifted = nTimesMPlusA >> (int)s;
+ int r = (int)shifted;
+
+ return r;
+ }
+
+ // i = z * 3;
+ // (x / z) = ((x * masTable[i]) + masTable[i + 1]) >> masTable[i + 2)
+ private static readonly uint[] masTable =
+ {
+ 0x00000000, 0x00000000, 0, // 0
+ 0x00000001, 0x00000000, 0, // 1
+ 0x00000001, 0x00000000, 1, // 2
+ 0xAAAAAAAB, 0x00000000, 33, // 3
+ 0x00000001, 0x00000000, 2, // 4
+ 0xCCCCCCCD, 0x00000000, 34, // 5
+ 0xAAAAAAAB, 0x00000000, 34, // 6
+ 0x49249249, 0x49249249, 33, // 7
+ 0x00000001, 0x00000000, 3, // 8
+ 0x38E38E39, 0x00000000, 33, // 9
+ 0xCCCCCCCD, 0x00000000, 35, // 10
+ 0xBA2E8BA3, 0x00000000, 35, // 11
+ 0xAAAAAAAB, 0x00000000, 35, // 12
+ 0x4EC4EC4F, 0x00000000, 34, // 13
+ 0x49249249, 0x49249249, 34, // 14
+ 0x88888889, 0x00000000, 35, // 15
+ 0x00000001, 0x00000000, 4, // 16
+ 0xF0F0F0F1, 0x00000000, 36, // 17
+ 0x38E38E39, 0x00000000, 34, // 18
+ 0xD79435E5, 0xD79435E5, 36, // 19
+ 0xCCCCCCCD, 0x00000000, 36, // 20
+ 0xC30C30C3, 0xC30C30C3, 36, // 21
+ 0xBA2E8BA3, 0x00000000, 36, // 22
+ 0xB21642C9, 0x00000000, 36, // 23
+ 0xAAAAAAAB, 0x00000000, 36, // 24
+ 0x51EB851F, 0x00000000, 35, // 25
+ 0x4EC4EC4F, 0x00000000, 35, // 26
+ 0x97B425ED, 0x97B425ED, 36, // 27
+ 0x49249249, 0x49249249, 35, // 28
+ 0x8D3DCB09, 0x00000000, 36, // 29
+ 0x88888889, 0x00000000, 36, // 30
+ 0x42108421, 0x42108421, 35, // 31
+ 0x00000001, 0x00000000, 5, // 32
+ 0x3E0F83E1, 0x00000000, 35, // 33
+ 0xF0F0F0F1, 0x00000000, 37, // 34
+ 0x75075075, 0x75075075, 36, // 35
+ 0x38E38E39, 0x00000000, 35, // 36
+ 0x6EB3E453, 0x6EB3E453, 36, // 37
+ 0xD79435E5, 0xD79435E5, 37, // 38
+ 0x69069069, 0x69069069, 36, // 39
+ 0xCCCCCCCD, 0x00000000, 37, // 40
+ 0xC7CE0C7D, 0x00000000, 37, // 41
+ 0xC30C30C3, 0xC30C30C3, 37, // 42
+ 0x2FA0BE83, 0x00000000, 35, // 43
+ 0xBA2E8BA3, 0x00000000, 37, // 44
+ 0x5B05B05B, 0x5B05B05B, 36, // 45
+ 0xB21642C9, 0x00000000, 37, // 46
+ 0xAE4C415D, 0x00000000, 37, // 47
+ 0xAAAAAAAB, 0x00000000, 37, // 48
+ 0x5397829D, 0x00000000, 36, // 49
+ 0x51EB851F, 0x00000000, 36, // 50
+ 0xA0A0A0A1, 0x00000000, 37, // 51
+ 0x4EC4EC4F, 0x00000000, 36, // 52
+ 0x9A90E7D9, 0x9A90E7D9, 37, // 53
+ 0x97B425ED, 0x97B425ED, 37, // 54
+ 0x94F2094F, 0x94F2094F, 37, // 55
+ 0x49249249, 0x49249249, 36, // 56
+ 0x47DC11F7, 0x47DC11F7, 36, // 57
+ 0x8D3DCB09, 0x00000000, 37, // 58
+ 0x22B63CBF, 0x00000000, 35, // 59
+ 0x88888889, 0x00000000, 37, // 60
+ 0x4325C53F, 0x00000000, 36, // 61
+ 0x42108421, 0x42108421, 36, // 62
+ 0x41041041, 0x41041041, 36, // 63
+ 0x00000001, 0x00000000, 6, // 64
+ 0xFC0FC0FD, 0x00000000, 38, // 65
+ 0x3E0F83E1, 0x00000000, 36, // 66
+ 0x07A44C6B, 0x00000000, 33, // 67
+ 0xF0F0F0F1, 0x00000000, 38, // 68
+ 0x76B981DB, 0x00000000, 37, // 69
+ 0x75075075, 0x75075075, 37, // 70
+ 0xE6C2B449, 0x00000000, 38, // 71
+ 0x38E38E39, 0x00000000, 36, // 72
+ 0x381C0E07, 0x381C0E07, 36, // 73
+ 0x6EB3E453, 0x6EB3E453, 37, // 74
+ 0x1B4E81B5, 0x00000000, 35, // 75
+ 0xD79435E5, 0xD79435E5, 38, // 76
+ 0x3531DEC1, 0x00000000, 36, // 77
+ 0x69069069, 0x69069069, 37, // 78
+ 0xCF6474A9, 0x00000000, 38, // 79
+ 0xCCCCCCCD, 0x00000000, 38, // 80
+ 0xCA4587E7, 0x00000000, 38, // 81
+ 0xC7CE0C7D, 0x00000000, 38, // 82
+ 0x3159721F, 0x00000000, 36, // 83
+ 0xC30C30C3, 0xC30C30C3, 38, // 84
+ 0xC0C0C0C1, 0x00000000, 38, // 85
+ 0x2FA0BE83, 0x00000000, 36, // 86
+ 0x2F149903, 0x00000000, 36, // 87
+ 0xBA2E8BA3, 0x00000000, 38, // 88
+ 0xB81702E1, 0x00000000, 38, // 89
+ 0x5B05B05B, 0x5B05B05B, 37, // 90
+ 0x2D02D02D, 0x2D02D02D, 36, // 91
+ 0xB21642C9, 0x00000000, 38, // 92
+ 0xB02C0B03, 0x00000000, 38, // 93
+ 0xAE4C415D, 0x00000000, 38, // 94
+ 0x2B1DA461, 0x2B1DA461, 36, // 95
+ 0xAAAAAAAB, 0x00000000, 38, // 96
+ 0xA8E83F57, 0xA8E83F57, 38, // 97
+ 0x5397829D, 0x00000000, 37, // 98
+ 0xA57EB503, 0x00000000, 38, // 99
+ 0x51EB851F, 0x00000000, 37, // 100
+ 0xA237C32B, 0xA237C32B, 38, // 101
+ 0xA0A0A0A1, 0x00000000, 38, // 102
+ 0x9F1165E7, 0x9F1165E7, 38, // 103
+ 0x4EC4EC4F, 0x00000000, 37, // 104
+ 0x27027027, 0x27027027, 36, // 105
+ 0x9A90E7D9, 0x9A90E7D9, 38, // 106
+ 0x991F1A51, 0x991F1A51, 38, // 107
+ 0x97B425ED, 0x97B425ED, 38, // 108
+ 0x2593F69B, 0x2593F69B, 36, // 109
+ 0x94F2094F, 0x94F2094F, 38, // 110
+ 0x24E6A171, 0x24E6A171, 36, // 111
+ 0x49249249, 0x49249249, 37, // 112
+ 0x90FDBC09, 0x90FDBC09, 38, // 113
+ 0x47DC11F7, 0x47DC11F7, 37, // 114
+ 0x8E78356D, 0x8E78356D, 38, // 115
+ 0x8D3DCB09, 0x00000000, 38, // 116
+ 0x23023023, 0x23023023, 36, // 117
+ 0x22B63CBF, 0x00000000, 36, // 118
+ 0x44D72045, 0x00000000, 37, // 119
+ 0x88888889, 0x00000000, 38, // 120
+ 0x8767AB5F, 0x8767AB5F, 38, // 121
+ 0x4325C53F, 0x00000000, 37, // 122
+ 0x85340853, 0x85340853, 38, // 123
+ 0x42108421, 0x42108421, 37, // 124
+ 0x10624DD3, 0x00000000, 35, // 125
+ 0x41041041, 0x41041041, 37, // 126
+ 0x10204081, 0x10204081, 35, // 127
+ 0x00000001, 0x00000000, 7, // 128
+ 0x0FE03F81, 0x00000000, 35, // 129
+ 0xFC0FC0FD, 0x00000000, 39, // 130
+ 0xFA232CF3, 0x00000000, 39, // 131
+ 0x3E0F83E1, 0x00000000, 37, // 132
+ 0xF6603D99, 0x00000000, 39, // 133
+ 0x07A44C6B, 0x00000000, 34, // 134
+ 0xF2B9D649, 0x00000000, 39, // 135
+ 0xF0F0F0F1, 0x00000000, 39, // 136
+ 0x077975B9, 0x00000000, 34, // 137
+ 0x76B981DB, 0x00000000, 38, // 138
+ 0x75DED953, 0x00000000, 38, // 139
+ 0x75075075, 0x75075075, 38, // 140
+ 0x3A196B1F, 0x00000000, 37, // 141
+ 0xE6C2B449, 0x00000000, 39, // 142
+ 0xE525982B, 0x00000000, 39, // 143
+ 0x38E38E39, 0x00000000, 37, // 144
+ 0xE1FC780F, 0x00000000, 39, // 145
+ 0x381C0E07, 0x381C0E07, 37, // 146
+ 0xDEE95C4D, 0x00000000, 39, // 147
+ 0x6EB3E453, 0x6EB3E453, 38, // 148
+ 0xDBEB61EF, 0x00000000, 39, // 149
+ 0x1B4E81B5, 0x00000000, 36, // 150
+ 0x36406C81, 0x00000000, 37, // 151
+ 0xD79435E5, 0xD79435E5, 39, // 152
+ 0xD62B80D7, 0x00000000, 39, // 153
+ 0x3531DEC1, 0x00000000, 37, // 154
+ 0xD3680D37, 0x00000000, 39, // 155
+ 0x69069069, 0x69069069, 38, // 156
+ 0x342DA7F3, 0x00000000, 37, // 157
+ 0xCF6474A9, 0x00000000, 39, // 158
+ 0xCE168A77, 0xCE168A77, 39, // 159
+ 0xCCCCCCCD, 0x00000000, 39, // 160
+ 0xCB8727C1, 0x00000000, 39, // 161
+ 0xCA4587E7, 0x00000000, 39, // 162
+ 0xC907DA4F, 0x00000000, 39, // 163
+ 0xC7CE0C7D, 0x00000000, 39, // 164
+ 0x634C0635, 0x00000000, 38, // 165
+ 0x3159721F, 0x00000000, 37, // 166
+ 0x621B97C3, 0x00000000, 38, // 167
+ 0xC30C30C3, 0xC30C30C3, 39, // 168
+ 0x60F25DEB, 0x00000000, 38, // 169
+ 0xC0C0C0C1, 0x00000000, 39, // 170
+ 0x17F405FD, 0x17F405FD, 36, // 171
+ 0x2FA0BE83, 0x00000000, 37, // 172
+ 0xBD691047, 0xBD691047, 39, // 173
+ 0x2F149903, 0x00000000, 37, // 174
+ 0x5D9F7391, 0x00000000, 38, // 175
+ 0xBA2E8BA3, 0x00000000, 39, // 176
+ 0x5C90A1FD, 0x5C90A1FD, 38, // 177
+ 0xB81702E1, 0x00000000, 39, // 178
+ 0x5B87DDAD, 0x5B87DDAD, 38, // 179
+ 0x5B05B05B, 0x5B05B05B, 38, // 180
+ 0xB509E68B, 0x00000000, 39, // 181
+ 0x2D02D02D, 0x2D02D02D, 37, // 182
+ 0xB30F6353, 0x00000000, 39, // 183
+ 0xB21642C9, 0x00000000, 39, // 184
+ 0x1623FA77, 0x1623FA77, 36, // 185
+ 0xB02C0B03, 0x00000000, 39, // 186
+ 0xAF3ADDC7, 0x00000000, 39, // 187
+ 0xAE4C415D, 0x00000000, 39, // 188
+ 0x15AC056B, 0x15AC056B, 36, // 189
+ 0x2B1DA461, 0x2B1DA461, 37, // 190
+ 0xAB8F69E3, 0x00000000, 39, // 191
+ 0xAAAAAAAB, 0x00000000, 39, // 192
+ 0x15390949, 0x00000000, 36, // 193
+ 0xA8E83F57, 0xA8E83F57, 39, // 194
+ 0x15015015, 0x15015015, 36, // 195
+ 0x5397829D, 0x00000000, 38, // 196
+ 0xA655C439, 0xA655C439, 39, // 197
+ 0xA57EB503, 0x00000000, 39, // 198
+ 0x5254E78F, 0x00000000, 38, // 199
+ 0x51EB851F, 0x00000000, 38, // 200
+ 0x028C1979, 0x00000000, 33, // 201
+ 0xA237C32B, 0xA237C32B, 39, // 202
+ 0xA16B312F, 0x00000000, 39, // 203
+ 0xA0A0A0A1, 0x00000000, 39, // 204
+ 0x4FEC04FF, 0x00000000, 38, // 205
+ 0x9F1165E7, 0x9F1165E7, 39, // 206
+ 0x27932B49, 0x00000000, 37, // 207
+ 0x4EC4EC4F, 0x00000000, 38, // 208
+ 0x9CC8E161, 0x00000000, 39, // 209
+ 0x27027027, 0x27027027, 37, // 210
+ 0x9B4C6F9F, 0x00000000, 39, // 211
+ 0x9A90E7D9, 0x9A90E7D9, 39, // 212
+ 0x99D722DB, 0x00000000, 39, // 213
+ 0x991F1A51, 0x991F1A51, 39, // 214
+ 0x4C346405, 0x00000000, 38, // 215
+ 0x97B425ED, 0x97B425ED, 39, // 216
+ 0x4B809701, 0x4B809701, 38, // 217
+ 0x2593F69B, 0x2593F69B, 37, // 218
+ 0x12B404AD, 0x12B404AD, 36, // 219
+ 0x94F2094F, 0x94F2094F, 39, // 220
+ 0x25116025, 0x25116025, 37, // 221
+ 0x24E6A171, 0x24E6A171, 37, // 222
+ 0x24BC44E1, 0x24BC44E1, 37, // 223
+ 0x49249249, 0x49249249, 38, // 224
+ 0x91A2B3C5, 0x00000000, 39, // 225
+ 0x90FDBC09, 0x90FDBC09, 39, // 226
+ 0x905A3863, 0x905A3863, 39, // 227
+ 0x47DC11F7, 0x47DC11F7, 38, // 228
+ 0x478BBCED, 0x00000000, 38, // 229
+ 0x8E78356D, 0x8E78356D, 39, // 230
+ 0x46ED2901, 0x46ED2901, 38, // 231
+ 0x8D3DCB09, 0x00000000, 39, // 232
+ 0x2328A701, 0x2328A701, 37, // 233
+ 0x23023023, 0x23023023, 37, // 234
+ 0x45B81A25, 0x45B81A25, 38, // 235
+ 0x22B63CBF, 0x00000000, 37, // 236
+ 0x08A42F87, 0x08A42F87, 35, // 237
+ 0x44D72045, 0x00000000, 38, // 238
+ 0x891AC73B, 0x00000000, 39, // 239
+ 0x88888889, 0x00000000, 39, // 240
+ 0x10FEF011, 0x00000000, 36, // 241
+ 0x8767AB5F, 0x8767AB5F, 39, // 242
+ 0x86D90545, 0x00000000, 39, // 243
+ 0x4325C53F, 0x00000000, 38, // 244
+ 0x85BF3761, 0x85BF3761, 39, // 245
+ 0x85340853, 0x85340853, 39, // 246
+ 0x10953F39, 0x10953F39, 36, // 247
+ 0x42108421, 0x42108421, 38, // 248
+ 0x41CC9829, 0x41CC9829, 38, // 249
+ 0x10624DD3, 0x00000000, 36, // 250
+ 0x828CBFBF, 0x00000000, 39, // 251
+ 0x41041041, 0x41041041, 38, // 252
+ 0x81848DA9, 0x00000000, 39, // 253
+ 0x10204081, 0x10204081, 36, // 254
+ 0x80808081, 0x00000000, 39 // 255
+ };
+ }
+}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs
index 2515815cb..915147d2a 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
@@ -9,25 +10,66 @@ using Tango.RemoteDesktop.Utils;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents a generic screen capture engine.
+ /// The <see cref="TFrame"/> type will be the type of difference object delivered by the engine.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ /// <seealso cref="Tango.RemoteDesktop.IScreenCaptureEngine{TFrame}" />
public class ScreenCaptureEngine<TFrame> : IScreenCaptureEngine<TFrame> where TFrame : class, IFrame
{
private Thread _captureThread;
private Bitmap _previousBitmap;
private bool _isDisposed;
+ /// <summary>
+ /// Occurs when a new screen frame is available.
+ /// </summary>
public event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived;
+ /// <summary>
+ /// Gets or sets the screen capture method.
+ /// </summary>
public ICaptureMethod CaptureMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the screen capture region.
+ /// </summary>
public CaptureRegion CaptureRegion { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is currently capturing.
+ /// </summary>
public bool IsStarted { get; private set; }
- public TimeSpan Interval { get; set; }
+
+ /// <summary>
+ /// Gets or sets the frame rate per second.
+ /// Default is 10.
+ /// </summary>
+ public int FrameRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to include the cursor when capturing.
+ /// </summary>
public bool CaptureCursor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bitmap comparer.
+ /// </summary>
public IBitmapComparer<TFrame> Comparer { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable image comparison.
+ /// </summary>
public bool EnableComparer { get; set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScreenCaptureEngine{TFrame}"/> class using the specified <see cref="IBitmapComparer{TFrame}"/>.
+ /// </summary>
+ /// <param name="comparer">The comparer.</param>
public ScreenCaptureEngine(IBitmapComparer<TFrame> comparer)
{
- Interval = TimeSpan.FromMilliseconds(100);
+ FrameRate = 10;
CaptureMethod = new CaptureMethods.DirectXScreenCapture();
CaptureRegion = new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds);
Comparer = comparer;
@@ -39,6 +81,10 @@ namespace Tango.RemoteDesktop
}
}
+ /// <summary>
+ /// Start capturing.
+ /// </summary>
+ /// <exception cref="ObjectDisposedException">Screen capture engine cannot be started after disposed.</exception>
public void Start()
{
if (_isDisposed)
@@ -57,10 +103,39 @@ namespace Tango.RemoteDesktop
}
}
+ /// <summary>
+ /// Stop capturing.
+ /// </summary>
+ public void Stop()
+ {
+ if (IsStarted)
+ {
+ IsStarted = false;
+ _previousBitmap?.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public void Dispose()
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+ Stop();
+ CaptureMethod?.Dispose();
+ }
+ }
+
private void CaptureThreadMethod()
{
+ Stopwatch watch = new Stopwatch();
+
while (IsStarted)
{
+ watch.Restart();
+
var bitmap = CaptureMethod.GetDesktopBitmap(CaptureRegion);
if (CaptureCursor)
@@ -76,48 +151,34 @@ namespace Tango.RemoteDesktop
if (_previousBitmap == null)
{
_previousBitmap = bitmap.Clone() as Bitmap;
- OnFrameReceived(bitmap, null);
+ OnFrameReceived(bitmap, null, false);
}
else
{
- var diffFrame = Comparer.CreateDifference(_previousBitmap, bitmap);
+ var result = Comparer.CreateDifference(_previousBitmap, bitmap);
_previousBitmap.Dispose();
_previousBitmap = bitmap.Clone() as Bitmap;
- OnFrameReceived(bitmap, diffFrame);
+ OnFrameReceived(bitmap, result.Frame, result.ContainsDifference);
}
}
else
{
- OnFrameReceived(bitmap, null);
+ OnFrameReceived(bitmap, null, false);
}
- Thread.Sleep(Interval);
- }
- }
-
- public void Stop()
- {
- if (IsStarted)
- {
- IsStarted = false;
+ int delay = Math.Max(5, (FrameRate * 10) - (int)watch.ElapsedMilliseconds);
+ Thread.Sleep(delay);
}
}
- public void Dispose()
- {
- if (!_isDisposed)
- {
- _isDisposed = true;
- Stop();
- CaptureMethod?.Dispose();
- }
- }
-
- private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame)
+ private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame, bool hasDifference)
{
FrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs<TFrame>()
{
Frame = new ScreenCaptureFrame<TFrame>(currentBitmap, diffFrame)
+ {
+ HasDifference = hasDifference
+ }
});
}
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs
index b45a9c9bc..75e7a961f 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs
@@ -10,29 +10,74 @@ using Tango.RemoteDesktop.Frames;
namespace Tango.RemoteDesktop
{
- public class ScreenCaptureFrame<T> : RasterFrame, IDisposable where T : IFrame
+ /// <summary>
+ /// Represents a screen capture frame for holding the original bitmap and difference frame.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ /// <seealso cref="Tango.RemoteDesktop.Frames.RasterFrame" />
+ /// <seealso cref="System.IDisposable" />
+ public class ScreenCaptureFrame<TFrame> : RasterFrame, IDisposable where TFrame : IFrame
{
- private T _diffFrame;
+ private TFrame _diffFrame;
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScreenCaptureFrame{TFrame}"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
public ScreenCaptureFrame(Bitmap bitmap) : base(bitmap)
{
+
}
- public ScreenCaptureFrame(Bitmap bitmap, T diffFrame) : this(bitmap)
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScreenCaptureFrame{TFrame}"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="diffFrame">The difference frame.</param>
+ public ScreenCaptureFrame(Bitmap bitmap, TFrame diffFrame) : this(bitmap)
{
_diffFrame = diffFrame;
}
+ /// <summary>
+ /// Gets a value indicating whether a difference frame is available.
+ /// </summary>
public bool DifferenceAvailable
{
get { return _diffFrame != null; }
}
- public T ToDifference()
+ /// <summary>
+ /// Gets or sets a value indicating whether the difference frame is available and contains any differences.
+ /// </summary>
+ private bool _hasDifference;
+ public bool HasDifference
{
+ get
+ {
+ return DifferenceAvailable && _hasDifference;
+ }
+ set { _hasDifference = value; }
+ }
+
+ /// <summary>
+ /// Returns the difference frame.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException">No difference is available at this point. Please use the 'DifferenceAvailable' property before attempting to get the difference.</exception>
+ public TFrame ToDifference()
+ {
+ if (!DifferenceAvailable)
+ {
+ throw new InvalidOperationException("No difference is available at this point. Please use the 'DifferenceAvailable' property before attempting to get the difference.");
+ }
+
return _diffFrame;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public override void Dispose()
{
base.Dispose();
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs
index 5732744a4..df53d6db9 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs
@@ -6,8 +6,16 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop
{
+ /// <summary>
+ /// Represents the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event arguments.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <seealso cref="System.EventArgs" />
public class ScreenCaptureFrameReceivedEventArgs<T> : EventArgs where T: IFrame
{
+ /// <summary>
+ /// Gets or sets the screen capture frame containing the original bitmap and difference frame.
+ /// </summary>
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
index 35f797626..f760b43dc 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj
@@ -61,14 +61,26 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
+ <Compile Include="BitmapComparerResult.cs" />
<Compile Include="CaptureRegion.cs" />
<Compile Include="CaptureMethods\BitBltScreenCapture.cs" />
<Compile Include="CaptureMethods\DirectXScreenCapture.cs" />
<Compile Include="CaptureMethods\GdiScreenCapture.cs" />
+ <Compile Include="Clipping\ClipResult.cs" />
+ <Compile Include="Clipping\BitmapCliper.cs" />
<Compile Include="Comparers\VectorBitmapComparer.cs" />
+ <Compile Include="Encoders\GifEncoder.cs" />
+ <Compile Include="Encoders\Png8BitEncoder.cs" />
<Compile Include="Engines\RasterScreenCaptureEngine.cs" />
<Compile Include="Engines\VectorScreenCaptureEngine.cs" />
+ <Compile Include="Frames\VectorFrameColor.cs" />
<Compile Include="IScreenCaptureEngine.cs" />
+ <Compile Include="Quantization\ColorBgra.cs" />
+ <Compile Include="Quantization\OctreeQuantizer.cs" />
+ <Compile Include="Quantization\PaletteTable.cs" />
+ <Compile Include="Quantization\Quantizer.cs" />
+ <Compile Include="Quantization\Scanline.cs" />
+ <Compile Include="Quantization\Utility.cs" />
<Compile Include="ScreenCaptureEngine.cs" />
<Compile Include="ScreenCaptureFrameReceivedEventArgs.cs" />
<Compile Include="Utils\CursorUtils.cs" />
@@ -90,6 +102,5 @@
<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
index 11bd4f6ff..9dad26984 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs
@@ -8,6 +8,9 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.Utils
{
+ /// <summary>
+ /// Represents a static cursor helper.
+ /// </summary>
public static class CursorUtils
{
#region INTERNALS
@@ -204,9 +207,16 @@ namespace Tango.RemoteDesktop.Utils
#endregion
+ /// <summary>
+ /// Applies the current user cursor icon at the specified position on the specified bitmap.
+ /// </summary>
+ /// <param name="graphics">The drawing graphics object.</param>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="left">The left cursor position.</param>
+ /// <param name="top">The top cursor position.</param>
[DebuggerHidden]
[DebuggerStepThrough]
- public static void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top)
+ public static void ApplyCursor(System.Drawing.Graphics graphics, System.Drawing.Bitmap bitmap, int left, int top)
{
try
{
@@ -221,14 +231,14 @@ namespace Tango.RemoteDesktop.Utils
//User32.ICONINFO iconInfo;
//User32.GetIconInfo(iconPointer, out iconInfo);
- var hdc = g.GetHdc();
+ var hdc = graphics.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();
+ graphics.ReleaseHdc();
}
catch { }
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs
index fbcc5710c..83b36fcfa 100644
--- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs
@@ -9,6 +9,10 @@ using System.Threading.Tasks;
namespace Tango.RemoteDesktop.Utils
{
+ /// <summary>
+ /// Represents a bitmap object with fast Get/Set Pixel methods.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
public class DirectBitmap : IDisposable
{
public Bitmap Bitmap { get; private set; }
@@ -19,6 +23,11 @@ namespace Tango.RemoteDesktop.Utils
protected GCHandle BitsHandle { get; private set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DirectBitmap"/> class.
+ /// </summary>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
public DirectBitmap(int width, int height)
{
Width = width;
@@ -28,14 +37,26 @@ namespace Tango.RemoteDesktop.Utils
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
}
- public void SetPixel(int x, int y, Color colour)
+ /// <summary>
+ /// Sets the pixel color.
+ /// </summary>
+ /// <param name="x">The x position.</param>
+ /// <param name="y">The y position.</param>
+ /// <param name="color">The color.</param>
+ public void SetPixel(int x, int y, Color color)
{
int index = x + (y * Width);
- int col = colour.ToArgb();
+ int col = color.ToArgb();
Bits[index] = col;
}
+ /// <summary>
+ /// Gets the pixel color.
+ /// </summary>
+ /// <param name="x">The x position.</param>
+ /// <param name="y">The y position.</param>
+ /// <returns></returns>
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
@@ -45,6 +66,9 @@ namespace Tango.RemoteDesktop.Utils
return result;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public void Dispose()
{
if (Disposed) return;
diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml
index 2a1c3a498..ab9249042 100644
--- a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml
+++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml
@@ -7,6 +7,6 @@
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
- <Image Stretch="Fill" x:Name="img" />
+ <Image Stretch="Fill" x:Name="img" RenderOptions.BitmapScalingMode="Fant" />
</Grid>
</Window>
diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs
index 85e5ca0bd..fd0df286c 100644
--- a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs
@@ -34,6 +34,7 @@ namespace WpfApp1
public MainWindow()
{
+
InitializeComponent();
ContentRendered += MainWindow_ContentRendered;
@@ -41,7 +42,7 @@ namespace WpfApp1
{
CaptureRegion = new CaptureRegion(0, 0, 1280, 800)
};
- _engine.Interval = TimeSpan.FromMilliseconds(100);
+ _engine.FrameRate = 5; //Per second
_engine.CaptureCursor = true;
_engine.FrameReceived += _engine_FrameReceived;
}
@@ -54,28 +55,49 @@ namespace WpfApp1
if (e.Frame.DifferenceAvailable)
{
- var diffFrame = e.Frame.ToDifference();
+ if (e.Frame.HasDifference)
+ {
+ var diffFrame = e.Frame.ToDifference();
+
+ var bitmapStream = diffFrame.OptimizeBounds().ToEncoder<PngEncoder>().ToStream();
+
+ size = (int)bitmapStream.Length;
+
+ Bitmap newBitmap = new Bitmap(bitmapStream);
+
+ diffFrame = new RasterFrame(newBitmap, diffFrame.Left, diffFrame.Top);
- size = diffFrame.Encode<PngEncoder>().ToArray().Length;
+ diffFrame.Apply(_currentBitmap);
- //diffFrame.Apply(_currentBitmap);
+ diffFrame.Dispose();
+ bitmapStream.Dispose();
- //var updatedFrame = new RasterFrame(_currentBitmap);
- //preview = updatedFrame.ToBitmapSource();
+ var updatedFrame = new RasterFrame(_currentBitmap);
+ preview = updatedFrame.ToBitmapSource();
+
+ Debug.WriteLine($"Actual frame size on network: {size / 1000} kb");
+
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ img.Source = preview;
+ }));
+ }
+ else
+ {
+ //No need to send new frames (no differences)...
+ }
}
else
{
_currentBitmap = e.Frame.ToBitmap().Clone() as Bitmap;
preview = e.Frame.ToBitmapSource();
- size = e.Frame.Encode<PngEncoder>().ToArray().Length;
- }
+ size = e.Frame.ToEncoder<Png8BitEncoder>().ToArray().Length;
- //Debug.WriteLine($"Actual frame size on network: {size / 1000} kb");
-
- //Dispatcher.BeginInvoke(new Action(() =>
- //{
- // img.Source = preview;
- //}));
+ Dispatcher.BeginInvoke(new Action(() =>
+ {
+ img.Source = preview;
+ }));
+ }
e.Frame.Dispose();
}
diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs
index 0a0f7ff57..f7673f0ab 100644
--- a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs
+++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs
@@ -30,6 +30,7 @@ namespace WpfApp1
public MainWindow()
{
+
InitializeComponent();
ContentRendered += MainWindow_ContentRendered;
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/BitmapComparerResult.cs b/Software/Visual_Studio/Tango.RemoteDesktop/BitmapComparerResult.cs
new file mode 100644
index 000000000..66a2f8660
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/BitmapComparerResult.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents an <see cref="IBitmapComparer{TFrame}.CreateDifference(System.Drawing.Bitmap, System.Drawing.Bitmap)"/> result.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ public class BitmapComparerResult<TFrame> where TFrame : IFrame
+ {
+ /// <summary>
+ /// Gets or sets the difference frame.
+ /// </summary>
+ public TFrame Frame { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the <see cref="Frame"/> contains any differences.
+ /// </summary>
+ public bool ContainsDifference { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/BitBltScreenCapture.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
index bd2386387..d8f1e214c 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/BitBltScreenCapture.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
@@ -6,9 +6,13 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.CaptureMethods
{
- public class BitBltScreenCapture : IScreenCaptureMethod
+ /// <summary>
+ /// Represents a BitBlt screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
+ public class BitBltScreenCapture : ICaptureMethod
{
#region Win32 API Screen shot calls
@@ -25,6 +29,11 @@ namespace Tango.ScreenCapture
#endregion
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
public Bitmap GetDesktopBitmap(CaptureRegion region)
{
const int SRCCOPY = 13369376;
@@ -48,6 +57,9 @@ namespace Tango.ScreenCapture
return bitmap;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public void Dispose()
{
//Do nothing.
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/DirectXScreenCapture.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs
index d752932ec..1a661b27e 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/DirectXScreenCapture.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs
@@ -7,13 +7,19 @@ using System.Threading.Tasks;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
+using Tango.RemoteDesktop.Utils;
using Device = SharpDX.Direct3D11.Device;
using MapFlags = SharpDX.Direct3D11.MapFlags;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.CaptureMethods
{
- public class DirectXScreenCapture : IScreenCaptureMethod
+ /// <summary>
+ /// Represents a high performance DirectX screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
+ public class DirectXScreenCapture : ICaptureMethod
{
+ private static bool _hasInstance;
private Device device;
private Output1 output1;
private Texture2DDescription textureDesc;
@@ -21,12 +27,27 @@ namespace Tango.ScreenCapture
private int monitorWidth;
private int monitorHeight;
+ /// <summary>
+ /// Gets or sets the index of the graphics adapter.
+ /// </summary>
public int AdapterIndex { get; set; }
+ /// <summary>
+ /// Gets or sets the index of the monitor within the graphics adapter.
+ /// </summary>
public int MonitorIndex { get; set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DirectXScreenCapture"/> class.
+ /// </summary>
+ /// <exception cref="InvalidOperationException">An instance of the DirectX screen capture method already exists. Please dispose it before attempting to create a new one.</exception>
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);
@@ -59,8 +80,15 @@ namespace Tango.ScreenCapture
// Duplicate the output
duplicatedOutput = output1.DuplicateOutput(device);
+
+ _hasInstance = true;
}
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
public virtual Bitmap GetDesktopBitmap(CaptureRegion region)
{
var screenTexture = new Texture2D(device, textureDesc);
@@ -120,8 +148,12 @@ namespace Tango.ScreenCapture
return bmpImage.Clone(cropArea, bmpImage.PixelFormat);
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public void Dispose()
{
+ _hasInstance = false;
duplicatedOutput.Dispose();
device.Dispose();
}
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/GdiScreenCapture.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
index 51591ec12..92f4403fd 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/GdiScreenCapture.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
@@ -5,13 +5,22 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.CaptureMethods
{
- public class GdiScreenCapture : IScreenCaptureMethod
+ /// <summary>
+ /// Represents a standard GDI screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
+ public class GdiScreenCapture : ICaptureMethod
{
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
public Bitmap GetDesktopBitmap(CaptureRegion region)
{
- var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
+ var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(bitmap))
{
@@ -21,6 +30,9 @@ namespace Tango.ScreenCapture
return bitmap;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public void Dispose()
{
//Do nothing
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/CaptureRegion.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureRegion.cs
new file mode 100644
index 000000000..511a61e88
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureRegion.cs
@@ -0,0 +1,92 @@
+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
+{
+ /// <summary>
+ /// Represents a capture rectangle region.
+ /// </summary>
+ public class CaptureRegion
+ {
+ /// <summary>
+ /// Gets or sets the region left corner.
+ /// </summary>
+ public int Left { get; set; }
+
+ /// <summary>
+ /// Gets or sets the region top corner.
+ /// </summary>
+ public int Top { get; set; }
+
+ /// <summary>
+ /// Gets or sets the region width.
+ /// </summary>
+ public int Width { get; set; }
+
+ /// <summary>
+ /// Gets or sets the region height.
+ /// </summary>
+ public int Height { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class.
+ /// </summary>
+ public CaptureRegion()
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class using a <see cref="Rectangle"/> object.
+ /// </summary>
+ /// <param name="rect">The rectangle..</param>
+ public CaptureRegion(Rectangle rect)
+ {
+ Left = rect.Left;
+ Top = rect.Top;
+ Width = rect.Width;
+ Height = rect.Height;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class using a <see cref="Rect"/> object.
+ /// </summary>
+ /// <param name="rect">The rectangle.</param>
+ public CaptureRegion(Rect rect)
+ {
+ Left = (int)rect.Left;
+ Top = (int)rect.Top;
+ Width = (int)rect.Width;
+ Height = (int)rect.Height;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CaptureRegion"/> class.
+ /// </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>
+ public CaptureRegion(int left, int top, int width, int height)
+ {
+ Left = left;
+ Top = top;
+ Width = width;
+ Height = height;
+ }
+
+ /// <summary>
+ /// Creates a capture region from by the bounds of the primary screen.
+ /// </summary>
+ /// <returns></returns>
+ public static CaptureRegion PrimaryScreenBounds()
+ {
+ return new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Clipping/BitmapCliper.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Clipping/BitmapCliper.cs
new file mode 100644
index 000000000..4f93eeb66
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Clipping/BitmapCliper.cs
@@ -0,0 +1,144 @@
+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.Clipping
+{
+ public class BitmapCliper
+ {
+ public static ClipResult ClipBitmap(Bitmap img)
+ {
+ //get image data
+ BitmapData bd = img.LockBits(new Rectangle(Point.Empty, img.Size),
+ ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+ int[] rgbValues = new int[img.Height * img.Width];
+ Marshal.Copy(bd.Scan0, rgbValues, 0, rgbValues.Length);
+ img.UnlockBits(bd);
+
+
+ #region determine bounds
+ int left = bd.Width;
+ int top = bd.Height;
+ int right = 0;
+ int bottom = 0;
+
+ //determine top
+ for (int i = 0; i < rgbValues.Length; i++)
+ {
+ int color = rgbValues[i] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ int r = i / bd.Width;
+ int c = i % bd.Width;
+
+ if (left > c)
+ {
+ left = c;
+ }
+ if (right < c)
+ {
+ right = c;
+ }
+ bottom = r;
+ top = r;
+ break;
+ }
+ }
+
+ //determine bottom
+ for (int i = rgbValues.Length - 1; i >= 0; i--)
+ {
+ int color = rgbValues[i] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ int r = i / bd.Width;
+ int c = i % bd.Width;
+
+ if (left > c)
+ {
+ left = c;
+ }
+ if (right < c)
+ {
+ right = c;
+ }
+ bottom = r;
+ break;
+ }
+ }
+
+ if (bottom > top)
+ {
+ for (int r = top + 1; r < bottom; r++)
+ {
+ //determine left
+ for (int c = 0; c < left; c++)
+ {
+ int color = rgbValues[r * bd.Width + c] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ if (left > c)
+ {
+ left = c;
+ break;
+ }
+ }
+ }
+
+ //determine right
+ for (int c = bd.Width - 1; c > right; c--)
+ {
+ int color = rgbValues[r * bd.Width + c] & 0x00ffffff;
+ if (color != 0x00ffffff)
+ {
+ if (right < c)
+ {
+ right = c;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ int width = right - left + 1;
+ int height = bottom - top + 1;
+ #endregion
+
+ if (width < 0 || height < 0)
+ {
+ return new ClipResult()
+ {
+ Bitmap = new Bitmap(1, 1),
+ Bounds = new Rectangle(0, 0, 1, 1)
+ };
+ }
+
+ //copy image data
+ int[] imgData = new int[width * height];
+ for (int r = top; r <= bottom; r++)
+ {
+ Array.Copy(rgbValues, r * bd.Width + left, imgData, (r - top) * width, width);
+ }
+
+ //create new image
+ Bitmap newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+ BitmapData nbd
+ = newImage.LockBits(new Rectangle(0, 0, width, height),
+ ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
+ Marshal.Copy(imgData, 0, nbd.Scan0, imgData.Length);
+ newImage.UnlockBits(nbd);
+
+ return new ClipResult()
+ {
+ Bitmap = newImage,
+ Bounds = new Rectangle(left, top, width, height)
+ };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Clipping/ClipResult.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Clipping/ClipResult.cs
new file mode 100644
index 000000000..bd0a52679
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Clipping/ClipResult.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Clipping
+{
+ public class ClipResult
+ {
+ public Bitmap Bitmap { get; set; }
+ public Rectangle Bounds { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs
new file mode 100644
index 000000000..34250cc59
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs
@@ -0,0 +1,120 @@
+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
+{
+ /// <summary>
+ /// Represents a raster <see cref="IBitmapComparer{TFrame}"/> comparer which will compare and return a <see cref="RasterFrame"/> difference.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IBitmapComparer{Tango.RemoteDesktop.Frames.RasterFrame}" />
+ public class RasterBitmapComparer : IBitmapComparer<RasterFrame>
+ {
+ /// <summary>
+ /// Creates the difference as <see cref="RasterFrame"/>.
+ /// </summary>
+ /// <param name="previousBitmap">The previous bitmap.</param>
+ /// <param name="currentBitmap">The current bitmap.</param>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException">
+ /// Cannot compare image. They are the same instance
+ /// or
+ /// Cannot compare image of different size.
+ /// </exception>
+ unsafe public BitmapComparerResult<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.");
+
+ bool hasDifference = false;
+
+ 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
+ }
+
+ if (same != 4)
+ {
+ hasDifference = true;
+ }
+ }
+
+ // 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 BitmapComparerResult<RasterFrame>()
+ {
+ Frame = new RasterFrame(diffImage),
+ ContainsDifference = hasDifference
+ };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs
new file mode 100644
index 000000000..2500e8ac7
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media.Imaging;
+using Tango.RemoteDesktop.Frames;
+
+namespace Tango.RemoteDesktop.Comparers
+{
+ /// <summary>
+ /// Represents a vector <see cref="IBitmapComparer{TFrame}"/> comparer which will compare and return a <see cref="VectorFrame"/> difference.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IBitmapComparer{Tango.RemoteDesktop.Frames.VectorFrame}" />
+ public class VectorBitmapComparer : IBitmapComparer<VectorFrame>
+ {
+ /// <summary>
+ /// Creates the difference as <see cref="VectorFrame"/>.
+ /// </summary>
+ /// <param name="previousBitmap">The previous bitmap.</param>
+ /// <param name="currentBitmap">The current bitmap.</param>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException">
+ /// Cannot compare image. They are the same instance
+ /// or
+ /// Cannot compare image of different size.
+ /// </exception>
+ public unsafe BitmapComparerResult<VectorFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap)
+ {
+ VectorFrame vector = new VectorFrame(previousBitmap.Width, previousBitmap.Height);
+
+ bool hasDifference = false;
+
+ 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)
+ {
+ int colorIndex = vector.Colors.Count;
+
+ VectorFrameColor color = new VectorFrameColor()
+ {
+ B = tmp[0],
+ G = tmp[1],
+ R = tmp[2],
+ Index = colorIndex
+ };
+
+ VectorFrameColor existingColor;
+
+ if (vector.Colors.TryGetValue(color.ToInt32(), out existingColor))
+ {
+ colorIndex = existingColor.Index;
+ }
+ else
+ {
+ vector.Colors.Add(color.ToInt32(), color);
+ }
+
+ vector.Pixels.Add(new VectorFramePixel()
+ {
+ X = j,
+ Y = i,
+ ColorIndex = colorIndex,
+ Color = color
+ });
+
+ hasDifference = true;
+ }
+ }
+
+ // at the end of each column, skip extra padding
+ if (rowPadding > 0)
+ {
+ data1Ptr += rowPadding;
+ data2Ptr += rowPadding;
+ }
+ }
+
+ previousBitmap.UnlockBits(data1);
+ currentBitmap.UnlockBits(data2);
+
+ return new BitmapComparerResult<VectorFrame>()
+ {
+ Frame = vector,
+ ContainsDifference = hasDifference
+ };
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs
new file mode 100644
index 000000000..18d144518
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs
@@ -0,0 +1,36 @@
+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
+{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> BMP encoder.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" />
+ public class BitmapEncoder : FrameEncoder
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="BitmapEncoder"/> class.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
+ public BitmapEncoder(IFrame frame) : base(frame)
+ {
+ }
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ public override MemoryStream ToStream()
+ {
+ MemoryStream ms = new MemoryStream();
+ Frame.ToBitmap().Save(ms, ImageFormat.Bmp);
+ return ms;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/GifEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/GifEncoder.cs
new file mode 100644
index 000000000..abf69d9a8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/GifEncoder.cs
@@ -0,0 +1,101 @@
+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;
+
+namespace Tango.RemoteDesktop.Encoders
+{
+ public class GifEncoder : JpegEncoder
+ {
+ public GifEncoder(IFrame frame) : base(frame)
+ {
+
+ }
+
+ public override MemoryStream ToStream()
+ {
+ return ToStream(100);
+ }
+
+ public override MemoryStream ToStream(long quality)
+ {
+ MemoryStream ms = new MemoryStream();
+ var gif = MakeTransparentGif(Frame.ToBitmap(), Color.Transparent);
+ gif.Save(ms, ImageFormat.Gif);
+ gif.Dispose();
+ ms.Position = 0;
+ return ms;
+ }
+
+ public static Bitmap MakeTransparentGif(Bitmap bitmap, Color color)
+ {
+ byte R = color.R;
+ byte G = color.G;
+ byte B = color.B;
+ MemoryStream fin = new MemoryStream();
+ bitmap.Save(fin, System.Drawing.Imaging.ImageFormat.Gif);
+ MemoryStream fout = new MemoryStream((int)fin.Length);
+ int count = 0;
+ byte[] buf = new byte[256];
+ byte transparentIdx = 0;
+ fin.Seek(0, SeekOrigin.Begin);
+ //header
+ count = fin.Read(buf, 0, 13);
+ if ((buf[0] != 71) || (buf[1] != 73) || (buf[2] != 70)) return null; //GIF
+ fout.Write(buf, 0, 13);
+ int i = 0;
+ if ((buf[10] & 0x80) > 0)
+ {
+ i = 1 << ((buf[10] & 7) + 1) == 256 ? 256 : 0;
+ }
+ for (; i != 0; i--)
+ {
+ fin.Read(buf, 0, 3);
+ if ((buf[0] == R) && (buf[1] == G) && (buf[2] == B))
+ {
+ transparentIdx = (byte)(256 - i);
+ }
+ fout.Write(buf, 0, 3);
+ }
+ bool gcePresent = false;
+ while (true)
+ {
+ fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ if (buf[0] != 0x21) break;
+ fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ gcePresent = (buf[0] == 0xf9);
+ while (true)
+ {
+ fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ if (buf[0] == 0) break;
+ count = buf[0];
+ if (fin.Read(buf, 0, count) != count) return null;
+ if (gcePresent)
+ {
+ if (count == 4)
+ {
+ buf[0] |= 0x01;
+ buf[3] = transparentIdx;
+ }
+ }
+ fout.Write(buf, 0, count);
+ }
+ }
+ while (count > 0)
+ {
+ count = fin.Read(buf, 0, 1);
+ fout.Write(buf, 0, 1);
+ }
+ fin.Close();
+ fout.Flush();
+ return new Bitmap(fout);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/JpegEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/JpegEncoder.cs
new file mode 100644
index 000000000..744849977
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/JpegEncoder.cs
@@ -0,0 +1,68 @@
+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
+{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> JPEG encoder.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" />
+ public class JpegEncoder : FrameEncoder
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="JpegEncoder"/> class.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
+ public JpegEncoder(IFrame frame) : base(frame)
+ {
+ }
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ public override MemoryStream ToStream()
+ {
+ return ToStream(100);
+ }
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame with the specified quality.
+ /// </summary>
+ /// <param name="quality">The quality.</param>
+ /// <returns></returns>
+ public virtual 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;
+ }
+
+ /// <summary>
+ /// Returns a byte array containing the encoded frame with the specified quality.
+ /// </summary>
+ /// <param name="quality">The quality.</param>
+ /// <returns></returns>
+ 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/Visual_Studio/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs
new file mode 100644
index 000000000..a3993d76d
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs
@@ -0,0 +1,37 @@
+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 Tango.RemoteDesktop.Quantization;
+
+namespace Tango.RemoteDesktop.Encoders
+{
+ public class Png8BitEncoder : FrameEncoder
+ {
+ public Png8BitEncoder(IFrame frame) : base(frame)
+ {
+ }
+
+ public override MemoryStream ToStream()
+ {
+ MemoryStream ms = new MemoryStream();
+ //var quantizer = new OctreeQuantizer(256, true);
+ //var quantized = quantizer.Quantize(Frame.ToBitmap());
+
+ ImageCodecInfo icf = GetEncoder(ImageFormat.Png);
+ EncoderParameters parms = new EncoderParameters(1);
+ EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 8);
+ parms.Param[0] = parm;
+ Frame.ToBitmap().Save(ms, icf, parms);
+
+ ms.Position = 0;
+ //quantized.Dispose();
+
+ return ms;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/PngEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/PngEncoder.cs
new file mode 100644
index 000000000..96b870a77
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Encoders/PngEncoder.cs
@@ -0,0 +1,36 @@
+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
+{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> PNG encoder.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" />
+ public class PngEncoder : FrameEncoder
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PngEncoder"/> class.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
+ public PngEncoder(IFrame frame) : base(frame)
+ {
+ }
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ public override MemoryStream ToStream()
+ {
+ MemoryStream ms = new MemoryStream();
+ Frame.ToBitmap().Save(ms, ImageFormat.Png);
+ return ms;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs
new file mode 100644
index 000000000..363cc1a35
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs
@@ -0,0 +1,26 @@
+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
+{
+ /// <summary>
+ /// Represents a raster screen capture engine.
+ /// The difference <see cref="IFrame"/> delivered by the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event is of type <see cref="RasterFrame"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ScreenCaptureEngine{Tango.RemoteDesktop.Frames.RasterFrame}" />
+ public class RasterScreenCaptureEngine : ScreenCaptureEngine<RasterFrame>
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RasterScreenCaptureEngine"/> class.
+ /// </summary>
+ public RasterScreenCaptureEngine() : base(new RasterBitmapComparer())
+ {
+
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs
new file mode 100644
index 000000000..7e14f220f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs
@@ -0,0 +1,23 @@
+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
+{
+ /// <summary>
+ /// Represents a vector screen capture engine.
+ /// The difference <see cref="IFrame"/> delivered by the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event is of type <see cref="VectorFrame"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ScreenCaptureEngine{Tango.RemoteDesktop.Frames.VectorFrame}" />
+ public class VectorScreenCaptureEngine : ScreenCaptureEngine<VectorFrame>
+ {
+ public VectorScreenCaptureEngine() : base(new VectorBitmapComparer())
+ {
+
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Frame.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Frame.cs
new file mode 100644
index 000000000..93f38bac0
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Frame.cs
@@ -0,0 +1,79 @@
+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
+{
+ /// <summary>
+ /// Represents an image frame base class.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IFrame" />
+ public abstract class Frame : IFrame
+ {
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
+ public abstract int Width { get; }
+
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
+ public abstract int Height { get; }
+
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
+ public abstract Bitmap ToBitmap();
+
+ /// <summary>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public virtual void Apply(Bitmap bitmap)
+ {
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.DrawImage(ToBitmap(), new Rectangle(0, 0, bitmap.Width, bitmap.Height));
+ }
+ }
+
+ /// <summary>
+ /// Returns a WPF BitmapSource representing the frame.
+ /// </summary>
+ /// <returns></returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Returns an instance of <see cref="IFrameEncoder" /> ready to encode this frame.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ public IFrameEncoder ToEncoder<T>() where T : IFrameEncoder
+ {
+ return Activator.CreateInstance(typeof(T), new object[] { this }) as IFrameEncoder;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public abstract void Dispose();
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/FrameEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/FrameEncoder.cs
new file mode 100644
index 000000000..a1413d9c9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/FrameEncoder.cs
@@ -0,0 +1,61 @@
+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;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents a base <see cref="IFrameEncoder"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.IFrameEncoder" />
+ public abstract class FrameEncoder : IFrameEncoder
+ {
+ /// <summary>
+ /// Gets the frame instance.
+ /// </summary>
+ public IFrame Frame { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FrameEncoder"/> class using an existing <see cref="IFrame"/>.
+ /// </summary>
+ /// <param name="frame">The frame.</param>
+ public FrameEncoder(IFrame frame)
+ {
+ Frame = frame;
+ }
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ public abstract MemoryStream ToStream();
+
+ /// <summary>
+ /// Returns a byte array containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ public byte[] ToArray()
+ {
+ using (var ms = ToStream())
+ {
+ return ms.ToArray();
+ }
+ }
+
+ /// <summary>
+ /// Gets the image encoder by the specified image format.
+ /// </summary>
+ /// <param name="format">The format.</param>
+ /// <returns></returns>
+ protected ImageCodecInfo GetEncoder(ImageFormat format)
+ {
+ ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
+ return codecs.Single(codec => codec.FormatID == format.Guid);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Frames/RasterFrame.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/RasterFrame.cs
new file mode 100644
index 000000000..bcb372bd3
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/RasterFrame.cs
@@ -0,0 +1,134 @@
+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;
+using Tango.RemoteDesktop.Clipping;
+
+namespace Tango.RemoteDesktop.Frames
+{
+ /// <summary>
+ /// Represents a raster frame encapsulating a standard GDI bitmap.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.Frame" />
+ public class RasterFrame : Frame
+ {
+ private Bitmap _bitmap;
+
+ /// <summary>
+ /// Gets the left position of this frame.
+ /// Will always be 0 unless <see cref="OptimizeBounds"/> was called.
+ /// </summary>
+ public int Left { get; private set; }
+
+ /// <summary>
+ /// Gets the top position of this frame.
+ /// Will always be 0 unless <see cref="OptimizeBounds"/> was called.
+ /// </summary>
+ public int Top { get; private set; }
+
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
+ public override int Width
+ {
+ get { return _bitmap.Width; }
+ }
+
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
+ public override int Height
+ {
+ get { return _bitmap.Height; }
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RasterFrame"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public RasterFrame(Bitmap bitmap)
+ {
+ _bitmap = bitmap;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RasterFrame"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="left">The left position of the frame.</param>
+ /// <param name="top">The top position of the frame.</param>
+ public RasterFrame(Bitmap bitmap, int left, int top) : this(bitmap)
+ {
+ Left = left;
+ Top = top;
+ }
+
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
+ public override Bitmap ToBitmap()
+ {
+ return _bitmap;
+ }
+
+ /// <summary>
+ /// Scales the frame by the specified coefficient.
+ /// </summary>
+ /// <param name="coefficient">The coefficient.</param>
+ /// <returns></returns>
+ public RasterFrame Scale(float coefficient)
+ {
+ var scaled = new Bitmap(_bitmap, new Size((int)(Width * coefficient), (int)(Height * coefficient)));
+ _bitmap.Dispose();
+ _bitmap = scaled;
+ return this;
+ }
+
+ /// <summary>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public override void Apply(Bitmap bitmap)
+ {
+ if (Top == 0 && Left == 0)
+ {
+ base.Apply(bitmap);
+ }
+ else
+ {
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.DrawImage(_bitmap, new Rectangle(Left, Top, Width, Height));
+ }
+ }
+ }
+
+ /// <summary>
+ /// Optimizes the bounds of this frame by removing unnecessary margins.
+ /// </summary>
+ /// <returns></returns>
+ public RasterFrame OptimizeBounds()
+ {
+ var result = BitmapCliper.ClipBitmap(_bitmap);
+ _bitmap.Dispose();
+ Left = result.Bounds.Left;
+ Top = result.Bounds.Top;
+ _bitmap = result.Bitmap;
+ return this;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public override void Dispose()
+ {
+ _bitmap.Dispose();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrame.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrame.cs
new file mode 100644
index 000000000..89287ad42
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrame.cs
@@ -0,0 +1,227 @@
+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
+{
+ /// <summary>
+ /// Represents a vector frame encapsulating a set of <see cref="VectorFramePixel"/>.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.Frame" />
+ public class VectorFrame : Frame
+ {
+ private int _width;
+ /// <summary>
+ /// Gets the frame width.
+ /// </summary>
+ public override int Width
+ {
+ get { return _width; }
+ }
+
+ private int _height;
+ /// <summary>
+ /// Gets the frame height.
+ /// </summary>
+ public override int Height
+ {
+ get { return _height; }
+ }
+
+ /// <summary>
+ /// Gets or sets the colors dictionary.
+ /// </summary>
+ public Dictionary<int, VectorFrameColor> Colors { get; set; }
+
+ /// <summary>
+ /// Gets the pixels.
+ /// </summary>
+ /// <value>
+ /// The pixels.
+ /// </value>
+ public List<VectorFramePixel> Pixels { get; private set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="VectorFrame"/> class.
+ /// </summary>
+ /// <param name="width">The frame width.</param>
+ /// <param name="height">The frame height.</param>
+ public VectorFrame(int width, int height)
+ {
+ _width = width;
+ _height = height;
+ Pixels = new List<VectorFramePixel>();
+ Colors = new Dictionary<int, VectorFrameColor>();
+ }
+
+ /// <summary>
+ /// Returns a GDI bitmap representing the frame.
+ /// </summary>
+ /// <returns></returns>
+ public override Bitmap ToBitmap()
+ {
+ DirectBitmap directBitmap = new DirectBitmap(Width, Height);
+ foreach (var pixel in Pixels)
+ {
+ directBitmap.SetPixel((int)pixel.X, (int)pixel.Y, pixel.Color.ToColor());
+ }
+
+ directBitmap.Dispose();
+ return directBitmap.Bitmap;
+ }
+
+ /// <summary>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public override void Apply(Bitmap bitmap)
+ {
+ using (FastBitmap fast = bitmap.FastLock())
+ {
+ foreach (var pixel in Pixels)
+ {
+ fast.SetPixel((int)pixel.X, (int)pixel.Y, pixel.Color.ToColor());
+ }
+ }
+ }
+
+ /// <summary>
+ /// Returns a byte array containing the binary serialized version of this frame.
+ /// </summary>
+ /// <returns></returns>
+ public byte[] Serialize()
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ using (BinaryWriter writer = new BinaryWriter(ms))
+ {
+ writer.Write((UInt16)Width);
+ writer.Write((UInt16)Height);
+ writer.Write((UInt16)Colors.Count);
+
+ if (Colors.Count > UInt16.MaxValue)
+ {
+ throw new InvalidOperationException("Number of color exceeded the maximum colors allowed.");
+ }
+
+ foreach (var color in Colors)
+ {
+ writer.Write(color.Value.R);
+ writer.Write(color.Value.G);
+ writer.Write(color.Value.B);
+ }
+
+ foreach (var pixel in Pixels)
+ {
+ writer.Write((UInt16)pixel.X);
+ writer.Write((UInt16)pixel.ColorIndex);
+ }
+
+ ms.Position = 0;
+ return ms.ToArray();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Calculates the size of <see cref="Serialize"/> byte output.
+ /// </summary>
+ /// <returns></returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Creates a new instance of <see cref="VectorFrame"/> from the specified serialized byte array.
+ /// </summary>
+ /// <param name="data">The byte array.</param>
+ /// <returns></returns>
+ 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.ReadUInt16(), reader.ReadUInt16());
+
+ int colorCount = reader.ReadUInt16(); //Number of colors in color table.
+
+ for (int i = 0; i < colorCount; i++)
+ {
+ VectorFrameColor color = new VectorFrameColor()
+ {
+ R = reader.ReadByte(),
+ G = reader.ReadByte(),
+ B = reader.ReadByte(),
+ Index = i,
+ };
+
+ vector.Colors.Add(color.ToInt32(), color);
+ }
+
+ for (int i = 0; i < vector.Height; i++)
+ {
+ VectorFramePixel pixel = new VectorFramePixel();
+ pixel.X = reader.ReadUInt16();
+ pixel.Y = i;
+ pixel.ColorIndex = reader.ReadUInt16();
+ pixel.Color = vector.Colors.ElementAt(pixel.ColorIndex).Value;
+ vector.Pixels.Add(pixel);
+ }
+
+ return vector;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public override void Dispose()
+ {
+ Pixels.Clear();
+ Pixels = null;
+ }
+
+ #region Color Helpers
+
+ /// <summary>
+ /// Converts the specified color to integer.
+ /// </summary>
+ /// <param name="color">The color.</param>
+ /// <returns></returns>
+ public static int ColorToInteger(byte r, byte g, byte b)
+ {
+ return (int)((255 << 24) | (r << 16) | (g << 8) | (b << 0));
+ }
+
+ /// <summary>
+ /// Converts the specified integer to color.
+ /// </summary>
+ /// <param name="integer">The integer.</param>
+ /// <returns></returns>
+ public static Color IntegerToColor(int integer)
+ {
+ byte a = (byte)(integer >> 24);
+ byte r = (byte)(integer >> 16);
+ byte g = (byte)(integer >> 8);
+ byte b = (byte)(integer >> 0);
+ return Color.FromArgb(a, r, g, b);
+ }
+
+ #endregion
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrameColor.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrameColor.cs
new file mode 100644
index 000000000..4bdb9ab68
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFrameColor.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Frames
+{
+ public struct VectorFrameColor
+ {
+ /// <summary>
+ /// Gets or sets the pixel color red component.
+ /// </summary>
+ public byte R { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pixel color green component.
+ /// </summary>
+ public byte G { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pixel color blue component.
+ /// </summary>
+ public byte B { get; set; }
+
+ /// <summary>
+ /// Gets or sets the color index within the dictionary.
+ /// </summary>
+ public int Index { get; set; }
+
+ /// <summary>
+ /// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
+ /// </summary>
+ /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
+ /// <returns>
+ /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
+ /// </returns>
+ public override bool Equals(object obj)
+ {
+ var other = (VectorFrameColor)obj;
+ return R == other.R && G == other.G && B == other.B;
+ }
+
+ /// <summary>
+ /// Returns a hash code for this instance.
+ /// </summary>
+ /// <returns>
+ /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
+ /// </returns>
+ public override int GetHashCode()
+ {
+ var hashCode = -1520100960;
+ hashCode = hashCode * -1521134295 + R.GetHashCode();
+ hashCode = hashCode * -1521134295 + G.GetHashCode();
+ hashCode = hashCode * -1521134295 + B.GetHashCode();
+ return hashCode;
+ }
+
+ /// <summary>
+ /// Returns a 32 bit integer representing this color.
+ /// </summary>
+ /// <returns></returns>
+ public int ToInt32()
+ {
+ return (int)((255 << 24) | (R << 16) | (G << 8) | (B << 0));
+ }
+
+ /// <summary>
+ /// Creates a color from the specified 32 bit integer.
+ /// </summary>
+ /// <param name="integer">The integer.</param>
+ /// <returns></returns>
+ public static VectorFrameColor FromInt32(int integer)
+ {
+ byte a = (byte)(integer >> 24);
+ byte r = (byte)(integer >> 16);
+ byte g = (byte)(integer >> 8);
+ byte b = (byte)(integer >> 0);
+ return new VectorFrameColor()
+ {
+ R = r,
+ G = g,
+ B = g,
+ };
+ }
+
+ public Color ToColor()
+ {
+ return Color.FromArgb(R, G, B);
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFramePixel.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFramePixel.cs
new file mode 100644
index 000000000..df1a71232
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Frames/VectorFramePixel.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Frames
+{
+ /// <summary>
+ /// Represents a <see cref="VectorFrame"/> pixel.
+ /// </summary>
+ public struct VectorFramePixel
+ {
+ /// <summary>
+ /// Gets or sets the x position.
+ /// </summary>
+ public int X { get; set; }
+ /// <summary>
+ /// Gets or sets the y position.
+ /// </summary>
+ public int Y { get; set; }
+
+ /// <summary>
+ /// Gets or sets the index of the color.
+ /// </summary>
+ public int ColorIndex { get; set; }
+
+ /// <summary>
+ /// Gets or sets the pixel color (not for serialization!).
+ /// </summary>
+ public VectorFrameColor Color { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/IBitmapComparer.cs b/Software/Visual_Studio/Tango.RemoteDesktop/IBitmapComparer.cs
new file mode 100644
index 000000000..6e1f8e999
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/IBitmapComparer.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents a bitmap comparison engine which returns a difference object of type <see cref="TFrame"/> between a previous and current bitmaps.
+ /// </summary>
+ /// <typeparam name="TFrame">Type of frame</typeparam>
+ public interface IBitmapComparer<TFrame> where TFrame : IFrame
+ {
+ /// <summary>
+ /// Creates the difference result from two bitmaps.
+ /// </summary>
+ /// <param name="previousBitmap">The previous bitmap.</param>
+ /// <param name="currentBitmap">The current bitmap.</param>
+ /// <returns></returns>
+ BitmapComparerResult<TFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/ICaptureMethod.cs b/Software/Visual_Studio/Tango.RemoteDesktop/ICaptureMethod.cs
new file mode 100644
index 000000000..0ac8add89
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/ICaptureMethod.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents a screen capture method.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public interface ICaptureMethod : IDisposable
+ {
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
+ Bitmap GetDesktopBitmap(CaptureRegion region);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/IFrame.cs b/Software/Visual_Studio/Tango.RemoteDesktop/IFrame.cs
new file mode 100644
index 000000000..f5a69218d
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/IFrame.cs
@@ -0,0 +1,53 @@
+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>
+ /// Applies this frame onto an existing bitmap.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ void Apply(Bitmap bitmap);
+
+ /// <summary>
+ /// Returns an instance of <see cref="IFrameEncoder"/> ready to encode this frame.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <returns></returns>
+ IFrameEncoder ToEncoder<T>() where T : IFrameEncoder;
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/IFrameEncoder.cs b/Software/Visual_Studio/Tango.RemoteDesktop/IFrameEncoder.cs
new file mode 100644
index 000000000..2df616ab8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/IFrameEncoder.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents an <see cref="IFrame"/> encoder.
+ /// </summary>
+ public interface IFrameEncoder
+ {
+ /// <summary>
+ /// Gets the frame instance.
+ /// </summary>
+ IFrame Frame { get; }
+
+ /// <summary>
+ /// Returns a byte array containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ byte[] ToArray();
+
+ /// <summary>
+ /// Returns a stream containing the encoded frame.
+ /// </summary>
+ /// <returns></returns>
+ MemoryStream ToStream();
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/IScreenCaptureEngine.cs b/Software/Visual_Studio/Tango.RemoteDesktop/IScreenCaptureEngine.cs
new file mode 100644
index 000000000..6b5db2fbe
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/IScreenCaptureEngine.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents a screen capture engine.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
+ public interface IScreenCaptureEngine : IDisposable
+ {
+ /// <summary>
+ /// Gets or sets the screen capture method.
+ /// </summary>
+ ICaptureMethod CaptureMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the screen capture region.
+ /// </summary>
+ CaptureRegion CaptureRegion { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is currently capturing.
+ /// </summary>
+ bool IsStarted { get; }
+
+ /// <summary>
+ /// Gets or sets the frame rate per second.
+ /// </summary>
+ int FrameRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to include the cursor when capturing.
+ /// </summary>
+ bool CaptureCursor { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable image comparison.
+ /// </summary>
+ bool EnableComparer { get; set; }
+
+ /// <summary>
+ /// Start capturing.
+ /// </summary>
+ void Start();
+
+ /// <summary>
+ /// Stop capturing.
+ /// </summary>
+ void Stop();
+ }
+
+ /// <summary>
+ /// Represents a screen capture engine.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ /// <seealso cref="System.IDisposable" />
+ public interface IScreenCaptureEngine<TFrame> : IScreenCaptureEngine where TFrame : IFrame
+ {
+ /// <summary>
+ /// Occurs when a new screen frame is available.
+ /// </summary>
+ event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived;
+
+ /// <summary>
+ /// Gets or sets the bitmap comparer.
+ /// </summary>
+ IBitmapComparer<TFrame> Comparer { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Properties/AssemblyInfo.cs
index 85b6ec515..7d2e42b5d 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/Properties/AssemblyInfo.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Properties/AssemblyInfo.cs
@@ -5,11 +5,11 @@ 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: AssemblyTitle("Tango.RemoteDesktop")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("Tango.ScreenCapture")]
+[assembly: AssemblyProduct("Tango.RemoteDesktop")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -20,7 +20,7 @@ using System.Runtime.InteropServices;
[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")]
+[assembly: Guid("a78068d4-2061-4376-8ede-583d8d880dec")]
// Version information for an assembly consists of the following four values:
//
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/ColorBgra.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/ColorBgra.cs
new file mode 100644
index 000000000..87c9e9637
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/ColorBgra.cs
@@ -0,0 +1,1851 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ /// <summary>
+ /// This is our pixel format that we will work with. It is always 32-bits / 4-bytes and is
+ /// always laid out in BGRA order.
+ /// Generally used with the Surface class.
+ /// </summary>
+ [Serializable]
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct ColorBgra
+ {
+ [FieldOffset(0)]
+ public byte B;
+
+ [FieldOffset(1)]
+ public byte G;
+
+ [FieldOffset(2)]
+ public byte R;
+
+ [FieldOffset(3)]
+ public byte A;
+
+ /// <summary>
+ /// Lets you change B, G, R, and A at the same time.
+ /// </summary>
+ [NonSerialized]
+ [FieldOffset(0)]
+ public uint Bgra;
+
+ public const int BlueChannel = 0;
+ public const int GreenChannel = 1;
+ public const int RedChannel = 2;
+ public const int AlphaChannel = 3;
+
+ public const int SizeOf = 4;
+
+ public static ColorBgra ParseHexString(string hexString)
+ {
+ uint value = Convert.ToUInt32(hexString, 16);
+ return ColorBgra.FromUInt32(value);
+ }
+
+ public string ToHexString()
+ {
+ int rgbNumber = (this.R << 16) | (this.G << 8) | this.B;
+ string colorString = Convert.ToString(rgbNumber, 16);
+
+ while (colorString.Length < 6)
+ {
+ colorString = "0" + colorString;
+ }
+
+ string alphaString = System.Convert.ToString(this.A, 16);
+
+ while (alphaString.Length < 2)
+ {
+ alphaString = "0" + alphaString;
+ }
+
+ colorString = alphaString + colorString;
+
+ return colorString.ToUpper();
+ }
+
+ /// <summary>
+ /// Gets or sets the byte value of the specified color channel.
+ /// </summary>
+ public unsafe byte this[int channel]
+ {
+ get
+ {
+ if (channel < 0 || channel > 3)
+ {
+ throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]");
+ }
+
+ fixed (byte *p = &B)
+ {
+ return p[channel];
+ }
+ }
+
+ set
+ {
+ if (channel < 0 || channel > 3)
+ {
+ throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]");
+ }
+
+ fixed (byte *p = &B)
+ {
+ p[channel] = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored.
+ /// </summary>
+ /// <returns>A value in the range 0 to 1 inclusive.</returns>
+ public double GetIntensity()
+ {
+ return ((0.114 * (double)B) + (0.587 * (double)G) + (0.299 * (double)R)) / 255.0;
+ }
+
+ /// <summary>
+ /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored.
+ /// </summary>
+ /// <returns>A value in the range 0 to 255 inclusive.</returns>
+ public byte GetIntensityByte()
+ {
+ return (byte)((7471 * B + 38470 * G + 19595 * R) >> 16);
+ }
+
+ /// <summary>
+ /// Returns the maximum value out of the B, G, and R values. Alpha is ignored.
+ /// </summary>
+ /// <returns></returns>
+ public byte GetMaxColorChannelValue()
+ {
+ return Math.Max(this.B, Math.Max(this.G, this.R));
+ }
+
+ /// <summary>
+ /// Returns the average of the B, G, and R values. Alpha is ignored.
+ /// </summary>
+ /// <returns></returns>
+ public byte GetAverageColorChannelValue()
+ {
+ return (byte)((this.B + this.G + this.R) / 3);
+ }
+
+ /// <summary>
+ /// Compares two ColorBgra instance to determine if they are equal.
+ /// </summary>
+ public static bool operator == (ColorBgra lhs, ColorBgra rhs)
+ {
+ return lhs.Bgra == rhs.Bgra;
+ }
+
+ /// <summary>
+ /// Compares two ColorBgra instance to determine if they are not equal.
+ /// </summary>
+ public static bool operator != (ColorBgra lhs, ColorBgra rhs)
+ {
+ return lhs.Bgra != rhs.Bgra;
+ }
+
+ /// <summary>
+ /// Compares two ColorBgra instance to determine if they are equal.
+ /// </summary>
+ public override bool Equals(object obj)
+ {
+
+ if (obj != null && obj is ColorBgra && ((ColorBgra)obj).Bgra == this.Bgra)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ /// <summary>
+ /// Returns a hash code for this color value.
+ /// </summary>
+ /// <returns></returns>
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (int)Bgra;
+ }
+ }
+
+ /// <summary>
+ /// Gets the equivalent GDI+ PixelFormat.
+ /// </summary>
+ /// <remarks>
+ /// This property always returns PixelFormat.Format32bppArgb.
+ /// </remarks>
+ public static PixelFormat PixelFormat
+ {
+ get
+ {
+ return PixelFormat.Format32bppArgb;
+ }
+ }
+
+ /// <summary>
+ /// Returns a new ColorBgra with the same color values but with a new alpha component value.
+ /// </summary>
+ public ColorBgra NewAlpha(byte newA)
+ {
+ return ColorBgra.FromBgra(B, G, R, newA);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ [Obsolete ("Use FromBgra() instead (make sure to swap the order of your b and r parameters)")]
+ public static ColorBgra FromRgba(byte r, byte g, byte b, byte a)
+ {
+ return FromBgra(b, g, r, a);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color values, and 255 for alpha.
+ /// </summary>
+ [Obsolete ("Use FromBgr() instead (make sure to swap the order of your b and r parameters)")]
+ public static ColorBgra FromRgb(byte r, byte g, byte b)
+ {
+ return FromBgr(b, g, r);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ public static ColorBgra FromBgra(byte b, byte g, byte r, byte a)
+ {
+ ColorBgra color = new ColorBgra();
+ color.Bgra = BgraToUInt32(b, g, r, a);
+ return color;
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ public static ColorBgra FromBgraClamped(int b, int g, int r, int a)
+ {
+ return FromBgra(
+ Utility.ClampToByte(b),
+ Utility.ClampToByte(g),
+ Utility.ClampToByte(r),
+ Utility.ClampToByte(a));
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color and alpha values.
+ /// </summary>
+ public static ColorBgra FromBgraClamped(float b, float g, float r, float a)
+ {
+ return FromBgra(
+ Utility.ClampToByte(b),
+ Utility.ClampToByte(g),
+ Utility.ClampToByte(r),
+ Utility.ClampToByte(a));
+ }
+
+ /// <summary>
+ /// Packs color and alpha values into a 32-bit integer.
+ /// </summary>
+ public static UInt32 BgraToUInt32(byte b, byte g, byte r, byte a)
+ {
+ return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24);
+ }
+
+ /// <summary>
+ /// Packs color and alpha values into a 32-bit integer.
+ /// </summary>
+ public static UInt32 BgraToUInt32(int b, int g, int r, int a)
+ {
+ return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24);
+ }
+
+ /// <summary>
+ /// Creates a new ColorBgra instance with the given color values, and 255 for alpha.
+ /// </summary>
+ public static ColorBgra FromBgr(byte b, byte g, byte r)
+ {
+ return FromBgra(b, g, r, 255);
+ }
+
+ /// <summary>
+ /// Constructs a new ColorBgra instance with the given 32-bit value.
+ /// </summary>
+ public static ColorBgra FromUInt32(UInt32 bgra)
+ {
+ ColorBgra color = new ColorBgra();
+ color.Bgra = bgra;
+ return color;
+ }
+
+ /// <summary>
+ /// Constructs a new ColorBgra instance given a 32-bit signed integer that represents an R,G,B triple.
+ /// Alpha will be initialized to 255.
+ /// </summary>
+ public static ColorBgra FromOpaqueInt32(Int32 bgr)
+ {
+ if (bgr < 0 || bgr > 0xffffff)
+ {
+ throw new ArgumentOutOfRangeException("bgr", "must be in the range [0, 0xffffff]");
+ }
+
+ ColorBgra color = new ColorBgra();
+ color.Bgra = (uint)bgr;
+ color.A = 255;
+
+ return color;
+ }
+
+ public static int ToOpaqueInt32(ColorBgra color)
+ {
+ if (color.A != 255)
+ {
+ throw new InvalidOperationException("Alpha value must be 255 for this to work");
+ }
+
+ return (int)(color.Bgra & 0xffffff);
+ }
+
+ /// <summary>
+ /// Constructs a new ColorBgra instance from the values in the given Color instance.
+ /// </summary>
+ public static ColorBgra FromColor(Color c)
+ {
+ return FromBgra(c.B, c.G, c.R, c.A);
+ }
+
+ /// <summary>
+ /// Converts this ColorBgra instance to a Color instance.
+ /// </summary>
+ public Color ToColor()
+ {
+ return Color.FromArgb(A, R, G, B);
+ }
+
+ /// <summary>
+ /// Smoothly blends between two colors.
+ /// </summary>
+ public static ColorBgra Blend(ColorBgra ca, ColorBgra cb, byte cbAlpha)
+ {
+ uint caA = (uint)Utility.FastScaleByteByByte((byte)(255 - cbAlpha), ca.A);
+ uint cbA = (uint)Utility.FastScaleByteByByte(cbAlpha, cb.A);
+ uint cbAT = caA + cbA;
+
+ uint r;
+ uint g;
+ uint b;
+
+ if (cbAT == 0)
+ {
+ r = 0;
+ g = 0;
+ b = 0;
+ }
+ else
+ {
+ r = ((ca.R * caA) + (cb.R * cbA)) / cbAT;
+ g = ((ca.G * caA) + (cb.G * cbA)) / cbAT;
+ b = ((ca.B * caA) + (cb.B * cbA)) / cbAT;
+ }
+
+ return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)cbAT);
+ }
+
+ /// <summary>
+ /// Linearly interpolates between two color values.
+ /// </summary>
+ /// <param name="from">The color value that represents 0 on the lerp number line.</param>
+ /// <param name="to">The color value that represents 1 on the lerp number line.</param>
+ /// <param name="frac">A value in the range [0, 1].</param>
+ /// <remarks>
+ /// This method does a simple lerp on each color value and on the alpha channel. It does
+ /// not properly take into account the alpha channel's effect on color blending.
+ /// </remarks>
+ public static ColorBgra Lerp(ColorBgra from, ColorBgra to, float frac)
+ {
+ ColorBgra ret = new ColorBgra();
+
+ ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac));
+ ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac));
+ ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac));
+ ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac));
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Linearly interpolates between two color values.
+ /// </summary>
+ /// <param name="from">The color value that represents 0 on the lerp number line.</param>
+ /// <param name="to">The color value that represents 1 on the lerp number line.</param>
+ /// <param name="frac">A value in the range [0, 1].</param>
+ /// <remarks>
+ /// This method does a simple lerp on each color value and on the alpha channel. It does
+ /// not properly take into account the alpha channel's effect on color blending.
+ /// </remarks>
+ public static ColorBgra Lerp(ColorBgra from, ColorBgra to, double frac)
+ {
+ ColorBgra ret = new ColorBgra();
+
+ ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac));
+ ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac));
+ ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac));
+ ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac));
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Blends four colors together based on the given weight values.
+ /// </summary>
+ /// <returns>The blended color.</returns>
+ /// <remarks>
+ /// The weights should be 16-bit fixed point numbers that add up to 65536 ("1.0").
+ /// 4W16IP means "4 colors, weights, 16-bit integer precision"
+ /// </remarks>
+ public static ColorBgra BlendColors4W16IP(ColorBgra c1, uint w1, ColorBgra c2, uint w2, ColorBgra c3, uint w3, ColorBgra c4, uint w4)
+ {
+#if DEBUG
+ /*
+ if ((w1 + w2 + w3 + w4) != 65536)
+ {
+ throw new ArgumentOutOfRangeException("w1 + w2 + w3 + w4 must equal 65536!");
+ }
+ * */
+#endif
+
+ const uint ww = 32768;
+ uint af = (c1.A * w1) + (c2.A * w2) + (c3.A * w3) + (c4.A * w4);
+ uint a = (af + ww) >> 16;
+
+ uint b;
+ uint g;
+ uint r;
+
+ if (a == 0)
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+ }
+ else
+ {
+ b = (uint)((((long)c1.A * c1.B * w1) + ((long)c2.A * c2.B * w2) + ((long)c3.A * c3.B * w3) + ((long)c4.A * c4.B * w4)) / af);
+ g = (uint)((((long)c1.A * c1.G * w1) + ((long)c2.A * c2.G * w2) + ((long)c3.A * c3.G * w3) + ((long)c4.A * c4.G * w4)) / af);
+ r = (uint)((((long)c1.A * c1.R * w1) + ((long)c2.A * c2.R * w2) + ((long)c3.A * c3.R * w3) + ((long)c4.A * c4.R * w4)) / af);
+ }
+
+ return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a);
+ }
+
+ /// <summary>
+ /// Blends the colors based on the given weight values.
+ /// </summary>
+ /// <param name="c">The array of color values.</param>
+ /// <param name="w">The array of weight values.</param>
+ /// <returns>
+ /// The weights should be fixed point numbers.
+ /// The total summation of the weight values will be treated as "1.0".
+ /// Each color will be blended in proportionally to its weight value respective to
+ /// the total summation of the weight values.
+ /// </returns>
+ /// <remarks>
+ /// "WAIP" stands for "weights, arbitrary integer precision"</remarks>
+ public static ColorBgra BlendColorsWAIP(ColorBgra[] c, uint[] w)
+ {
+ if (c.Length != w.Length)
+ {
+ throw new ArgumentException("c.Length != w.Length");
+ }
+
+ if (c.Length == 0)
+ {
+ return ColorBgra.FromUInt32(0);
+ }
+
+ long wsum = 0;
+ long asum = 0;
+
+ for (int i = 0; i < w.Length; ++i)
+ {
+ wsum += w[i];
+ asum += c[i].A * w[i];
+ }
+
+ uint a = (uint)((asum + (wsum >> 1)) / wsum);
+
+ long b;
+ long g;
+ long r;
+
+ if (a == 0)
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+ }
+ else
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+
+ for (int i = 0; i < c.Length; ++i)
+ {
+ b += (long)c[i].A * c[i].B * w[i];
+ g += (long)c[i].A * c[i].G * w[i];
+ r += (long)c[i].A * c[i].R * w[i];
+ }
+
+ b /= asum;
+ g /= asum;
+ r /= asum;
+ }
+
+ return ColorBgra.FromUInt32((uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24));
+ }
+
+ /// <summary>
+ /// Blends the colors based on the given weight values.
+ /// </summary>
+ /// <param name="c">The array of color values.</param>
+ /// <param name="w">The array of weight values.</param>
+ /// <returns>
+ /// Each color will be blended in proportionally to its weight value respective to
+ /// the total summation of the weight values.
+ /// </returns>
+ /// <remarks>
+ /// "WAIP" stands for "weights, floating-point"</remarks>
+ public static ColorBgra BlendColorsWFP(ColorBgra[] c, double[] w)
+ {
+ if (c.Length != w.Length)
+ {
+ throw new ArgumentException("c.Length != w.Length");
+ }
+
+ if (c.Length == 0)
+ {
+ return ColorBgra.Transparent;
+ }
+
+ double wsum = 0;
+ double asum = 0;
+
+ for (int i = 0; i < w.Length; ++i)
+ {
+ wsum += w[i];
+ asum += (double)c[i].A * w[i];
+ }
+
+ double a = asum / wsum;
+ double aMultWsum = a * wsum;
+
+ double b;
+ double g;
+ double r;
+
+ if (asum == 0)
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+ }
+ else
+ {
+ b = 0;
+ g = 0;
+ r = 0;
+
+ for (int i = 0; i < c.Length; ++i)
+ {
+ b += (double)c[i].A * c[i].B * w[i];
+ g += (double)c[i].A * c[i].G * w[i];
+ r += (double)c[i].A * c[i].R * w[i];
+ }
+
+ b /= aMultWsum;
+ g /= aMultWsum;
+ r /= aMultWsum;
+ }
+
+ return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a);
+ }
+
+ public static ColorBgra Blend(ColorBgra[] colors)
+ {
+ unsafe
+ {
+ fixed (ColorBgra* pColors = colors)
+ {
+ return Blend(pColors, colors.Length);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Smoothly blends the given colors together, assuming equal weighting for each one.
+ /// </summary>
+ /// <param name="colors"></param>
+ /// <param name="colorCount"></param>
+ /// <returns></returns>
+ public unsafe static ColorBgra Blend(ColorBgra* colors, int count)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException("count must be 0 or greater");
+ }
+
+ if (count == 0)
+ {
+ return ColorBgra.Transparent;
+ }
+
+ ulong aSum = 0;
+
+ for (int i = 0; i < count; ++i)
+ {
+ aSum += (ulong)colors[i].A;
+ }
+
+ byte b = 0;
+ byte g = 0;
+ byte r = 0;
+ byte a = (byte)(aSum / (ulong)count);
+
+ if (aSum != 0)
+ {
+ ulong bSum = 0;
+ ulong gSum = 0;
+ ulong rSum = 0;
+
+ for (int i = 0; i < count; ++i)
+ {
+ bSum += (ulong)(colors[i].A * colors[i].B);
+ gSum += (ulong)(colors[i].A * colors[i].G);
+ rSum += (ulong)(colors[i].A * colors[i].R);
+ }
+
+ b = (byte)(bSum / aSum);
+ g = (byte)(gSum / aSum);
+ r = (byte)(rSum / aSum);
+ }
+
+ return ColorBgra.FromBgra(b, g, r, a);
+ }
+
+ public override string ToString()
+ {
+ return "B: " + B + ", G: " + G + ", R: " + R + ", A: " + A;
+ }
+
+ /// <summary>
+ /// Casts a ColorBgra to a UInt32.
+ /// </summary>
+ public static explicit operator UInt32(ColorBgra color)
+ {
+ return color.Bgra;
+ }
+
+ /// <summary>
+ /// Casts a UInt32 to a ColorBgra.
+ /// </summary>
+ public static explicit operator ColorBgra(UInt32 uint32)
+ {
+ return ColorBgra.FromUInt32(uint32);
+ }
+
+ // Colors: copied from System.Drawing.Color's list (don't worry I didn't type it in
+ // manually, I used a code generator w/ reflection ...)
+
+ public static ColorBgra Transparent
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 255, 0);
+ }
+ }
+
+ public static ColorBgra AliceBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 248, 240, 255);
+ }
+ }
+
+ public static ColorBgra AntiqueWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(215, 235, 250, 255);
+ }
+ }
+
+ public static ColorBgra Aqua
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra Aquamarine
+ {
+ get
+ {
+ return ColorBgra.FromBgra(212, 255, 127, 255);
+ }
+ }
+
+ public static ColorBgra Azure
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 240, 255);
+ }
+ }
+
+ public static ColorBgra Beige
+ {
+ get
+ {
+ return ColorBgra.FromBgra(220, 245, 245, 255);
+ }
+ }
+
+ public static ColorBgra Bisque
+ {
+ get
+ {
+ return ColorBgra.FromBgra(196, 228, 255, 255);
+ }
+ }
+
+ public static ColorBgra Black
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra BlanchedAlmond
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 235, 255, 255);
+ }
+ }
+
+ public static ColorBgra Blue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra BlueViolet
+ {
+ get
+ {
+ return ColorBgra.FromBgra(226, 43, 138, 255);
+ }
+ }
+
+ public static ColorBgra Brown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(42, 42, 165, 255);
+ }
+ }
+
+ public static ColorBgra BurlyWood
+ {
+ get
+ {
+ return ColorBgra.FromBgra(135, 184, 222, 255);
+ }
+ }
+
+ public static ColorBgra CadetBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(160, 158, 95, 255);
+ }
+ }
+
+ public static ColorBgra Chartreuse
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 255, 127, 255);
+ }
+ }
+
+ public static ColorBgra Chocolate
+ {
+ get
+ {
+ return ColorBgra.FromBgra(30, 105, 210, 255);
+ }
+ }
+
+ public static ColorBgra Coral
+ {
+ get
+ {
+ return ColorBgra.FromBgra(80, 127, 255, 255);
+ }
+ }
+
+ public static ColorBgra CornflowerBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(237, 149, 100, 255);
+ }
+ }
+
+ public static ColorBgra Cornsilk
+ {
+ get
+ {
+ return ColorBgra.FromBgra(220, 248, 255, 255);
+ }
+ }
+
+ public static ColorBgra Crimson
+ {
+ get
+ {
+ return ColorBgra.FromBgra(60, 20, 220, 255);
+ }
+ }
+
+ public static ColorBgra Cyan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkCyan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 139, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkGoldenrod
+ {
+ get
+ {
+ return ColorBgra.FromBgra(11, 134, 184, 255);
+ }
+ }
+
+ public static ColorBgra DarkGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(169, 169, 169, 255);
+ }
+ }
+
+ public static ColorBgra DarkGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 100, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkKhaki
+ {
+ get
+ {
+ return ColorBgra.FromBgra(107, 183, 189, 255);
+ }
+ }
+
+ public static ColorBgra DarkMagenta
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 0, 139, 255);
+ }
+ }
+
+ public static ColorBgra DarkOliveGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(47, 107, 85, 255);
+ }
+ }
+
+ public static ColorBgra DarkOrange
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 140, 255, 255);
+ }
+ }
+
+ public static ColorBgra DarkOrchid
+ {
+ get
+ {
+ return ColorBgra.FromBgra(204, 50, 153, 255);
+ }
+ }
+
+ public static ColorBgra DarkRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 139, 255);
+ }
+ }
+
+ public static ColorBgra DarkSalmon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(122, 150, 233, 255);
+ }
+ }
+
+ public static ColorBgra DarkSeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 188, 143, 255);
+ }
+ }
+
+ public static ColorBgra DarkSlateBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(139, 61, 72, 255);
+ }
+ }
+
+ public static ColorBgra DarkSlateGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(79, 79, 47, 255);
+ }
+ }
+
+ public static ColorBgra DarkTurquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(209, 206, 0, 255);
+ }
+ }
+
+ public static ColorBgra DarkViolet
+ {
+ get
+ {
+ return ColorBgra.FromBgra(211, 0, 148, 255);
+ }
+ }
+
+ public static ColorBgra DeepPink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(147, 20, 255, 255);
+ }
+ }
+
+ public static ColorBgra DeepSkyBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 191, 0, 255);
+ }
+ }
+
+ public static ColorBgra DimGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(105, 105, 105, 255);
+ }
+ }
+
+ public static ColorBgra DodgerBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 144, 30, 255);
+ }
+ }
+
+ public static ColorBgra Firebrick
+ {
+ get
+ {
+ return ColorBgra.FromBgra(34, 34, 178, 255);
+ }
+ }
+
+ public static ColorBgra FloralWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(240, 250, 255, 255);
+ }
+ }
+
+ public static ColorBgra ForestGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(34, 139, 34, 255);
+ }
+ }
+
+ public static ColorBgra Fuchsia
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 0, 255, 255);
+ }
+ }
+
+ public static ColorBgra Gainsboro
+ {
+ get
+ {
+ return ColorBgra.FromBgra(220, 220, 220, 255);
+ }
+ }
+
+ public static ColorBgra GhostWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 248, 248, 255);
+ }
+ }
+
+ public static ColorBgra Gold
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 215, 255, 255);
+ }
+ }
+
+ public static ColorBgra Goldenrod
+ {
+ get
+ {
+ return ColorBgra.FromBgra(32, 165, 218, 255);
+ }
+ }
+
+ public static ColorBgra Gray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 128, 128, 255);
+ }
+ }
+
+ public static ColorBgra Green
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 128, 0, 255);
+ }
+ }
+
+ public static ColorBgra GreenYellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(47, 255, 173, 255);
+ }
+ }
+
+ public static ColorBgra Honeydew
+ {
+ get
+ {
+ return ColorBgra.FromBgra(240, 255, 240, 255);
+ }
+ }
+
+ public static ColorBgra HotPink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(180, 105, 255, 255);
+ }
+ }
+
+ public static ColorBgra IndianRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(92, 92, 205, 255);
+ }
+ }
+
+ public static ColorBgra Indigo
+ {
+ get
+ {
+ return ColorBgra.FromBgra(130, 0, 75, 255);
+ }
+ }
+
+ public static ColorBgra Ivory
+ {
+ get
+ {
+ return ColorBgra.FromBgra(240, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra Khaki
+ {
+ get
+ {
+ return ColorBgra.FromBgra(140, 230, 240, 255);
+ }
+ }
+
+ public static ColorBgra Lavender
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 230, 230, 255);
+ }
+ }
+
+ public static ColorBgra LavenderBlush
+ {
+ get
+ {
+ return ColorBgra.FromBgra(245, 240, 255, 255);
+ }
+ }
+
+ public static ColorBgra LawnGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 252, 124, 255);
+ }
+ }
+
+ public static ColorBgra LemonChiffon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 250, 255, 255);
+ }
+ }
+
+ public static ColorBgra LightBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 216, 173, 255);
+ }
+ }
+
+ public static ColorBgra LightCoral
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 128, 240, 255);
+ }
+ }
+
+ public static ColorBgra LightCyan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 224, 255);
+ }
+ }
+
+ public static ColorBgra LightGoldenrodYellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(210, 250, 250, 255);
+ }
+ }
+
+ public static ColorBgra LightGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(144, 238, 144, 255);
+ }
+ }
+
+ public static ColorBgra LightGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(211, 211, 211, 255);
+ }
+ }
+
+ public static ColorBgra LightPink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(193, 182, 255, 255);
+ }
+ }
+
+ public static ColorBgra LightSalmon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(122, 160, 255, 255);
+ }
+ }
+
+ public static ColorBgra LightSeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(170, 178, 32, 255);
+ }
+ }
+
+ public static ColorBgra LightSkyBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 206, 135, 255);
+ }
+ }
+
+ public static ColorBgra LightSlateGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(153, 136, 119, 255);
+ }
+ }
+
+ public static ColorBgra LightSteelBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(222, 196, 176, 255);
+ }
+ }
+
+ public static ColorBgra LightYellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(224, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra Lime
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra LimeGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(50, 205, 50, 255);
+ }
+ }
+
+ public static ColorBgra Linen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 240, 250, 255);
+ }
+ }
+
+ public static ColorBgra Magenta
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 0, 255, 255);
+ }
+ }
+
+ public static ColorBgra Maroon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 128, 255);
+ }
+ }
+
+ public static ColorBgra MediumAquamarine
+ {
+ get
+ {
+ return ColorBgra.FromBgra(170, 205, 102, 255);
+ }
+ }
+
+ public static ColorBgra MediumBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra MediumOrchid
+ {
+ get
+ {
+ return ColorBgra.FromBgra(211, 85, 186, 255);
+ }
+ }
+
+ public static ColorBgra MediumPurple
+ {
+ get
+ {
+ return ColorBgra.FromBgra(219, 112, 147, 255);
+ }
+ }
+
+ public static ColorBgra MediumSeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(113, 179, 60, 255);
+ }
+ }
+
+ public static ColorBgra MediumSlateBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 104, 123, 255);
+ }
+ }
+
+ public static ColorBgra MediumSpringGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(154, 250, 0, 255);
+ }
+ }
+
+ public static ColorBgra MediumTurquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(204, 209, 72, 255);
+ }
+ }
+
+ public static ColorBgra MediumVioletRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(133, 21, 199, 255);
+ }
+ }
+
+ public static ColorBgra MidnightBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(112, 25, 25, 255);
+ }
+ }
+
+ public static ColorBgra MintCream
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 255, 245, 255);
+ }
+ }
+
+ public static ColorBgra MistyRose
+ {
+ get
+ {
+ return ColorBgra.FromBgra(225, 228, 255, 255);
+ }
+ }
+
+ public static ColorBgra Moccasin
+ {
+ get
+ {
+ return ColorBgra.FromBgra(181, 228, 255, 255);
+ }
+ }
+
+ public static ColorBgra NavajoWhite
+ {
+ get
+ {
+ return ColorBgra.FromBgra(173, 222, 255, 255);
+ }
+ }
+
+ public static ColorBgra Navy
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 0, 0, 255);
+ }
+ }
+
+ public static ColorBgra OldLace
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 245, 253, 255);
+ }
+ }
+
+ public static ColorBgra Olive
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 128, 128, 255);
+ }
+ }
+
+ public static ColorBgra OliveDrab
+ {
+ get
+ {
+ return ColorBgra.FromBgra(35, 142, 107, 255);
+ }
+ }
+
+ public static ColorBgra Orange
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 165, 255, 255);
+ }
+ }
+
+ public static ColorBgra OrangeRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 69, 255, 255);
+ }
+ }
+
+ public static ColorBgra Orchid
+ {
+ get
+ {
+ return ColorBgra.FromBgra(214, 112, 218, 255);
+ }
+ }
+
+ public static ColorBgra PaleGoldenrod
+ {
+ get
+ {
+ return ColorBgra.FromBgra(170, 232, 238, 255);
+ }
+ }
+
+ public static ColorBgra PaleGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(152, 251, 152, 255);
+ }
+ }
+
+ public static ColorBgra PaleTurquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 238, 175, 255);
+ }
+ }
+
+ public static ColorBgra PaleVioletRed
+ {
+ get
+ {
+ return ColorBgra.FromBgra(147, 112, 219, 255);
+ }
+ }
+
+ public static ColorBgra PapayaWhip
+ {
+ get
+ {
+ return ColorBgra.FromBgra(213, 239, 255, 255);
+ }
+ }
+
+ public static ColorBgra PeachPuff
+ {
+ get
+ {
+ return ColorBgra.FromBgra(185, 218, 255, 255);
+ }
+ }
+
+ public static ColorBgra Peru
+ {
+ get
+ {
+ return ColorBgra.FromBgra(63, 133, 205, 255);
+ }
+ }
+
+ public static ColorBgra Pink
+ {
+ get
+ {
+ return ColorBgra.FromBgra(203, 192, 255, 255);
+ }
+ }
+
+ public static ColorBgra Plum
+ {
+ get
+ {
+ return ColorBgra.FromBgra(221, 160, 221, 255);
+ }
+ }
+
+ public static ColorBgra PowderBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(230, 224, 176, 255);
+ }
+ }
+
+ public static ColorBgra Purple
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 0, 128, 255);
+ }
+ }
+
+ public static ColorBgra Red
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 0, 255, 255);
+ }
+ }
+
+ public static ColorBgra RosyBrown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(143, 143, 188, 255);
+ }
+ }
+
+ public static ColorBgra RoyalBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(225, 105, 65, 255);
+ }
+ }
+
+ public static ColorBgra SaddleBrown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(19, 69, 139, 255);
+ }
+ }
+
+ public static ColorBgra Salmon
+ {
+ get
+ {
+ return ColorBgra.FromBgra(114, 128, 250, 255);
+ }
+ }
+
+ public static ColorBgra SandyBrown
+ {
+ get
+ {
+ return ColorBgra.FromBgra(96, 164, 244, 255);
+ }
+ }
+
+ public static ColorBgra SeaGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(87, 139, 46, 255);
+ }
+ }
+
+ public static ColorBgra SeaShell
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 245, 255, 255);
+ }
+ }
+
+ public static ColorBgra Sienna
+ {
+ get
+ {
+ return ColorBgra.FromBgra(45, 82, 160, 255);
+ }
+ }
+
+ public static ColorBgra Silver
+ {
+ get
+ {
+ return ColorBgra.FromBgra(192, 192, 192, 255);
+ }
+ }
+
+ public static ColorBgra SkyBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(235, 206, 135, 255);
+ }
+ }
+
+ public static ColorBgra SlateBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(205, 90, 106, 255);
+ }
+ }
+
+ public static ColorBgra SlateGray
+ {
+ get
+ {
+ return ColorBgra.FromBgra(144, 128, 112, 255);
+ }
+ }
+
+ public static ColorBgra Snow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(250, 250, 255, 255);
+ }
+ }
+
+ public static ColorBgra SpringGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(127, 255, 0, 255);
+ }
+ }
+
+ public static ColorBgra SteelBlue
+ {
+ get
+ {
+ return ColorBgra.FromBgra(180, 130, 70, 255);
+ }
+ }
+
+ public static ColorBgra Tan
+ {
+ get
+ {
+ return ColorBgra.FromBgra(140, 180, 210, 255);
+ }
+ }
+
+ public static ColorBgra Teal
+ {
+ get
+ {
+ return ColorBgra.FromBgra(128, 128, 0, 255);
+ }
+ }
+
+ public static ColorBgra Thistle
+ {
+ get
+ {
+ return ColorBgra.FromBgra(216, 191, 216, 255);
+ }
+ }
+
+ public static ColorBgra Tomato
+ {
+ get
+ {
+ return ColorBgra.FromBgra(71, 99, 255, 255);
+ }
+ }
+
+ public static ColorBgra Turquoise
+ {
+ get
+ {
+ return ColorBgra.FromBgra(208, 224, 64, 255);
+ }
+ }
+
+ public static ColorBgra Violet
+ {
+ get
+ {
+ return ColorBgra.FromBgra(238, 130, 238, 255);
+ }
+ }
+
+ public static ColorBgra Wheat
+ {
+ get
+ {
+ return ColorBgra.FromBgra(179, 222, 245, 255);
+ }
+ }
+
+ public static ColorBgra White
+ {
+ get
+ {
+ return ColorBgra.FromBgra(255, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra WhiteSmoke
+ {
+ get
+ {
+ return ColorBgra.FromBgra(245, 245, 245, 255);
+ }
+ }
+
+ public static ColorBgra Yellow
+ {
+ get
+ {
+ return ColorBgra.FromBgra(0, 255, 255, 255);
+ }
+ }
+
+ public static ColorBgra YellowGreen
+ {
+ get
+ {
+ return ColorBgra.FromBgra(50, 205, 154, 255);
+ }
+ }
+
+ public static ColorBgra Zero
+ {
+ get
+ {
+ return (ColorBgra)0;
+ }
+ }
+
+ private static Dictionary<string, ColorBgra> predefinedColors;
+
+ /// <summary>
+ /// Gets a hashtable that contains a list of all the predefined colors.
+ /// These are the same color values that are defined as public static properties
+ /// in System.Drawing.Color. The hashtable uses strings for the keys, and
+ /// ColorBgras for the values.
+ /// </summary>
+ public static Dictionary<string, ColorBgra> PredefinedColors
+ {
+ get
+ {
+ if (predefinedColors != null)
+ {
+ Type colorBgraType = typeof(ColorBgra);
+ PropertyInfo[] propInfos = colorBgraType.GetProperties(BindingFlags.Static | BindingFlags.Public);
+ Hashtable colors = new Hashtable();
+
+ foreach (PropertyInfo pi in propInfos)
+ {
+ if (pi.PropertyType == colorBgraType)
+ {
+ colors.Add(pi.Name, (ColorBgra)pi.GetValue(null, null));
+ }
+ }
+ }
+
+ return new Dictionary<string, ColorBgra>(predefinedColors);
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs
new file mode 100644
index 000000000..a9629f2d8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs
@@ -0,0 +1,569 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp
+
+using Tango.RemoteDesktop.Quantization;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal unsafe class OctreeQuantizer
+ : Quantizer
+ {
+ private bool enableTransparency;
+ private Octree octree;
+ private int maxColors;
+
+ /// <summary>
+ /// Construct the octree quantizer
+ /// </summary>
+ /// <remarks>
+ /// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree,
+ /// the second pass quantizes a color based on the nodes in the tree
+ /// </remarks>
+ /// <param name="maxColors">The maximum number of colors to return</param>
+ /// <param name="maxColorBits">The number of significant bits</param>
+ /// <param name="enableTransparency">If true, then one color slot in the palette will be reserved for transparency.
+ /// Any color passed through QuantizePixel which does not have an alpha of 255 will use this color slot.
+ /// If false, then all colors should have an alpha of 255. Otherwise the results may be unpredictable.</param>
+ public OctreeQuantizer(int maxColors, bool enableTransparency)
+ : base(false)
+ {
+ if (maxColors > 256)
+ {
+ throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be 256 or less");
+ }
+
+ if (maxColors < 2)
+ {
+ throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors must be 2 or more");
+ }
+
+ this.octree = new Octree(8); // 8-bits per color
+ this.enableTransparency = enableTransparency;
+ this.maxColors = maxColors - (this.enableTransparency ? 1 : 0); // subtract 1 if enableTransparency is true
+ }
+
+ /// <summary>
+ /// Process the pixel in the first pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <remarks>
+ /// This function need only be overridden if your quantize algorithm needs two passes,
+ /// such as an Octree quantizer.
+ /// </remarks>
+ protected override void InitialQuantizePixel(ColorBgra *pixel)
+ {
+ this.octree.AddColor(pixel);
+ }
+
+ /// <summary>
+ /// Override this to process the pixel in the second pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <returns>The quantized value</returns>
+ protected override byte QuantizePixel(ColorBgra *pixel)
+ {
+ byte paletteIndex = 0;
+
+ if (!this.enableTransparency || pixel->A == 255)
+ {
+ paletteIndex = (byte)this.octree.GetPaletteIndex(pixel);
+ }
+ else
+ {
+ paletteIndex = (byte)this.maxColors; // maxColors will have a maximum value of 255 is enableTransparency is true
+ }
+
+ return paletteIndex;
+ }
+
+ /// <summary>
+ /// Retrieve the palette for the quantized image
+ /// </summary>
+ /// <param name="original">Any old palette, this is overwritten</param>
+ /// <returns>The new color palette</returns>
+ protected override ColorPalette GetPalette(ColorPalette original)
+ {
+ // First off convert the octree to _maxColors colors
+ List<Color> palette = this.octree.Palletize(maxColors);
+
+ // Then convert the palette based on those colors
+ for (int index = 0; index < palette.Count; index++)
+ {
+ original.Entries[index] = palette[index];
+ }
+
+ // Fill the rest of the palette with transparent
+ for (int i = palette.Count; i < original.Entries.Length; ++i)
+ {
+ original.Entries[i] = Color.FromArgb(255, 0, 0, 0);
+ }
+
+ // Add the transparent color
+ if (this.enableTransparency)
+ {
+ original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0);
+ }
+
+ return original;
+ }
+
+ /// <summary>
+ /// Class which does the actual quantization
+ /// </summary>
+ private class Octree
+ {
+ /// <summary>
+ /// Construct the octree
+ /// </summary>
+ /// <param name="maxColorBits">The maximum number of significant bits in the image</param>
+ public Octree(int maxColorBits)
+ {
+ _maxColorBits = maxColorBits;
+ _leafCount = 0;
+ _reducibleNodes = new OctreeNode[9];
+ _root = new OctreeNode(0, _maxColorBits, this);
+ _previousColor = 0;
+ _previousNode = null;
+ }
+
+ /// <summary>
+ /// Add a given color value to the octree
+ /// </summary>
+ /// <param name="pixel"></param>
+ public void AddColor(ColorBgra *pixel)
+ {
+ // Check if this request is for the same color as the last
+ if (_previousColor == pixel->Bgra)
+ {
+ // If so, check if I have a previous node setup. This will only ocurr if the first color in the image
+ // happens to be black, with an alpha component of zero.
+ if (null == _previousNode)
+ {
+ _previousColor = pixel->Bgra;
+ _root.AddColor(pixel, _maxColorBits, 0, this);
+ }
+ else
+ {
+ // Just update the previous node
+ _previousNode.Increment(pixel);
+ }
+ }
+ else
+ {
+ _previousColor = pixel->Bgra;
+ _root.AddColor(pixel, _maxColorBits, 0, this);
+ }
+ }
+
+ /// <summary>
+ /// Reduce the depth of the tree
+ /// </summary>
+ public void Reduce()
+ {
+ int index;
+
+ // Find the deepest level containing at least one reducible node
+ for (index = _maxColorBits - 1;
+ (index > 0) && (null == _reducibleNodes[index]);
+ index--)
+ {
+ // intentionally blank
+ }
+
+ // Reduce the node most recently added to the list at level 'index'
+ OctreeNode node = _reducibleNodes[index];
+ _reducibleNodes[index] = node.NextReducible;
+
+ // Decrement the leaf count after reducing the node
+ _leafCount -= node.Reduce();
+
+ // And just in case I've reduced the last color to be added, and the next color to
+ // be added is the same, invalidate the previousNode...
+ _previousNode = null;
+ }
+
+ /// <summary>
+ /// Get/Set the number of leaves in the tree
+ /// </summary>
+ public int Leaves
+ {
+ get
+ {
+ return _leafCount;
+ }
+
+ set
+ {
+ _leafCount = value;
+ }
+ }
+
+ /// <summary>
+ /// Return the array of reducible nodes
+ /// </summary>
+ protected OctreeNode[] ReducibleNodes
+ {
+ get
+ {
+ return _reducibleNodes;
+ }
+ }
+
+ /// <summary>
+ /// Keep track of the previous node that was quantized
+ /// </summary>
+ /// <param name="node">The node last quantized</param>
+ protected void TrackPrevious(OctreeNode node)
+ {
+ _previousNode = node;
+ }
+
+ private Color[] _palette;
+ private PaletteTable paletteTable;
+
+ /// <summary>
+ /// Convert the nodes in the octree to a palette with a maximum of colorCount colors
+ /// </summary>
+ /// <param name="colorCount">The maximum number of colors</param>
+ /// <returns>A list with the palettized colors</returns>
+ public List<Color> Palletize(int colorCount)
+ {
+ while (Leaves > colorCount)
+ {
+ Reduce();
+ }
+
+ // Now palettize the nodes
+ List<Color> palette = new List<Color>(Leaves);
+ int paletteIndex = 0;
+
+ _root.ConstructPalette(palette, ref paletteIndex);
+
+ // And return the palette
+ this._palette = palette.ToArray();
+ this.paletteTable = null;
+
+ return palette;
+ }
+
+ /// <summary>
+ /// Get the palette index for the passed color
+ /// </summary>
+ /// <param name="pixel"></param>
+ /// <returns></returns>
+ public int GetPaletteIndex(ColorBgra *pixel)
+ {
+ int ret = -1;
+
+ ret = _root.GetPaletteIndex(pixel, 0);
+
+ if (ret < 0)
+ {
+ if (this.paletteTable == null)
+ {
+ this.paletteTable = new PaletteTable(this._palette);
+ }
+
+ ret = this.paletteTable.FindClosestPaletteIndex(pixel->ToColor());
+ }
+
+ return ret;
+ }
+
+ /// <summary>
+ /// Mask used when getting the appropriate pixels for a given node
+ /// </summary>
+ private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
+
+ /// <summary>
+ /// The root of the octree
+ /// </summary>
+ private OctreeNode _root;
+
+ /// <summary>
+ /// Number of leaves in the tree
+ /// </summary>
+ private int _leafCount;
+
+ /// <summary>
+ /// Array of reducible nodes
+ /// </summary>
+ private OctreeNode[] _reducibleNodes;
+
+ /// <summary>
+ /// Maximum number of significant bits in the image
+ /// </summary>
+ private int _maxColorBits;
+
+ /// <summary>
+ /// Store the last node quantized
+ /// </summary>
+ private OctreeNode _previousNode;
+
+ /// <summary>
+ /// Cache the previous color quantized
+ /// </summary>
+ private uint _previousColor;
+
+ /// <summary>
+ /// Class which encapsulates each node in the tree
+ /// </summary>
+ protected class OctreeNode
+ {
+ /// <summary>
+ /// Construct the node
+ /// </summary>
+ /// <param name="level">The level in the tree = 0 - 7</param>
+ /// <param name="colorBits">The number of significant color bits in the image</param>
+ /// <param name="octree">The tree to which this node belongs</param>
+ public OctreeNode(int level, int colorBits, Octree octree)
+ {
+ // Construct the new node
+ _leaf = (level == colorBits);
+
+ _red = 0;
+ _green = 0;
+ _blue = 0;
+ _pixelCount = 0;
+
+ // If a leaf, increment the leaf count
+ if (_leaf)
+ {
+ octree.Leaves++;
+ _nextReducible = null;
+ _children = null;
+ }
+ else
+ {
+ // Otherwise add this to the reducible nodes
+ _nextReducible = octree.ReducibleNodes[level];
+ octree.ReducibleNodes[level] = this;
+ _children = new OctreeNode[8];
+ }
+ }
+
+ /// <summary>
+ /// Add a color into the tree
+ /// </summary>
+ /// <param name="pixel">The color</param>
+ /// <param name="colorBits">The number of significant color bits</param>
+ /// <param name="level">The level in the tree</param>
+ /// <param name="octree">The tree to which this node belongs</param>
+ public void AddColor(ColorBgra *pixel, int colorBits, int level, Octree octree)
+ {
+ // Update the color information if this is a leaf
+ if (_leaf)
+ {
+ Increment(pixel);
+
+ // Setup the previous node
+ octree.TrackPrevious(this);
+ }
+ else
+ {
+ // Go to the next level down in the tree
+ int shift = 7 - level;
+ int index = ((pixel->R & mask[level]) >> (shift - 2)) |
+ ((pixel->G & mask[level]) >> (shift - 1)) |
+ ((pixel->B & mask[level]) >> (shift));
+
+ OctreeNode child = _children[index];
+
+ if (null == child)
+ {
+ // Create a new child node & store in the array
+ child = new OctreeNode(level + 1, colorBits, octree);
+ _children[index] = child;
+ }
+
+ // Add the color to the child node
+ child.AddColor(pixel, colorBits, level + 1, octree);
+ }
+
+ }
+
+ /// <summary>
+ /// Get/Set the next reducible node
+ /// </summary>
+ public OctreeNode NextReducible
+ {
+ get
+ {
+ return _nextReducible;
+ }
+
+ set
+ {
+ _nextReducible = value;
+ }
+ }
+
+ /// <summary>
+ /// Return the child nodes
+ /// </summary>
+ public OctreeNode[] Children
+ {
+ get
+ {
+ return _children;
+ }
+ }
+
+ /// <summary>
+ /// Reduce this node by removing all of its children
+ /// </summary>
+ /// <returns>The number of leaves removed</returns>
+ public int Reduce()
+ {
+ int children = 0;
+ _red = 0;
+ _green = 0;
+ _blue = 0;
+
+ // Loop through all children and add their information to this node
+ for (int index = 0; index < 8; index++)
+ {
+ if (null != _children[index])
+ {
+ _red += _children[index]._red;
+ _green += _children[index]._green;
+ _blue += _children[index]._blue;
+ _pixelCount += _children[index]._pixelCount;
+ ++children;
+ _children[index] = null;
+ }
+ }
+
+ // Now change this to a leaf node
+ _leaf = true;
+
+ // Return the number of nodes to decrement the leaf count by
+ return(children - 1);
+ }
+
+ /// <summary>
+ /// Traverse the tree, building up the color palette
+ /// </summary>
+ /// <param name="palette">The palette</param>
+ /// <param name="paletteIndex">The current palette index</param>
+ public void ConstructPalette(List<Color> palette, ref int paletteIndex)
+ {
+ if (_leaf)
+ {
+ // Consume the next palette index
+ _paletteIndex = paletteIndex++;
+
+ // And set the color of the palette entry
+ int r = _red / _pixelCount;
+ int g = _green / _pixelCount;
+ int b = _blue / _pixelCount;
+
+ palette.Add(Color.FromArgb(r, g, b));
+ }
+ else
+ {
+ // Loop through children looking for leaves
+ for (int index = 0; index < 8; index++)
+ {
+ if (null != _children[index])
+ {
+ _children[index].ConstructPalette(palette, ref paletteIndex);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Return the palette index for the passed color
+ /// </summary>
+ public int GetPaletteIndex(ColorBgra *pixel, int level)
+ {
+ int paletteIndex = _paletteIndex;
+
+ if (!_leaf)
+ {
+ int shift = 7 - level;
+ int index = ((pixel->R & mask[level]) >> (shift - 2)) |
+ ((pixel->G & mask[level]) >> (shift - 1)) |
+ ((pixel->B & mask[level]) >> (shift));
+
+ if (null != _children[index])
+ {
+ paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1);
+ }
+ else
+ {
+ paletteIndex = -1;
+ }
+ }
+
+ return paletteIndex;
+ }
+
+ /// <summary>
+ /// Increment the pixel count and add to the color information
+ /// </summary>
+ public void Increment(ColorBgra *pixel)
+ {
+ ++_pixelCount;
+ _red += pixel->R;
+ _green += pixel->G;
+ _blue += pixel->B;
+ }
+
+ /// <summary>
+ /// Flag indicating that this is a leaf node
+ /// </summary>
+ private bool _leaf;
+
+ /// <summary>
+ /// Number of pixels in this node
+ /// </summary>
+ private int _pixelCount;
+
+ /// <summary>
+ /// Red component
+ /// </summary>
+ private int _red;
+
+ /// <summary>
+ /// Green Component
+ /// </summary>
+ private int _green;
+
+ /// <summary>
+ /// Blue component
+ /// </summary>
+ private int _blue;
+
+ /// <summary>
+ /// Pointers to any child nodes
+ /// </summary>
+ private OctreeNode[] _children;
+
+ /// <summary>
+ /// Pointer to next reducible node
+ /// </summary>
+ private OctreeNode _nextReducible;
+
+ /// <summary>
+ /// The index of this node in the palette
+ /// </summary>
+ private int _paletteIndex;
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/PaletteTable.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/PaletteTable.cs
new file mode 100644
index 000000000..5c24fe80f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/PaletteTable.cs
@@ -0,0 +1,72 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Collections;
+using System.Drawing;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal sealed class PaletteTable
+ {
+ private Color[] palette;
+
+ public Color this[int index]
+ {
+ get
+ {
+ return this.palette[index];
+ }
+
+ set
+ {
+ this.palette[index] = value;
+ }
+ }
+
+ private int GetDistanceSquared(Color a, Color b)
+ {
+ int dsq = 0; // delta squared
+ int v;
+
+ v = a.B - b.B;
+ dsq += v * v;
+ v = a.G - b.G;
+ dsq += v * v;
+ v = a.R - b.R;
+ dsq += v * v;
+
+ return dsq;
+ }
+
+ public int FindClosestPaletteIndex(Color pixel)
+ {
+ int dsqBest = int.MaxValue;
+ int ret = 0;
+
+ for (int i = 0; i < this.palette.Length; ++i)
+ {
+ int dsq = GetDistanceSquared(this.palette[i], pixel);
+
+ if (dsq < dsqBest)
+ {
+ dsqBest = dsq;
+ ret = i;
+ }
+ }
+
+ return ret;
+ }
+
+ public PaletteTable(Color[] palette)
+ {
+ this.palette = (Color[])palette.Clone();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs
new file mode 100644
index 000000000..e63dc1ad8
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Quantizer.cs
@@ -0,0 +1,335 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp
+
+using Tango.RemoteDesktop.Quantization;
+using System;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Runtime.InteropServices;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal unsafe abstract class Quantizer
+ {
+ /// <summary>
+ /// Flag used to indicate whether a single pass or two passes are needed for quantization.
+ /// </summary>
+ private bool singlePass;
+
+ protected int ditherLevel;
+ public int DitherLevel
+ {
+ get
+ {
+ return this.ditherLevel;
+ }
+
+ set
+ {
+ this.ditherLevel = value;
+ }
+ }
+
+ /// <summary>
+ /// Construct the quantizer
+ /// </summary>
+ /// <param name="singlePass">If true, the quantization only needs to loop through the source pixels once</param>
+ /// <remarks>
+ /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image,
+ /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage'
+ /// and then 'QuantizeImage'.
+ /// </remarks>
+ public Quantizer(bool singlePass)
+ {
+ this.singlePass = singlePass;
+ }
+
+ /// <summary>
+ /// Quantize an image and return the resulting output bitmap
+ /// </summary>
+ /// <param name="source">The image to quantize</param>
+ /// <returns>A quantized version of the image</returns>
+ public Bitmap Quantize(Image source)
+ {
+ // Get the size of the source image
+ int height = source.Height;
+ int width = source.Width;
+
+ // And construct a rectangle from these dimensions
+ Rectangle bounds = new Rectangle(0, 0, width, height);
+
+ // First off take a 32bpp version of the image
+ Bitmap img32bpp;
+
+ if (source is Bitmap && source.PixelFormat == PixelFormat.Format32bppArgb)
+ {
+ img32bpp = (Bitmap)source;
+ }
+ else
+ {
+ img32bpp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
+
+ // Now lock the bitmap into memory
+ using (Graphics g = Graphics.FromImage(img32bpp))
+ {
+ g.PageUnit = GraphicsUnit.Pixel;
+
+ // Draw the source image onto the copy bitmap,
+ // which will effect a widening as appropriate.
+ g.DrawImage(source, 0, 0, bounds.Width, bounds.Height);
+ }
+ }
+
+ // And construct an 8bpp version
+ Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
+
+ // Define a pointer to the bitmap data
+ BitmapData sourceData = null;
+
+ try
+ {
+ // Get the source image bits and lock into memory
+ sourceData = img32bpp.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
+
+ // Call the FirstPass function if not a single pass algorithm.
+ // For something like an octree quantizer, this will run through
+ // all image pixels, build a data structure, and create a palette.
+ if (!singlePass)
+ {
+ FirstPass(sourceData, width, height);
+ }
+
+ // Then set the color palette on the output bitmap. I'm passing in the current palette
+ // as there's no way to construct a new, empty palette.
+ output.Palette = this.GetPalette(output.Palette);
+
+ // Then call the second pass which actually does the conversion
+ SecondPass(sourceData, output, width, height, bounds);
+ }
+
+ finally
+ {
+ // Ensure that the bits are unlocked
+ img32bpp.UnlockBits(sourceData);
+ }
+
+ if (img32bpp != source)
+ {
+ img32bpp.Dispose();
+ img32bpp = null;
+ }
+
+ // Last but not least, return the output bitmap
+ return output;
+ }
+
+ /// <summary>
+ /// Execute the first pass through the pixels in the image
+ /// </summary>
+ /// <param name="sourceData">The source data</param>
+ /// <param name="width">The width in pixels of the image</param>
+ /// <param name="height">The height in pixels of the image</param>
+ protected virtual void FirstPass(BitmapData sourceData, int width, int height)
+ {
+ // Define the source data pointers. The source row is a byte to
+ // keep addition of the stride value easier (as this is in bytes)
+ byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer();
+ Int32* pSourcePixel;
+
+ // Loop through each row
+ for (int row = 0; row < height; row++)
+ {
+ // Set the source pixel to the first pixel in this row
+ pSourcePixel = (Int32*)pSourceRow;
+
+ // And loop through each column
+ for (int col = 0; col < width; col++, pSourcePixel++)
+ {
+ InitialQuantizePixel((ColorBgra *)pSourcePixel);
+ }
+
+ // Add the stride to the source row
+ pSourceRow += sourceData.Stride;
+ }
+ }
+
+ /// <summary>
+ /// Execute a second pass through the bitmap
+ /// </summary>
+ /// <param name="sourceData">The source bitmap, locked into memory</param>
+ /// <param name="output">The output bitmap</param>
+ /// <param name="width">The width in pixels of the image</param>
+ /// <param name="height">The height in pixels of the image</param>
+ /// <param name="bounds">The bounding rectangle</param>
+ protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds)
+ {
+ BitmapData outputData = null;
+ Color[] pallete = output.Palette.Entries;
+ int weight = ditherLevel;
+
+ try
+ {
+ // Lock the output bitmap into memory
+ outputData = output.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
+
+ // Define the source data pointers. The source row is a byte to
+ // keep addition of the stride value easier (as this is in bytes)
+ byte* pSourceRow = (byte *)sourceData.Scan0.ToPointer();
+ Int32* pSourcePixel = (Int32 *)pSourceRow;
+
+ // Now define the destination data pointers
+ byte* pDestinationRow = (byte *)outputData.Scan0.ToPointer();
+ byte* pDestinationPixel = pDestinationRow;
+
+ int[] errorThisRowR = new int[width + 1];
+ int[] errorThisRowG = new int[width + 1];
+ int[] errorThisRowB = new int[width + 1];
+
+ for (int row = 0; row < height; row++)
+ {
+ int[] errorNextRowR = new int[width + 1];
+ int[] errorNextRowG = new int[width + 1];
+ int[] errorNextRowB = new int[width + 1];
+
+ int ptrInc;
+
+ if ((row & 1) == 0)
+ {
+ pSourcePixel = (Int32*)pSourceRow;
+ pDestinationPixel = pDestinationRow;
+ ptrInc = +1;
+ }
+ else
+ {
+ pSourcePixel = (Int32*)pSourceRow + width - 1;
+ pDestinationPixel = pDestinationRow + width - 1;
+ ptrInc = -1;
+ }
+
+ // Loop through each pixel on this scan line
+ for (int col = 0; col < width; ++col)
+ {
+ // Quantize the pixel
+ ColorBgra srcPixel = *(ColorBgra *)pSourcePixel;
+ ColorBgra target = new ColorBgra();
+
+ target.B = Utility.ClampToByte(srcPixel.B - ((errorThisRowB[col] * weight) / 8));
+ target.G = Utility.ClampToByte(srcPixel.G - ((errorThisRowG[col] * weight) / 8));
+ target.R = Utility.ClampToByte(srcPixel.R - ((errorThisRowR[col] * weight) / 8));
+ target.A = srcPixel.A;
+
+ byte pixelValue = QuantizePixel(&target);
+ *pDestinationPixel = pixelValue;
+
+ ColorBgra actual = ColorBgra.FromColor(pallete[pixelValue]);
+
+ int errorR = actual.R - target.R;
+ int errorG = actual.G - target.G;
+ int errorB = actual.B - target.B;
+
+ // Floyd-Steinberg Error Diffusion:
+ // a) 7/16 error goes to x+1
+ // b) 5/16 error goes to y+1
+ // c) 3/16 error goes to x-1,y+1
+ // d) 1/16 error goes to x+1,y+1
+
+ const int a = 7;
+ const int b = 5;
+ const int c = 3;
+
+ int errorRa = (errorR * a) / 16;
+ int errorRb = (errorR * b) / 16;
+ int errorRc = (errorR * c) / 16;
+ int errorRd = errorR - errorRa - errorRb - errorRc;
+
+ int errorGa = (errorG * a) / 16;
+ int errorGb = (errorG * b) / 16;
+ int errorGc = (errorG * c) / 16;
+ int errorGd = errorG - errorGa - errorGb - errorGc;
+
+ int errorBa = (errorB * a) / 16;
+ int errorBb = (errorB * b) / 16;
+ int errorBc = (errorB * c) / 16;
+ int errorBd = errorB - errorBa - errorBb - errorBc;
+
+ errorThisRowR[col + 1] += errorRa;
+ errorThisRowG[col + 1] += errorGa;
+ errorThisRowB[col + 1] += errorBa;
+
+ errorNextRowR[width - col] += errorRb;
+ errorNextRowG[width - col] += errorGb;
+ errorNextRowB[width - col] += errorBb;
+
+ if (col != 0)
+ {
+ errorNextRowR[width - (col - 1)] += errorRc;
+ errorNextRowG[width - (col - 1)] += errorGc;
+ errorNextRowB[width - (col - 1)] += errorBc;
+ }
+
+ errorNextRowR[width - (col + 1)] += errorRd;
+ errorNextRowG[width - (col + 1)] += errorGd;
+ errorNextRowB[width - (col + 1)] += errorBd;
+
+ // unchecked is necessary because otherwise it throws a fit if ptrInc is negative.
+ unchecked
+ {
+ pSourcePixel += ptrInc;
+ pDestinationPixel += ptrInc;
+ }
+ }
+
+ // Add the stride to the source row
+ pSourceRow += sourceData.Stride;
+
+ // And to the destination row
+ pDestinationRow += outputData.Stride;
+
+ errorThisRowB = errorNextRowB;
+ errorThisRowG = errorNextRowG;
+ errorThisRowR = errorNextRowR;
+ }
+ }
+
+ finally
+ {
+ // Ensure that I unlock the output bits
+ output.UnlockBits(outputData);
+ }
+ }
+
+ /// <summary>
+ /// Override this to process the pixel in the first pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <remarks>
+ /// This function need only be overridden if your quantize algorithm needs two passes,
+ /// such as an Octree quantizer.
+ /// </remarks>
+ protected virtual void InitialQuantizePixel(ColorBgra *pixel)
+ {
+ }
+
+ /// <summary>
+ /// Override this to process the pixel in the second pass of the algorithm
+ /// </summary>
+ /// <param name="pixel">The pixel to quantize</param>
+ /// <returns>The quantized value</returns>
+ protected abstract byte QuantizePixel(ColorBgra *pixel);
+
+ /// <summary>
+ /// Retrieve the palette for the quantized image
+ /// </summary>
+ /// <param name="original">Any old palette, this is overrwritten</param>
+ /// <returns>The new color palette</returns>
+ protected abstract ColorPalette GetPalette(ColorPalette original);
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Scanline.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Scanline.cs
new file mode 100644
index 000000000..4ce8ac958
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Scanline.cs
@@ -0,0 +1,88 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using System;
+using System.Drawing;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ internal struct Scanline
+ {
+ private int x;
+ private int y;
+ private int length;
+
+ public int X
+ {
+ get
+ {
+ return x;
+ }
+ }
+
+ public int Y
+ {
+ get
+ {
+ return y;
+ }
+ }
+
+ public int Length
+ {
+ get
+ {
+ return length;
+ }
+ }
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return length.GetHashCode() + x.GetHashCode() + y.GetHashCode();
+ }
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (obj is Scanline)
+ {
+ Scanline rhs = (Scanline)obj;
+ return x == rhs.x && y == rhs.y && length == rhs.length;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public static bool operator== (Scanline lhs, Scanline rhs)
+ {
+ return lhs.x == rhs.x && lhs.y == rhs.y && lhs.length == rhs.length;
+ }
+
+ public static bool operator!= (Scanline lhs, Scanline rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+ public override string ToString()
+ {
+ return "(" + x + "," + y + "):[" + length.ToString() + "]";
+ }
+
+ public Scanline(int x, int y, int length)
+ {
+ this.x = x;
+ this.y = y;
+ this.length = length;
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Utility.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Utility.cs
new file mode 100644
index 000000000..74119e9c5
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Quantization/Utility.cs
@@ -0,0 +1,2531 @@
+/////////////////////////////////////////////////////////////////////////////////
+// Paint.NET //
+// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. //
+// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. //
+// See src/Resources/Files/License.txt for full licensing and attribution //
+// details. //
+// . //
+/////////////////////////////////////////////////////////////////////////////////
+
+using Microsoft.Win32;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using System.Runtime.Serialization;
+using System.Runtime.Serialization.Formatters.Binary;
+using System.Text;
+using System.Threading;
+
+namespace Tango.RemoteDesktop.Quantization
+{
+ /// <summary>
+ /// Defines miscellaneous constants and static functions.
+ /// </summary>
+ /// // TODO: refactor into mini static classes
+ internal sealed class Utility
+ {
+ private Utility()
+ {
+ }
+
+ internal static bool IsNumber(float x)
+ {
+ return x >= float.MinValue && x <= float.MaxValue;
+ }
+
+ internal static bool IsNumber(double x)
+ {
+ return x >= double.MinValue && x <= double.MaxValue;
+ }
+
+ internal static int Min(int val0, params int[] vals)
+ {
+ int min = val0;
+
+ for (int i = 0; i < vals.Length; ++i)
+ {
+ if (vals[i] < min)
+ {
+ min = vals[i];
+ }
+ }
+
+ return min;
+ }
+
+ internal static int Max(int val0, params int[] vals)
+ {
+ int max = val0;
+
+ for (int i = 0; i < vals.Length; ++i)
+ {
+ if (vals[i] > max)
+ {
+ max = vals[i];
+ }
+ }
+
+ return max;
+ }
+
+ public static PointF[] GetRgssOffsets(int quality)
+ {
+ unsafe
+ {
+ int sampleCount = quality * quality;
+ PointF[] samplesArray = new PointF[sampleCount];
+
+ fixed (PointF* pSamplesArray = samplesArray)
+ {
+ GetRgssOffsets(pSamplesArray, sampleCount, quality);
+ }
+
+ return samplesArray;
+ }
+ }
+
+ public static unsafe void GetRgssOffsets(PointF* samplesArray, int sampleCount, int quality)
+ {
+ if (sampleCount < 1)
+ {
+ throw new ArgumentOutOfRangeException("sampleCount", "sampleCount must be [0, int.MaxValue]");
+ }
+
+ if (sampleCount != quality * quality)
+ {
+ throw new ArgumentOutOfRangeException("sampleCount != (quality * quality)");
+ }
+
+ if (sampleCount == 1)
+ {
+ samplesArray[0] = new PointF(0.0f, 0.0f);
+ }
+ else
+ {
+ for (int i = 0; i < sampleCount; ++i)
+ {
+ double y = (i + 1d) / (sampleCount + 1d);
+ double x = y * quality;
+
+ x -= (int)x;
+
+ samplesArray[i] = new PointF((float)(x - 0.5d), (float)(y - 0.5d));
+ }
+ }
+ }
+
+ public static bool IsObsolete(Type type, bool inherit)
+ {
+ object[] attrs = type.GetCustomAttributes(typeof(ObsoleteAttribute), inherit);
+ return (attrs.Length != 0);
+ }
+
+ public static void DrawDropShadow1px(Graphics g, Rectangle rect)
+ {
+ Brush b0 = new SolidBrush(Color.FromArgb(15, Color.Black));
+ Brush b1 = new SolidBrush(Color.FromArgb(47, Color.Black));
+ Pen p2 = new Pen(Color.FromArgb(63, Color.Black));
+
+ g.FillRectangle(b0, rect.Left, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Left + 1, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Left, rect.Top + 1, 1, 1);
+
+ g.FillRectangle(b0, rect.Right - 1, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Right - 2, rect.Top, 1, 1);
+ g.FillRectangle(b1, rect.Right - 1, rect.Top + 1, 1, 1);
+
+ g.FillRectangle(b0, rect.Left, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Left + 1, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Left, rect.Bottom - 2, 1, 1);
+
+ g.FillRectangle(b0, rect.Right - 1, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Right - 2, rect.Bottom - 1, 1, 1);
+ g.FillRectangle(b1, rect.Right - 1, rect.Bottom - 2, 1, 1);
+
+ g.DrawLine(p2, rect.Left + 2, rect.Top, rect.Right - 3, rect.Top);
+ g.DrawLine(p2, rect.Left, rect.Top + 2, rect.Left, rect.Bottom - 3);
+ g.DrawLine(p2, rect.Left + 2, rect.Bottom - 1, rect.Right - 3, rect.Bottom - 1);
+ g.DrawLine(p2, rect.Right - 1, rect.Top + 2, rect.Right - 1, rect.Bottom - 3);
+
+ b0.Dispose();
+ b0 = null;
+ b1.Dispose();
+ b1 = null;
+ p2.Dispose();
+ p2 = null;
+ }
+
+ public static void DrawColorRectangle(Graphics g, Rectangle rect, Color color, bool drawBorder)
+ {
+ int inflateAmt = drawBorder ? -2 : 0;
+ Rectangle colorRectangle = Rectangle.Inflate(rect, inflateAmt, inflateAmt);
+ Brush colorBrush = new LinearGradientBrush(colorRectangle, Color.FromArgb(255, color), color, 90.0f, false);
+ HatchBrush backgroundBrush = new HatchBrush(HatchStyle.LargeCheckerBoard, Color.FromArgb(191, 191, 191), Color.FromArgb(255, 255, 255));
+
+ if (drawBorder)
+ {
+ g.DrawRectangle(Pens.Black, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1);
+ g.DrawRectangle(Pens.White, rect.Left + 1, rect.Top + 1, rect.Width - 3, rect.Height - 3);
+ }
+
+ PixelOffsetMode oldPOM = g.PixelOffsetMode;
+ g.PixelOffsetMode = PixelOffsetMode.Half;
+ g.FillRectangle(backgroundBrush, colorRectangle);
+ g.FillRectangle(colorBrush, colorRectangle);
+ g.PixelOffsetMode = oldPOM;
+
+ backgroundBrush.Dispose();
+ colorBrush.Dispose();
+ }
+
+ public static Size ComputeThumbnailSize(Size originalSize, int maxEdgeLength)
+ {
+ Size thumbSize;
+
+ if (originalSize.Width > originalSize.Height)
+ {
+ int longSide = Math.Min(originalSize.Width, maxEdgeLength);
+ thumbSize = new Size(longSide, Math.Max(1, (originalSize.Height * longSide) / originalSize.Width));
+ }
+ else if (originalSize.Height > originalSize.Width)
+ {
+ int longSide = Math.Min(originalSize.Height, maxEdgeLength);
+ thumbSize = new Size(Math.Max(1, (originalSize.Width * longSide) / originalSize.Height), longSide);
+ }
+ else // if (docSize.Width == docSize.Height)
+ {
+ int longSide = Math.Min(originalSize.Width, maxEdgeLength);
+ thumbSize = new Size(longSide, longSide);
+ }
+
+ return thumbSize;
+ }
+
+ public static Font CreateFont(string name, float size, FontStyle style)
+ {
+ Font returnFont;
+
+ try
+ {
+ returnFont = new Font(name, size, style);
+ }
+
+ catch (Exception)
+ {
+ returnFont = new Font(FontFamily.GenericSansSerif, size);
+ }
+
+ return returnFont;
+ }
+
+ public static Font CreateFont(string name, float size, string backupName, float backupSize, FontStyle style)
+ {
+ Font returnFont;
+
+ try
+ {
+ returnFont = new Font(name, size, style);
+ }
+
+ catch (Exception)
+ {
+ returnFont = CreateFont(backupName, backupSize, style);
+ }
+
+ return returnFont;
+ }
+
+ public static readonly Color TransparentKey = Color.FromArgb(192, 192, 192);
+
+ private static bool allowGCFullCollect = true;
+ public static bool AllowGCFullCollect
+ {
+ get
+ {
+ return allowGCFullCollect;
+ }
+
+ set
+ {
+ allowGCFullCollect = value;
+ }
+ }
+
+ public static void GCFullCollect()
+ {
+ if (AllowGCFullCollect)
+ {
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ GC.Collect();
+ GC.WaitForPendingFinalizers();
+ }
+ }
+
+ private static int defaultSimplificationFactor = 50;
+ public static int DefaultSimplificationFactor
+ {
+ get
+ {
+ return defaultSimplificationFactor;
+ }
+
+ set
+ {
+ defaultSimplificationFactor = value;
+ }
+ }
+
+ public static void SplitRectangle(Rectangle rect, Rectangle[] rects)
+ {
+ int height = rect.Height;
+
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ Rectangle newRect = Rectangle.FromLTRB(rect.Left,
+ rect.Top + ((height * i) / rects.Length),
+ rect.Right,
+ rect.Top + ((height * (i + 1)) / rects.Length));
+
+ rects[i] = newRect;
+ }
+ }
+
+ public static long TicksToMs(long ticks)
+ {
+ return ticks / 10000;
+ }
+
+ public static string GetStaticName(Type type)
+ {
+ PropertyInfo pi = type.GetProperty("StaticName", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty);
+ return (string)pi.GetValue(null, null);
+ }
+
+ public static readonly float[][] Identity5x5F = new float[][] {
+ new float[] { 1, 0, 0, 0, 0 },
+ new float[] { 0, 1, 0, 0, 0 },
+ new float[] { 0, 0, 1, 0, 0 },
+ new float[] { 0, 0, 0, 1, 0 },
+ new float[] { 0, 0, 0, 0, 1 }
+ };
+
+ public static readonly ColorMatrix IdentityColorMatrix = new ColorMatrix(Identity5x5F);
+
+ [ThreadStatic]
+ private static Matrix identityMatrix = null;
+ public static Matrix IdentityMatrix
+ {
+ get
+ {
+ if (identityMatrix == null)
+ {
+ identityMatrix = new Matrix();
+ identityMatrix.Reset();
+ }
+
+ return identityMatrix;
+ }
+ }
+
+ /// <summary>
+ /// Rounds an integer to the smallest power of 2 that is greater
+ /// than or equal to it.
+ /// </summary>
+ public static int Log2RoundUp(int x)
+ {
+ if (x == 0)
+ {
+ return 1;
+ }
+
+ if (x == 1)
+ {
+ return 1;
+ }
+
+ return 1 << (1 + HighestBit(x - 1));
+ }
+
+ private static int HighestBit(int x)
+ {
+ if (x == 0)
+ {
+ return 0;
+ }
+
+ int b = 0;
+ int hi = 0;
+
+ while (b <= 30)
+ {
+ if ((x & (1 << b)) != 0)
+ {
+ hi = b;
+ }
+
+ ++b;
+ }
+
+ return hi;
+ }
+
+ private int CountBits(int x)
+ {
+ uint y = (uint)x;
+ int count = 0;
+
+ for (int bit = 0; bit < 32; ++bit)
+ {
+ if ((y & ((uint)1 << bit)) != 0)
+ {
+ ++count;
+ }
+ }
+
+ return count;
+ }
+
+ public static string RemoveSpaces(string s)
+ {
+ StringBuilder sb = new StringBuilder();
+
+ foreach (char c in s)
+ {
+ if (!char.IsWhiteSpace(c))
+ {
+ sb.Append(c);
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ public static int Max(int[,] array)
+ {
+ int max = int.MinValue;
+
+ for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i)
+ {
+ for (int j = array.GetLowerBound(1); j <= array.GetUpperBound(1); ++j)
+ {
+ if (array[i,j] > max)
+ {
+ max = array[i,j];
+ }
+ }
+ }
+
+ return max;
+ }
+
+ public static int Sum(int[][] array)
+ {
+ int sum = 0;
+
+ for (int i = 0; i < array.Length; ++i)
+ {
+ int[] row = array[i];
+
+ for (int j = 0; j < row.Length; ++j)
+ {
+ sum += row[j];
+ }
+ }
+
+ return sum;
+ }
+
+ public static Icon ImageToIcon(Image image)
+ {
+ return ImageToIcon(image, Utility.TransparentKey);
+ }
+
+ public static Icon ImageToIcon(Image image, bool disposeImage)
+ {
+ return ImageToIcon(image, Utility.TransparentKey, disposeImage);
+ }
+
+ public static Icon ImageToIcon(Image image, Color seeThru)
+ {
+ return ImageToIcon(image, seeThru, false);
+ }
+
+ /// <summary>
+ /// Converts an Image to an Icon.
+ /// </summary>
+ /// <param name="image">The Image to convert to an icon. Must be an appropriate icon size (32x32, 16x16, etc).</param>
+ /// <param name="seeThru">The color that will be treated as transparent in the icon.</param>
+ /// <param name="disposeImage">Whether or not to dispose the passed-in Image.</param>
+ /// <returns>An Icon representation of the Image.</returns>
+ public static Icon ImageToIcon(Image image, Color seeThru, bool disposeImage)
+ {
+ Bitmap bitmap = new Bitmap(image);
+
+ for (int y = 0; y < bitmap.Height; ++y)
+ {
+ for (int x = 0; x < bitmap.Width; ++x)
+ {
+ if (bitmap.GetPixel(x, y) == seeThru)
+ {
+ bitmap.SetPixel(x, y, Color.FromArgb(0));
+ }
+ }
+ }
+
+ Icon icon = Icon.FromHandle(bitmap.GetHicon());
+ bitmap.Dispose();
+
+ if (disposeImage)
+ {
+ image.Dispose();
+ }
+
+ return icon;
+ }
+
+ public static Icon BitmapToIcon(Bitmap bitmap, bool disposeBitmap)
+ {
+ Icon icon = Icon.FromHandle(bitmap.GetHicon());
+
+ if (disposeBitmap)
+ {
+ bitmap.Dispose();
+ }
+
+ return icon;
+ }
+
+ public static Point GetRectangleCenter(Rectangle rect)
+ {
+ return new Point((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2);
+ }
+
+ public static PointF GetRectangleCenter(RectangleF rect)
+ {
+ return new PointF((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2);
+ }
+
+ public static Scanline[] GetRectangleScans(Rectangle rect)
+ {
+ Scanline[] scans = new Scanline[rect.Height];
+
+ for (int y = 0; y < rect.Height; ++y)
+ {
+ scans[y] = new Scanline(rect.X, rect.Y + y, rect.Width);
+ }
+
+ return scans;
+ }
+
+ public static Scanline[] GetRegionScans(Rectangle[] region)
+ {
+ int scanCount = 0;
+
+ for (int i = 0; i < region.Length; ++i)
+ {
+ scanCount += region[i].Height;
+ }
+
+ Scanline[] scans = new Scanline[scanCount];
+ int scanIndex = 0;
+
+ foreach (Rectangle rect in region)
+ {
+ for (int y = 0; y < rect.Height; ++y)
+ {
+ scans[scanIndex] = new Scanline(rect.X, rect.Y + y, rect.Width);
+ ++scanIndex;
+ }
+ }
+
+ return scans;
+ }
+
+ public static Rectangle[] ScanlinesToRectangles(Scanline[] scans)
+ {
+ return ScanlinesToRectangles(scans, 0, scans.Length);
+ }
+
+ public static Rectangle[] ScanlinesToRectangles(Scanline[] scans, int startIndex, int length)
+ {
+ Rectangle[] rects = new Rectangle[length];
+
+ for (int i = 0; i < length; ++i)
+ {
+ Scanline scan = scans[i + startIndex];
+ rects[i] = new Rectangle(scan.X, scan.Y, scan.Length, 1);
+ }
+
+ return rects;
+ }
+
+ public static RectangleF RectangleFromCenter(PointF center, float halfSize)
+ {
+ RectangleF ret = new RectangleF(center.X, center.Y, 0, 0);
+ ret.Inflate(halfSize, halfSize);
+ return ret;
+ }
+
+ public static List<PointF> PointListToPointFList(List<Point> ptList)
+ {
+ List<PointF> ret = new List<PointF>(ptList.Count);
+
+ for (int i = 0; i < ptList.Count; ++i)
+ {
+ ret.Add((PointF)ptList[i]);
+ }
+
+ return ret;
+ }
+
+ public static PointF[] PointArrayToPointFArray(Point[] ptArray)
+ {
+ PointF[] ret = new PointF[ptArray.Length];
+
+ for (int i = 0; i < ret.Length; ++i)
+ {
+ ret[i] = (PointF)ptArray[i];
+ }
+
+ return ret;
+ }
+
+ public static Rectangle[] InflateRectangles(Rectangle[] rects, int amount)
+ {
+ Rectangle[] inflated = new Rectangle[rects.Length];
+
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ inflated[i] = Rectangle.Inflate(rects[i], amount, amount);
+ }
+
+ return inflated;
+ }
+
+ public static void InflateRectanglesInPlace(Rectangle[] rects, int amount)
+ {
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ rects[i].Inflate(amount, amount);
+ }
+ }
+
+ public static RectangleF[] InflateRectangles(RectangleF[] rectsF, int amount)
+ {
+ RectangleF[] inflated = new RectangleF[rectsF.Length];
+
+ for (int i = 0; i < rectsF.Length; ++i)
+ {
+ inflated[i] = RectangleF.Inflate(rectsF[i], amount, amount);
+ }
+
+ return inflated;
+ }
+
+ public static void InflateRectanglesInPlace(RectangleF[] rectsF, float amount)
+ {
+ for (int i = 0; i < rectsF.Length; ++i)
+ {
+ rectsF[i].Inflate(amount, amount);
+ }
+ }
+
+ public static Rectangle PointsToConstrainedRectangle(Point a, Point b)
+ {
+ Rectangle rect = Utility.PointsToRectangle(a, b);
+ int minWH = Math.Min(rect.Width, rect.Height);
+
+ rect.Width = minWH;
+ rect.Height = minWH;
+
+ if (rect.Y != a.Y)
+ {
+ rect.Location = new Point(rect.X, a.Y - minWH);
+ }
+
+ if (rect.X != a.X)
+ {
+ rect.Location = new Point(a.X - minWH, rect.Y);
+ }
+
+ return rect;
+ }
+
+ public static RectangleF PointsToConstrainedRectangle(PointF a, PointF b)
+ {
+ RectangleF rect = Utility.PointsToRectangle(a, b);
+ float minWH = Math.Min(rect.Width, rect.Height);
+
+ rect.Width = minWH;
+ rect.Height = minWH;
+
+ if (rect.Y != a.Y)
+ {
+ rect.Location = new PointF(rect.X, a.Y - minWH);
+ }
+
+ if (rect.X != a.X)
+ {
+ rect.Location = new PointF(a.X - minWH, rect.Y);
+ }
+
+ return rect;
+ }
+
+ /// <summary>
+ /// Takes two points and creates a bounding rectangle from them.
+ /// </summary>
+ /// <param name="a">One corner of the rectangle.</param>
+ /// <param name="b">The other corner of the rectangle.</param>
+ /// <returns>A Rectangle instance that bounds the two points.</returns>
+ public static Rectangle PointsToRectangle(Point a, Point b)
+ {
+ int x = Math.Min(a.X, b.X);
+ int y = Math.Min(a.Y, b.Y);
+ int width = Math.Abs(a.X - b.X) + 1;
+ int height = Math.Abs(a.Y - b.Y) + 1;
+
+ return new Rectangle(x, y, width, height);
+ }
+
+ public static RectangleF PointsToRectangle(PointF a, PointF b)
+ {
+ float x = Math.Min(a.X, b.X);
+ float y = Math.Min(a.Y, b.Y);
+ float width = Math.Abs(a.X - b.X) + 1;
+ float height = Math.Abs(a.Y - b.Y) + 1;
+
+ return new RectangleF(x, y, width, height);
+ }
+
+ public static Rectangle PointsToRectangleExclusive(Point a, Point b)
+ {
+ int x = Math.Min(a.X, b.X);
+ int y = Math.Min(a.Y, b.Y);
+ int width = Math.Abs(a.X - b.X);
+ int height = Math.Abs(a.Y - b.Y);
+
+ return new Rectangle(x, y, width, height);
+ }
+
+ public static RectangleF PointsToRectangleExclusive(PointF a, PointF b)
+ {
+ float x = Math.Min(a.X, b.X);
+ float y = Math.Min(a.Y, b.Y);
+ float width = Math.Abs(a.X - b.X);
+ float height = Math.Abs(a.Y - b.Y);
+
+ return new RectangleF(x, y, width, height);
+ }
+
+ public static RectangleF[] PointsToRectangles(PointF[] pointsF)
+ {
+ if (pointsF.Length == 0)
+ {
+ return new RectangleF[] { };
+ }
+
+ if (pointsF.Length == 1)
+ {
+ return new RectangleF[] { new RectangleF(pointsF[0].X, pointsF[0].Y, 1, 1) };
+ }
+
+ RectangleF[] rectsF = new RectangleF[pointsF.Length - 1];
+
+ for (int i = 0; i < pointsF.Length - 1; ++i)
+ {
+ rectsF[i] = PointsToRectangle(pointsF[i], pointsF[i + 1]);
+ }
+
+ return rectsF;
+ }
+
+ public static Rectangle[] PointsToRectangles(Point[] points)
+ {
+ if (points.Length == 0)
+ {
+ return new Rectangle[] { };
+ }
+
+ if (points.Length == 1)
+ {
+ return new Rectangle[] { new Rectangle(points[0].X, points[0].Y, 1, 1) };
+ }
+
+ Rectangle[] rects = new Rectangle[points.Length - 1];
+
+ for (int i = 0; i < points.Length - 1; ++i)
+ {
+ rects[i] = PointsToRectangle(points[i], points[i + 1]);
+ }
+
+ return rects;
+ }
+
+ /// <summary>
+ /// Converts a RectangleF to RectangleF by rounding down the Location and rounding
+ /// up the Size.
+ /// </summary>
+ public static Rectangle RoundRectangle(RectangleF rectF)
+ {
+ float left = (float)Math.Floor(rectF.Left);
+ float top = (float)Math.Floor(rectF.Top);
+ float right = (float)Math.Ceiling(rectF.Right);
+ float bottom = (float)Math.Ceiling(rectF.Bottom);
+
+ return Rectangle.Truncate(RectangleF.FromLTRB(left, top, right, bottom));
+ }
+
+ public static Stack Reverse(Stack reverseMe)
+ {
+ Stack reversed = new Stack();
+
+ foreach (object o in reverseMe)
+ {
+ reversed.Push(o);
+ }
+
+ return reversed;
+ }
+
+ public static void SerializeObjectToStream(object graph, Stream stream)
+ {
+ new BinaryFormatter().Serialize(stream, graph);
+ }
+
+ public static object DeserializeObjectFromStream(Stream stream)
+ {
+ return new BinaryFormatter().Deserialize(stream);
+ }
+
+ [Obsolete("Use rect.Contains() instead", true)]
+ public static bool IsPointInRectangle(Point pt, Rectangle rect)
+ {
+ return rect.Contains(pt);
+ }
+
+ [Obsolete("Use rect.Contains() instead", true)]
+ public static bool IsPointInRectangle(int x, int y, Rectangle rect)
+ {
+ return rect.Contains(x, y);
+ }
+
+ public static Bitmap FullCloneBitmap(Bitmap cloneMe)
+ {
+ Bitmap bitmap = new Bitmap(cloneMe.Width, cloneMe.Height, cloneMe.PixelFormat);
+
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.DrawImage(cloneMe, 0, 0, cloneMe.Width, cloneMe.Height);
+ }
+
+ return bitmap;
+ }
+
+ /// <summary>
+ /// Allows you to find the bounding box for a "region" that is described as an
+ /// array of bounding boxes.
+ /// </summary>
+ /// <param name="rectsF">The "region" you want to find a bounding box for.</param>
+ /// <returns>A RectangleF structure that surrounds the Region.</returns>
+ public static RectangleF GetRegionBounds(RectangleF[] rectsF, int startIndex, int length)
+ {
+ if (rectsF.Length == 0)
+ {
+ return RectangleF.Empty;
+ }
+
+ float left = rectsF[startIndex].Left;
+ float top = rectsF[startIndex].Top;
+ float right = rectsF[startIndex].Right;
+ float bottom = rectsF[startIndex].Bottom;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ RectangleF rectF = rectsF[i];
+
+ if (rectF.Left < left)
+ {
+ left = rectF.Left;
+ }
+
+ if (rectF.Top < top)
+ {
+ top = rectF.Top;
+ }
+
+ if (rectF.Right > right)
+ {
+ right = rectF.Right;
+ }
+
+ if (rectF.Bottom > bottom)
+ {
+ bottom = rectF.Bottom;
+ }
+ }
+
+ return RectangleF.FromLTRB(left, top, right, bottom);
+ }
+
+ public static RectangleF GetTraceBounds(PointF[] pointsF, int startIndex, int length)
+ {
+ if (pointsF.Length == 0)
+ {
+ return RectangleF.Empty;
+ }
+
+ float left = pointsF[startIndex].X;
+ float top = pointsF[startIndex].Y;
+ float right = 1 + pointsF[startIndex].X;
+ float bottom = 1 + pointsF[startIndex].Y;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ PointF pointF = pointsF[i];
+
+ if (pointF.X < left)
+ {
+ left = pointF.X;
+ }
+
+ if (pointF.Y < top)
+ {
+ top = pointF.Y;
+ }
+
+ if (pointF.X > right)
+ {
+ right = pointF.X;
+ }
+
+ if (pointF.Y > bottom)
+ {
+ bottom = pointF.Y;
+ }
+ }
+
+ return RectangleF.FromLTRB(left, top, right, bottom);
+ }
+
+ public static Rectangle GetTraceBounds(Point[] points, int startIndex, int length)
+ {
+ if (points.Length == 0)
+ {
+ return Rectangle.Empty;
+ }
+
+ int left = points[startIndex].X;
+ int top = points[startIndex].Y;
+ int right = 1 + points[startIndex].X;
+ int bottom = 1 + points[startIndex].Y;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ Point point = points[i];
+
+ if (point.X < left)
+ {
+ left = point.X;
+ }
+
+ if (point.Y < top)
+ {
+ top = point.Y;
+ }
+
+ if (point.X > right)
+ {
+ right = point.X;
+ }
+
+ if (point.Y > bottom)
+ {
+ bottom = point.Y;
+ }
+ }
+
+ return Rectangle.FromLTRB(left, top, right, bottom);
+ }
+
+ /// <summary>
+ /// Allows you to find the bounding box for a "region" that is described as an
+ /// array of bounding boxes.
+ /// </summary>
+ /// <param name="rectsF">The "region" you want to find a bounding box for.</param>
+ /// <returns>A RectangleF structure that surrounds the Region.</returns>
+ public static Rectangle GetRegionBounds(Rectangle[] rects, int startIndex, int length)
+ {
+ if (rects.Length == 0)
+ {
+ return Rectangle.Empty;
+ }
+
+ int left = rects[startIndex].Left;
+ int top = rects[startIndex].Top;
+ int right = rects[startIndex].Right;
+ int bottom = rects[startIndex].Bottom;
+
+ for (int i = startIndex + 1; i < startIndex + length; ++i)
+ {
+ Rectangle rect = rects[i];
+
+ if (rect.Left < left)
+ {
+ left = rect.Left;
+ }
+
+ if (rect.Top < top)
+ {
+ top = rect.Top;
+ }
+
+ if (rect.Right > right)
+ {
+ right = rect.Right;
+ }
+
+ if (rect.Bottom > bottom)
+ {
+ bottom = rect.Bottom;
+ }
+ }
+
+ return Rectangle.FromLTRB(left, top, right, bottom);
+ }
+
+ public static RectangleF GetRegionBounds(RectangleF[] rectsF)
+ {
+ return GetRegionBounds(rectsF, 0, rectsF.Length);
+ }
+
+ public static Rectangle GetRegionBounds(Rectangle[] rects)
+ {
+ return GetRegionBounds(rects, 0, rects.Length);
+ }
+
+ private static float DistanceSquared(RectangleF[] rectsF, int indexA, int indexB)
+ {
+ PointF centerA = new PointF(rectsF[indexA].Left + (rectsF[indexA].Width / 2), rectsF[indexA].Top + (rectsF[indexA].Height / 2));
+ PointF centerB = new PointF(rectsF[indexB].Left + (rectsF[indexB].Width / 2), rectsF[indexB].Top + (rectsF[indexB].Height / 2));
+
+ return ((centerA.X - centerB.X) * (centerA.X - centerB.X)) +
+ ((centerA.Y - centerB.Y) * (centerA.Y - centerB.Y));
+ }
+
+ public static Rectangle[] SimplifyRegion(Rectangle[] rects, int complexity)
+ {
+ if (complexity == 0 || rects.Length < complexity)
+ {
+ return (Rectangle[])rects.Clone();
+ }
+
+ Rectangle[] boxes = new Rectangle[complexity];
+
+ for (int i = 0; i < complexity; ++i)
+ {
+ int startIndex = (i * rects.Length) / complexity;
+ int length = Math.Min(rects.Length, ((i + 1) * rects.Length) / complexity) - startIndex;
+ boxes[i] = GetRegionBounds(rects, startIndex, length);
+ }
+
+ return boxes;
+ }
+
+
+ public static RectangleF[] SimplifyTrace(PointF[] pointsF, int complexity)
+ {
+ if (complexity == 0 ||
+ (pointsF.Length - 1) < complexity)
+ {
+ return PointsToRectangles(pointsF);
+ }
+
+ RectangleF[] boxes = new RectangleF[complexity];
+ int parLength = pointsF.Length - 1; // "(points as Rectangles).Length"
+
+ for (int i = 0; i < complexity; ++i)
+ {
+ int startIndex = (i * parLength) / complexity;
+ int length = Math.Min(parLength, ((i + 1) * parLength) / complexity) - startIndex;
+ boxes[i] = GetTraceBounds(pointsF, startIndex, length + 1);
+ }
+
+ return boxes;
+ }
+
+ public static RectangleF[] SimplifyTrace(PointF[] pointsF)
+ {
+ return SimplifyTrace(pointsF, defaultSimplificationFactor);
+ }
+
+ public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects, int complexity, int inflationAmount)
+ {
+ Rectangle[] simplified = SimplifyRegion(rects, complexity);
+
+ for (int i = 0; i < simplified.Length; ++i)
+ {
+ simplified[i].Inflate(inflationAmount, inflationAmount);
+ }
+
+ return simplified;
+ }
+
+ public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects)
+ {
+ return SimplifyAndInflateRegion(rects, defaultSimplificationFactor, 1);
+ }
+
+ public static RectangleF[] TranslateRectangles(RectangleF[] rectsF, PointF offset)
+ {
+ RectangleF[] retRectsF = new RectangleF[rectsF.Length];
+ int i = 0;
+
+ foreach (RectangleF rectF in rectsF)
+ {
+ retRectsF[i] = new RectangleF(rectF.X + offset.X, rectF.Y + offset.Y, rectF.Width, rectF.Height);
+ ++i;
+ }
+
+ return retRectsF;
+ }
+
+ public static Rectangle[] TranslateRectangles(Rectangle[] rects, int dx, int dy)
+ {
+ Rectangle[] retRects = new Rectangle[rects.Length];
+
+ for (int i = 0; i < rects.Length; ++i)
+ {
+ retRects[i] = new Rectangle(rects[i].X + dx, rects[i].Y + dy, rects[i].Width, rects[i].Height);
+ }
+
+ return retRects;
+ }
+
+ public static void TranslatePointsInPlace(PointF[] ptsF, float dx, float dy)
+ {
+ for (int i = 0; i < ptsF.Length; ++i)
+ {
+ ptsF[i].X += dx;
+ ptsF[i].Y += dy;
+ }
+ }
+
+ public static void TranslatePointsInPlace(Point[] pts, int dx, int dy)
+ {
+ for (int i = 0; i < pts.Length; ++i)
+ {
+ pts[i].X += dx;
+ pts[i].Y += dy;
+ }
+ }
+
+ public static Rectangle[] TruncateRectangles(RectangleF[] rectsF)
+ {
+ Rectangle[] rects = new Rectangle[rectsF.Length];
+
+ for (int i = 0; i < rectsF.Length; ++i)
+ {
+ rects[i] = Rectangle.Truncate(rectsF[i]);
+ }
+
+ return rects;
+ }
+
+ public static Point[] TruncatePoints(PointF[] pointsF)
+ {
+ Point[] points = new Point[pointsF.Length];
+
+ for (int i = 0; i < pointsF.Length; ++i)
+ {
+ points[i] = Point.Truncate(pointsF[i]);
+ }
+
+ return points;
+ }
+
+ public static Point[] RoundPoints(PointF[] pointsF)
+ {
+ Point[] points = new Point[pointsF.Length];
+
+ for (int i = 0; i < pointsF.Length; ++i)
+ {
+ points[i] = Point.Round(pointsF[i]);
+ }
+
+ return points;
+ }
+
+ /// <summary>
+ /// The Sutherland-Hodgman clipping alrogithm.
+ /// http://ezekiel.vancouver.wsu.edu/~cs442/lectures/clip/clip/index.html
+ ///
+ /// # Clipping a convex polygon to a convex region (e.g., rectangle) will always produce a convex polygon (or no polygon if completely outside the clipping region).
+ /// # Clipping a concave polygon to a rectangle may produce several polygons (see figure above) or, as the following algorithm does, produce a single, possibly degenerate, polygon.
+ /// # Divide and conquer: Clip entire polygon against a single edge (i.e., half-plane). Repeat for each edge in the clipping region.
+ ///
+ /// The input is a sequence of vertices: {v0, v1, ... vn} given as an array of Points
+ /// the result is a sequence of vertices, given as an array of Points. This result may have
+ /// less than, equal, more than, or 0 vertices.
+ /// </summary>
+ /// <param name="vertices"></param>
+ /// <returns></returns>
+ public static List<PointF> SutherlandHodgman(RectangleF bounds, List<PointF> v)
+ {
+ List<PointF> p1 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Left, v);
+ List<PointF> p2 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Right, p1);
+ List<PointF> p3 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Top, p2);
+ List<PointF> p4 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Bottom, p3);
+
+ return p4;
+ }
+
+ private enum RectangleEdge
+ {
+ Left,
+ Right,
+ Top,
+ Bottom
+ }
+
+ private static List<PointF> SutherlandHodgmanOneAxis(RectangleF bounds, RectangleEdge edge, List<PointF> v)
+ {
+ if (v.Count == 0)
+ {
+ return new List<PointF>();
+ }
+
+ List<PointF> polygon = new List<PointF>();
+
+ PointF s = v[v.Count - 1];
+
+ for (int i = 0; i < v.Count; ++i)
+ {
+ PointF p = v[i];
+ bool pIn = IsInside(bounds, edge, p);
+ bool sIn = IsInside(bounds, edge, s);
+
+ if (sIn && pIn)
+ {
+ // case 1: inside -> inside
+ polygon.Add(p);
+ }
+ else if (sIn && !pIn)
+ {
+ // case 2: inside -> outside
+ polygon.Add(LineIntercept(bounds, edge, s, p));
+ }
+ else if (!sIn && !pIn)
+ {
+ // case 3: outside -> outside
+ // emit nothing
+ }
+ else if (!sIn && pIn)
+ {
+ // case 4: outside -> inside
+ polygon.Add(LineIntercept(bounds, edge, s, p));
+ polygon.Add(p);
+ }
+
+ s = p;
+ }
+
+ return polygon;
+ }
+
+ private static bool IsInside(RectangleF bounds, RectangleEdge edge, PointF p)
+ {
+ switch (edge)
+ {
+ case RectangleEdge.Left:
+ return !(p.X < bounds.Left);
+
+ case RectangleEdge.Right:
+ return !(p.X >= bounds.Right);
+
+ case RectangleEdge.Top:
+ return !(p.Y < bounds.Top);
+
+ case RectangleEdge.Bottom:
+ return !(p.Y >= bounds.Bottom);
+
+ default:
+ throw new InvalidEnumArgumentException("edge");
+ }
+ }
+
+ private static Point LineIntercept(Rectangle bounds, RectangleEdge edge, Point a, Point b)
+ {
+ if (a == b)
+ {
+ return a;
+ }
+
+ switch (edge)
+ {
+ case RectangleEdge.Bottom:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom);
+
+ case RectangleEdge.Left:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Right:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Top:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new Point(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top);
+ }
+
+ throw new ArgumentException("no intercept found");
+ }
+
+ private static PointF LineIntercept(RectangleF bounds, RectangleEdge edge, PointF a, PointF b)
+ {
+ if (a == b)
+ {
+ return a;
+ }
+
+ switch (edge)
+ {
+ case RectangleEdge.Bottom:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom);
+
+ case RectangleEdge.Left:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Right:
+ if (b.X == a.X)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X)));
+
+ case RectangleEdge.Top:
+ if (b.Y == a.Y)
+ {
+ throw new ArgumentException("no intercept found");
+ }
+
+ return new PointF(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top);
+ }
+
+ throw new ArgumentException("no intercept found");
+ }
+
+ public static Point[] GetLinePoints(Point first, Point second)
+ {
+ Point[] coords = null;
+
+ int x1 = first.X;
+ int y1 = first.Y;
+ int x2 = second.X;
+ int y2 = second.Y;
+ int dx = x2 - x1;
+ int dy = y2 - y1;
+ int dxabs = Math.Abs(dx);
+ int dyabs = Math.Abs(dy);
+ int px = x1;
+ int py = y1;
+ int sdx = Math.Sign(dx);
+ int sdy = Math.Sign(dy);
+ int x = 0;
+ int y = 0;
+
+ if (dxabs > dyabs)
+ {
+ coords = new Point[dxabs + 1];
+
+ for (int i = 0; i <= dxabs; i++)
+ {
+ y += dyabs;
+
+ if (y >= dxabs)
+ {
+ y -= dxabs;
+ py += sdy;
+ }
+
+ coords[i] = new Point(px, py);
+ px += sdx;
+ }
+ }
+ else
+ // had to add in this cludge for slopes of 1 ... wasn't drawing half the line
+ if (dxabs == dyabs)
+ {
+ coords = new Point[dxabs + 1];
+
+ for (int i = 0; i <= dxabs; i++)
+ {
+ coords[i] = new Point(px, py);
+ px += sdx;
+ py += sdy;
+ }
+ }
+ else
+ {
+ coords = new Point[dyabs + 1];
+
+ for (int i = 0; i <= dyabs; i++)
+ {
+ x += dxabs;
+
+ if (x >= dyabs)
+ {
+ x -= dyabs;
+ px += sdx;
+ }
+
+ coords[i] = new Point(px, py);
+ py += sdy;
+ }
+ }
+
+ return coords;
+ }
+
+ public static long GetTimeMs()
+ {
+ return Utility.TicksToMs(DateTime.Now.Ticks);
+ }
+
+ /// <summary>
+ /// Returns the Distance between two points
+ /// </summary>
+ public static float Distance(PointF a, PointF b)
+ {
+ return Magnitude(new PointF(a.X - b.X, a.Y - b.Y));
+ }
+
+ /// <summary>
+ /// Returns the Magnitude (distance to origin) of a point
+ /// </summary>
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static float Magnitude(PointF p)
+ {
+ return (float)Math.Sqrt(p.X * p.X + p.Y * p.Y);
+ }
+
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static double Clamp(double x, double min, double max)
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static float Clamp(float x, float min, float max)
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ // TODO: In v4.0 codebase, turn this into an extension method
+ public static int Clamp(int x, int min, int max)
+ {
+ if (x < min)
+ {
+ return min;
+ }
+ else if (x > max)
+ {
+ return max;
+ }
+ else
+ {
+ return x;
+ }
+ }
+
+ public static byte ClampToByte(double x)
+ {
+ if (x > 255)
+ {
+ return 255;
+ }
+ else if (x < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)x;
+ }
+ }
+
+ public static byte ClampToByte(float x)
+ {
+ if (x > 255)
+ {
+ return 255;
+ }
+ else if (x < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)x;
+ }
+ }
+
+ public static byte ClampToByte(int x)
+ {
+ if (x > 255)
+ {
+ return 255;
+ }
+ else if (x < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ return (byte)x;
+ }
+ }
+
+ public static float Lerp(float from, float to, float frac)
+ {
+ return (from + frac * (to - from));
+ }
+
+ public static double Lerp(double from, double to, double frac)
+ {
+ return (from + frac * (to - from));
+ }
+
+ public static PointF Lerp(PointF from, PointF to, float frac)
+ {
+ return new PointF(Lerp(from.X, to.X, frac), Lerp(from.Y, to.Y, frac));
+ }
+
+ public static int ColorDifference(ColorBgra a, ColorBgra b)
+ {
+ return (int)Math.Ceiling(Math.Sqrt(ColorDifferenceSquared(a, b)));
+ }
+
+ public static int ColorDifferenceSquared(ColorBgra a, ColorBgra b)
+ {
+ int diffSq = 0, tmp;
+
+ tmp = a.R - b.R;
+ diffSq += tmp * tmp;
+ tmp = a.G - b.G;
+ diffSq += tmp * tmp;
+ tmp = a.B - b.B;
+ diffSq += tmp * tmp;
+
+ return diffSq / 3;
+ }
+
+ /// <summary>
+ /// Reads a 16-bit unsigned integer from a Stream in little-endian format.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <returns>-1 on failure, else the 16-bit unsigned integer that was read.</returns>
+ public static int ReadUInt16(Stream stream)
+ {
+ int byte1 = stream.ReadByte();
+
+ if (byte1 == -1)
+ {
+ return -1;
+ }
+
+ int byte2 = stream.ReadByte();
+
+ if (byte2 == -1)
+ {
+ return -1;
+ }
+
+ return byte1 + (byte2 << 8);
+ }
+
+ public static void WriteUInt16(Stream stream, UInt16 word)
+ {
+ stream.WriteByte((byte)(word & 0xff));
+ stream.WriteByte((byte)(word >> 8));
+ }
+
+ public static void WriteUInt24(Stream stream, int uint24)
+ {
+ stream.WriteByte((byte)(uint24 & 0xff));
+ stream.WriteByte((byte)((uint24 >> 8) & 0xff));
+ stream.WriteByte((byte)((uint24 >> 16) & 0xff));
+ }
+
+ public static void WriteUInt32(Stream stream, UInt32 uint32)
+ {
+ stream.WriteByte((byte)(uint32 & 0xff));
+ stream.WriteByte((byte)((uint32 >> 8) & 0xff));
+ stream.WriteByte((byte)((uint32 >> 16) & 0xff));
+ stream.WriteByte((byte)((uint32 >> 24) & 0xff));
+ }
+
+ /// <summary>
+ /// Reads a 24-bit unsigned integer from a Stream in little-endian format.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <returns>-1 on failure, else the 24-bit unsigned integer that was read.</returns>
+ public static int ReadUInt24(Stream stream)
+ {
+ int byte1 = stream.ReadByte();
+
+ if (byte1 == -1)
+ {
+ return -1;
+ }
+
+ int byte2 = stream.ReadByte();
+
+ if (byte2 == -1)
+ {
+ return -1;
+ }
+
+ int byte3 = stream.ReadByte();
+
+ if (byte3 == -1)
+ {
+ return -1;
+ }
+
+ return byte1 + (byte2 << 8) + (byte3 << 16);
+ }
+
+ /// <summary>
+ /// Reads a 32-bit unsigned integer from a Stream in little-endian format.
+ /// </summary>
+ /// <param name="stream"></param>
+ /// <returns>-1 on failure, else the 32-bit unsigned integer that was read.</returns>
+ public static long ReadUInt32(Stream stream)
+ {
+ int byte1 = stream.ReadByte();
+
+ if (byte1 == -1)
+ {
+ return -1;
+ }
+
+ int byte2 = stream.ReadByte();
+
+ if (byte2 == -1)
+ {
+ return -1;
+ }
+
+ int byte3 = stream.ReadByte();
+
+ if (byte3 == -1)
+ {
+ return -1;
+ }
+
+ int byte4 = stream.ReadByte();
+
+ if (byte4 == -1)
+ {
+ return -1;
+ }
+
+ return unchecked((long)((uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24))));
+ }
+
+ public static int ReadFromStream(Stream input, byte[] buffer, int offset, int count)
+ {
+ int totalBytesRead = 0;
+
+ while (totalBytesRead < count)
+ {
+ int bytesRead = input.Read(buffer, offset + totalBytesRead, count - totalBytesRead);
+
+ if (bytesRead == 0)
+ {
+ throw new IOException("ran out of data");
+ }
+
+ totalBytesRead += bytesRead;
+ }
+
+ return totalBytesRead;
+ }
+
+ public static long CopyStream(Stream input, Stream output, long maxBytes)
+ {
+ long bytesCopied = 0;
+ byte[] buffer = new byte[4096];
+
+ while (true)
+ {
+ int bytesRead = input.Read(buffer, 0, buffer.Length);
+
+ if (bytesRead == 0)
+ {
+ break;
+ }
+ else
+ {
+ int bytesToCopy;
+
+ if (maxBytes != -1 && (bytesCopied + bytesRead) > maxBytes)
+ {
+ bytesToCopy = (int)(maxBytes - bytesCopied);
+ }
+ else
+ {
+ bytesToCopy = bytesRead;
+ }
+
+ output.Write(buffer, 0, bytesRead);
+ bytesCopied += bytesToCopy;
+
+ if (bytesToCopy != bytesRead)
+ {
+ break;
+ }
+ }
+ }
+
+ return bytesCopied;
+ }
+
+ public static long CopyStream(Stream input, Stream output)
+ {
+ return CopyStream(input, output, -1);
+ }
+
+ private struct Edge
+ {
+ public int miny; // int
+ public int maxy; // int
+ public int x; // fixed point: 24.8
+ public int dxdy; // fixed point: 24.8
+
+ public Edge(int miny, int maxy, int x, int dxdy)
+ {
+ this.miny = miny;
+ this.maxy = maxy;
+ this.x = x;
+ this.dxdy = dxdy;
+ }
+ }
+
+ public static Scanline[] GetScans(Point[] vertices)
+ {
+ return GetScans(vertices, 0, vertices.Length);
+ }
+
+ public static Scanline[] GetScans(Point[] vertices, int startIndex, int length)
+ {
+ if (length > vertices.Length - startIndex)
+ {
+ throw new ArgumentException("out of bounds: length > vertices.Length - startIndex");
+ }
+
+ int ymax = 0;
+
+ // Build edge table
+ Edge[] edgeTable = new Edge[length];
+ int edgeCount = 0;
+
+ for (int i = startIndex; i < startIndex + length; ++i)
+ {
+ Point top = vertices[i];
+ Point bottom = vertices[(((i + 1) - startIndex) % length) + startIndex];
+ int dy;
+
+ if (top.Y > bottom.Y)
+ {
+ Point temp = top;
+ top = bottom;
+ bottom = temp;
+ }
+
+ dy = bottom.Y - top.Y;
+
+ if (dy != 0)
+ {
+ edgeTable[edgeCount] = new Edge(top.Y, bottom.Y, top.X << 8, (((bottom.X - top.X) << 8) / dy));
+ ymax = Math.Max(ymax, bottom.Y);
+ ++edgeCount;
+ }
+ }
+
+ // Sort edge table by miny
+ for (int i = 0; i < edgeCount - 1; ++i)
+ {
+ int min = i;
+
+ for (int j = i + 1; j < edgeCount; ++j)
+ {
+ if (edgeTable[j].miny < edgeTable[min].miny)
+ {
+ min = j;
+ }
+ }
+
+ if (min != i)
+ {
+ Edge temp = edgeTable[min];
+ edgeTable[min] = edgeTable[i];
+ edgeTable[i] = temp;
+ }
+ }
+
+ // Compute how many scanlines we will be emitting
+ int scanCount = 0;
+ int activeLow = 0;
+ int activeHigh = 0;
+ int yscan1 = edgeTable[0].miny;
+
+ // we assume that edgeTable[0].miny == yscan
+ while (activeHigh < edgeCount - 1 &&
+ edgeTable[activeHigh + 1].miny == yscan1)
+ {
+ ++activeHigh;
+ }
+
+ while (yscan1 <= ymax)
+ {
+ // Find new edges where yscan == miny
+ while (activeHigh < edgeCount - 1 &&
+ edgeTable[activeHigh + 1].miny == yscan1)
+ {
+ ++activeHigh;
+ }
+
+ int count = 0;
+ for (int i = activeLow; i <= activeHigh; ++i)
+ {
+ if (edgeTable[i].maxy > yscan1)
+ {
+ ++count;
+ }
+ }
+
+ scanCount += count / 2;
+ ++yscan1;
+
+ // Remove edges where yscan == maxy
+ while (activeLow < edgeCount - 1 &&
+ edgeTable[activeLow].maxy <= yscan1)
+ {
+ ++activeLow;
+ }
+
+ if (activeLow > activeHigh)
+ {
+ activeHigh = activeLow;
+ }
+ }
+
+ // Allocate scanlines that we'll return
+ Scanline[] scans = new Scanline[scanCount];
+
+ // Active Edge Table (AET): it is indices into the Edge Table (ET)
+ int[] active = new int[edgeCount];
+ int activeCount = 0;
+ int yscan2 = edgeTable[0].miny;
+ int scansIndex = 0;
+
+ // Repeat until both the ET and AET are empty
+ while (yscan2 <= ymax)
+ {
+ // Move any edges from the ET to the AET where yscan == miny
+ for (int i = 0; i < edgeCount; ++i)
+ {
+ if (edgeTable[i].miny == yscan2)
+ {
+ active[activeCount] = i;
+ ++activeCount;
+ }
+ }
+
+ // Sort the AET on x
+ for (int i = 0; i < activeCount - 1; ++i)
+ {
+ int min = i;
+
+ for (int j = i + 1; j < activeCount; ++j)
+ {
+ if (edgeTable[active[j]].x < edgeTable[active[min]].x)
+ {
+ min = j;
+ }
+ }
+
+ if (min != i)
+ {
+ int temp = active[min];
+ active[min] = active[i];
+ active[i] = temp;
+ }
+ }
+
+ // For each pair of entries in the AET, fill in pixels between their info
+ for (int i = 0; i < activeCount; i += 2)
+ {
+ Edge el = edgeTable[active[i]];
+ Edge er = edgeTable[active[i + 1]];
+ int startx = (el.x + 0xff) >> 8; // ceil(x)
+ int endx = er.x >> 8; // floor(x)
+
+ scans[scansIndex] = new Scanline(startx, yscan2, endx - startx);
+ ++scansIndex;
+ }
+
+ ++yscan2;
+
+ // Remove from the AET any edge where yscan == maxy
+ int k = 0;
+ while (k < activeCount && activeCount > 0)
+ {
+ if (edgeTable[active[k]].maxy == yscan2)
+ {
+ // remove by shifting everything down one
+ for (int j = k + 1; j < activeCount; ++j)
+ {
+ active[j - 1] = active[j];
+ }
+
+ --activeCount;
+ }
+ else
+ {
+ ++k;
+ }
+ }
+
+ // Update x for each entry in AET
+ for (int i = 0; i < activeCount; ++i)
+ {
+ edgeTable[active[i]].x += edgeTable[active[i]].dxdy;
+ }
+ }
+
+ return scans;
+ }
+
+ public static PointF TransformOnePoint(Matrix matrix, PointF ptF)
+ {
+ PointF[] ptFs = new PointF[1] { ptF };
+ matrix.TransformPoints(ptFs);
+ return ptFs[0];
+ }
+
+ public static PointF TransformOneVector(Matrix matrix, PointF ptF)
+ {
+ PointF[] ptFs = new PointF[1] { ptF };
+ matrix.TransformVectors(ptFs);
+ return ptFs[0];
+ }
+
+ public static PointF NormalizeVector(PointF vecF)
+ {
+ float magnitude = Magnitude(vecF);
+ vecF.X /= magnitude;
+ vecF.Y /= magnitude;
+ return vecF;
+ }
+
+ public static PointF NormalizeVector2(PointF vecF)
+ {
+ float magnitude = Magnitude(vecF);
+
+ if (magnitude == 0)
+ {
+ vecF.X = 0;
+ vecF.Y = 0;
+ }
+ else
+ {
+ vecF.X /= magnitude;
+ vecF.Y /= magnitude;
+ }
+
+ return vecF;
+ }
+
+ public static void NormalizeVectors(PointF[] vecsF)
+ {
+ for (int i = 0; i < vecsF.Length; ++i)
+ {
+ vecsF[i] = NormalizeVector(vecsF[i]);
+ }
+ }
+
+ public static PointF RotateVector(PointF vecF, float angleDelta)
+ {
+ angleDelta *= (float)( Math.PI / 180.0);
+ float vecFLen = Magnitude(vecF);
+ float vecFAngle = angleDelta + (float)Math.Atan2(vecF.Y, vecF.X);
+ vecF.X = (float)Math.Cos(vecFAngle);
+ vecF.Y = (float)Math.Sin(vecFAngle);
+ return vecF;
+ }
+
+ public static void RotateVectors(PointF[] vecFs, float angleDelta)
+ {
+ for (int i = 0; i < vecFs.Length; ++i)
+ {
+ vecFs[i] = RotateVector(vecFs[i], angleDelta);
+ }
+ }
+
+ public static PointF MultiplyVector(PointF vecF, float scalar)
+ {
+ return new PointF(vecF.X * scalar, vecF.Y * scalar);
+ }
+
+ public static PointF AddVectors(PointF a, PointF b)
+ {
+ return new PointF(a.X + b.X, a.Y + b.Y);
+ }
+
+ public static PointF SubtractVectors(PointF lhs, PointF rhs)
+ {
+ return new PointF(lhs.X - rhs.X, lhs.Y - rhs.Y);
+ }
+
+ public static PointF NegateVector(PointF v)
+ {
+ return new PointF(-v.X, -v.Y);
+ }
+
+ public static float GetAngleOfTransform(Matrix matrix)
+ {
+ PointF[] pts = new PointF[] { new PointF(1.0f, 0.0f) };
+ matrix.TransformVectors(pts);
+ double atan2 = Math.Atan2(pts[0].Y, pts[0].X);
+ double angle = atan2 * (180.0f / Math.PI);
+
+ return (float)angle;
+ }
+
+ public static bool IsTransformFlipped(Matrix matrix)
+ {
+ PointF ptX = new PointF(1.0f, 0.0f);
+ PointF ptXT = Utility.TransformOneVector(matrix, ptX);
+ double atan2X = Math.Atan2(ptXT.Y, ptXT.X);
+ double angleX = atan2X * (180.0 / Math.PI);
+
+ PointF ptY = new PointF(0.0f, 1.0f);
+ PointF ptYT = Utility.TransformOneVector(matrix, ptY);
+ double atan2Y = Math.Atan2(ptYT.Y, ptYT.X);
+ double angleY = (atan2Y * (180.0 / Math.PI)) - 90.0;
+
+ while (angleX < 0)
+ {
+ angleX += 360;
+ }
+
+ while (angleY < 0)
+ {
+ angleY += 360;
+ }
+
+ double angleDelta = Math.Abs(angleX - angleY);
+
+ return angleDelta > 1.0 && angleDelta < 359.0;
+ }
+
+ /// <summary>
+ /// Calculates the dot product of two vectors.
+ /// </summary>
+ public static float DotProduct(PointF lhs, PointF rhs)
+ {
+ return lhs.X * rhs.X + lhs.Y * rhs.Y;
+ }
+
+ /// <summary>
+ /// Calculates the orthogonal projection of y on to u.
+ /// yhat = u * ((y dot u) / (u dot u))
+ /// z = y - yhat
+ /// Section 6.2 (pg. 381) of Linear Algebra and its Applications, Second Edition, by David C. Lay
+ /// </summary>
+ /// <param name="y">The vector to decompose</param>
+ /// <param name="u">The non-zero vector to project y on to</param>
+ /// <param name="yhat">The orthogonal projection of y onto u</param>
+ /// <param name="yhatLen">The length of yhat such that yhat = yhatLen * u</param>
+ /// <param name="z">The component of y orthogonal to u</param>
+ /// <remarks>
+ /// As a special case, if u=(0,0) the results are all zero.
+ /// </remarks>
+ public static void GetProjection(PointF y, PointF u, out PointF yhat, out float yhatLen, out PointF z)
+ {
+ if (u.X == 0 && u.Y == 0)
+ {
+ yhat = new PointF(0, 0);
+ yhatLen = 0;
+ z = new PointF(0, 0);
+ }
+ else
+ {
+ float yDotU = DotProduct(y, u);
+ float uDotU = DotProduct(u, u);
+ yhatLen = yDotU / uDotU;
+ yhat = MultiplyVector(u, yhatLen);
+ z = SubtractVectors(y, yhat);
+ }
+ }
+
+ public static int GreatestCommonDivisor(int a, int b)
+ {
+ int r;
+
+ if (a < b)
+ {
+ r = a;
+ a = b;
+ b = r;
+ }
+
+ do
+ {
+ r = a % b;
+ a = b;
+ b = r;
+ } while (r != 0);
+
+ return a;
+ }
+
+ public static void Swap(ref int a, ref int b)
+ {
+ int t;
+
+ t = a;
+ a = b;
+ b = t;
+ }
+
+ public static void Swap<T>(ref T a, ref T b)
+ {
+ T t;
+
+ t = a;
+ a = b;
+ b = t;
+ }
+
+ private static byte[] DownloadSmallFile(Uri uri, WebProxy proxy)
+ {
+ WebRequest request = WebRequest.Create(uri);
+
+ if (proxy != null)
+ {
+ request.Proxy = proxy;
+ }
+
+ request.Timeout = 5000;
+ WebResponse response = request.GetResponse();
+ Stream stream = response.GetResponseStream();
+
+ try
+ {
+ byte[] buffer = new byte[8192];
+ int offset = 0;
+
+ while (offset < buffer.Length)
+ {
+ int bytesRead = stream.Read(buffer, offset, buffer.Length - offset);
+
+ if (bytesRead == 0)
+ {
+ byte[] smallerBuffer = new byte[offset + bytesRead];
+
+ for (int i = 0; i < offset + bytesRead; ++i)
+ {
+ smallerBuffer[i] = buffer[i];
+ }
+
+ buffer = smallerBuffer;
+ }
+
+ offset += bytesRead;
+ }
+
+ return buffer;
+ }
+
+ finally
+ {
+ if (stream != null)
+ {
+ stream.Close();
+ stream = null;
+ }
+
+ if (response != null)
+ {
+ response.Close();
+ response = null;
+ }
+ }
+ }
+
+ public static T[] RepeatArray<T>(T[] array, int repeatCount)
+ {
+ T[] returnArray = new T[repeatCount * array.Length];
+
+ for (int i = 0; i < repeatCount; ++i)
+ {
+ for (int j = 0; j < array.Length; ++j)
+ {
+ int index = (i * array.Length) + j;
+ returnArray[index] = array[j];
+ }
+ }
+
+ return returnArray;
+ }
+
+ public static byte FastScaleByteByByte(byte a, byte b)
+ {
+ int r1 = a * b + 0x80;
+ int r2 = ((r1 >> 8) + r1) >> 8;
+ return (byte)r2;
+ }
+
+ public static int FastDivideShortByByte(ushort n, byte d)
+ {
+ int i = d * 3;
+ uint m = masTable[i];
+ uint a = masTable[i + 1];
+ uint s = masTable[i + 2];
+
+ uint nTimesMPlusA = unchecked((n * m) + a);
+ uint shifted = nTimesMPlusA >> (int)s;
+ int r = (int)shifted;
+
+ return r;
+ }
+
+ // i = z * 3;
+ // (x / z) = ((x * masTable[i]) + masTable[i + 1]) >> masTable[i + 2)
+ private static readonly uint[] masTable =
+ {
+ 0x00000000, 0x00000000, 0, // 0
+ 0x00000001, 0x00000000, 0, // 1
+ 0x00000001, 0x00000000, 1, // 2
+ 0xAAAAAAAB, 0x00000000, 33, // 3
+ 0x00000001, 0x00000000, 2, // 4
+ 0xCCCCCCCD, 0x00000000, 34, // 5
+ 0xAAAAAAAB, 0x00000000, 34, // 6
+ 0x49249249, 0x49249249, 33, // 7
+ 0x00000001, 0x00000000, 3, // 8
+ 0x38E38E39, 0x00000000, 33, // 9
+ 0xCCCCCCCD, 0x00000000, 35, // 10
+ 0xBA2E8BA3, 0x00000000, 35, // 11
+ 0xAAAAAAAB, 0x00000000, 35, // 12
+ 0x4EC4EC4F, 0x00000000, 34, // 13
+ 0x49249249, 0x49249249, 34, // 14
+ 0x88888889, 0x00000000, 35, // 15
+ 0x00000001, 0x00000000, 4, // 16
+ 0xF0F0F0F1, 0x00000000, 36, // 17
+ 0x38E38E39, 0x00000000, 34, // 18
+ 0xD79435E5, 0xD79435E5, 36, // 19
+ 0xCCCCCCCD, 0x00000000, 36, // 20
+ 0xC30C30C3, 0xC30C30C3, 36, // 21
+ 0xBA2E8BA3, 0x00000000, 36, // 22
+ 0xB21642C9, 0x00000000, 36, // 23
+ 0xAAAAAAAB, 0x00000000, 36, // 24
+ 0x51EB851F, 0x00000000, 35, // 25
+ 0x4EC4EC4F, 0x00000000, 35, // 26
+ 0x97B425ED, 0x97B425ED, 36, // 27
+ 0x49249249, 0x49249249, 35, // 28
+ 0x8D3DCB09, 0x00000000, 36, // 29
+ 0x88888889, 0x00000000, 36, // 30
+ 0x42108421, 0x42108421, 35, // 31
+ 0x00000001, 0x00000000, 5, // 32
+ 0x3E0F83E1, 0x00000000, 35, // 33
+ 0xF0F0F0F1, 0x00000000, 37, // 34
+ 0x75075075, 0x75075075, 36, // 35
+ 0x38E38E39, 0x00000000, 35, // 36
+ 0x6EB3E453, 0x6EB3E453, 36, // 37
+ 0xD79435E5, 0xD79435E5, 37, // 38
+ 0x69069069, 0x69069069, 36, // 39
+ 0xCCCCCCCD, 0x00000000, 37, // 40
+ 0xC7CE0C7D, 0x00000000, 37, // 41
+ 0xC30C30C3, 0xC30C30C3, 37, // 42
+ 0x2FA0BE83, 0x00000000, 35, // 43
+ 0xBA2E8BA3, 0x00000000, 37, // 44
+ 0x5B05B05B, 0x5B05B05B, 36, // 45
+ 0xB21642C9, 0x00000000, 37, // 46
+ 0xAE4C415D, 0x00000000, 37, // 47
+ 0xAAAAAAAB, 0x00000000, 37, // 48
+ 0x5397829D, 0x00000000, 36, // 49
+ 0x51EB851F, 0x00000000, 36, // 50
+ 0xA0A0A0A1, 0x00000000, 37, // 51
+ 0x4EC4EC4F, 0x00000000, 36, // 52
+ 0x9A90E7D9, 0x9A90E7D9, 37, // 53
+ 0x97B425ED, 0x97B425ED, 37, // 54
+ 0x94F2094F, 0x94F2094F, 37, // 55
+ 0x49249249, 0x49249249, 36, // 56
+ 0x47DC11F7, 0x47DC11F7, 36, // 57
+ 0x8D3DCB09, 0x00000000, 37, // 58
+ 0x22B63CBF, 0x00000000, 35, // 59
+ 0x88888889, 0x00000000, 37, // 60
+ 0x4325C53F, 0x00000000, 36, // 61
+ 0x42108421, 0x42108421, 36, // 62
+ 0x41041041, 0x41041041, 36, // 63
+ 0x00000001, 0x00000000, 6, // 64
+ 0xFC0FC0FD, 0x00000000, 38, // 65
+ 0x3E0F83E1, 0x00000000, 36, // 66
+ 0x07A44C6B, 0x00000000, 33, // 67
+ 0xF0F0F0F1, 0x00000000, 38, // 68
+ 0x76B981DB, 0x00000000, 37, // 69
+ 0x75075075, 0x75075075, 37, // 70
+ 0xE6C2B449, 0x00000000, 38, // 71
+ 0x38E38E39, 0x00000000, 36, // 72
+ 0x381C0E07, 0x381C0E07, 36, // 73
+ 0x6EB3E453, 0x6EB3E453, 37, // 74
+ 0x1B4E81B5, 0x00000000, 35, // 75
+ 0xD79435E5, 0xD79435E5, 38, // 76
+ 0x3531DEC1, 0x00000000, 36, // 77
+ 0x69069069, 0x69069069, 37, // 78
+ 0xCF6474A9, 0x00000000, 38, // 79
+ 0xCCCCCCCD, 0x00000000, 38, // 80
+ 0xCA4587E7, 0x00000000, 38, // 81
+ 0xC7CE0C7D, 0x00000000, 38, // 82
+ 0x3159721F, 0x00000000, 36, // 83
+ 0xC30C30C3, 0xC30C30C3, 38, // 84
+ 0xC0C0C0C1, 0x00000000, 38, // 85
+ 0x2FA0BE83, 0x00000000, 36, // 86
+ 0x2F149903, 0x00000000, 36, // 87
+ 0xBA2E8BA3, 0x00000000, 38, // 88
+ 0xB81702E1, 0x00000000, 38, // 89
+ 0x5B05B05B, 0x5B05B05B, 37, // 90
+ 0x2D02D02D, 0x2D02D02D, 36, // 91
+ 0xB21642C9, 0x00000000, 38, // 92
+ 0xB02C0B03, 0x00000000, 38, // 93
+ 0xAE4C415D, 0x00000000, 38, // 94
+ 0x2B1DA461, 0x2B1DA461, 36, // 95
+ 0xAAAAAAAB, 0x00000000, 38, // 96
+ 0xA8E83F57, 0xA8E83F57, 38, // 97
+ 0x5397829D, 0x00000000, 37, // 98
+ 0xA57EB503, 0x00000000, 38, // 99
+ 0x51EB851F, 0x00000000, 37, // 100
+ 0xA237C32B, 0xA237C32B, 38, // 101
+ 0xA0A0A0A1, 0x00000000, 38, // 102
+ 0x9F1165E7, 0x9F1165E7, 38, // 103
+ 0x4EC4EC4F, 0x00000000, 37, // 104
+ 0x27027027, 0x27027027, 36, // 105
+ 0x9A90E7D9, 0x9A90E7D9, 38, // 106
+ 0x991F1A51, 0x991F1A51, 38, // 107
+ 0x97B425ED, 0x97B425ED, 38, // 108
+ 0x2593F69B, 0x2593F69B, 36, // 109
+ 0x94F2094F, 0x94F2094F, 38, // 110
+ 0x24E6A171, 0x24E6A171, 36, // 111
+ 0x49249249, 0x49249249, 37, // 112
+ 0x90FDBC09, 0x90FDBC09, 38, // 113
+ 0x47DC11F7, 0x47DC11F7, 37, // 114
+ 0x8E78356D, 0x8E78356D, 38, // 115
+ 0x8D3DCB09, 0x00000000, 38, // 116
+ 0x23023023, 0x23023023, 36, // 117
+ 0x22B63CBF, 0x00000000, 36, // 118
+ 0x44D72045, 0x00000000, 37, // 119
+ 0x88888889, 0x00000000, 38, // 120
+ 0x8767AB5F, 0x8767AB5F, 38, // 121
+ 0x4325C53F, 0x00000000, 37, // 122
+ 0x85340853, 0x85340853, 38, // 123
+ 0x42108421, 0x42108421, 37, // 124
+ 0x10624DD3, 0x00000000, 35, // 125
+ 0x41041041, 0x41041041, 37, // 126
+ 0x10204081, 0x10204081, 35, // 127
+ 0x00000001, 0x00000000, 7, // 128
+ 0x0FE03F81, 0x00000000, 35, // 129
+ 0xFC0FC0FD, 0x00000000, 39, // 130
+ 0xFA232CF3, 0x00000000, 39, // 131
+ 0x3E0F83E1, 0x00000000, 37, // 132
+ 0xF6603D99, 0x00000000, 39, // 133
+ 0x07A44C6B, 0x00000000, 34, // 134
+ 0xF2B9D649, 0x00000000, 39, // 135
+ 0xF0F0F0F1, 0x00000000, 39, // 136
+ 0x077975B9, 0x00000000, 34, // 137
+ 0x76B981DB, 0x00000000, 38, // 138
+ 0x75DED953, 0x00000000, 38, // 139
+ 0x75075075, 0x75075075, 38, // 140
+ 0x3A196B1F, 0x00000000, 37, // 141
+ 0xE6C2B449, 0x00000000, 39, // 142
+ 0xE525982B, 0x00000000, 39, // 143
+ 0x38E38E39, 0x00000000, 37, // 144
+ 0xE1FC780F, 0x00000000, 39, // 145
+ 0x381C0E07, 0x381C0E07, 37, // 146
+ 0xDEE95C4D, 0x00000000, 39, // 147
+ 0x6EB3E453, 0x6EB3E453, 38, // 148
+ 0xDBEB61EF, 0x00000000, 39, // 149
+ 0x1B4E81B5, 0x00000000, 36, // 150
+ 0x36406C81, 0x00000000, 37, // 151
+ 0xD79435E5, 0xD79435E5, 39, // 152
+ 0xD62B80D7, 0x00000000, 39, // 153
+ 0x3531DEC1, 0x00000000, 37, // 154
+ 0xD3680D37, 0x00000000, 39, // 155
+ 0x69069069, 0x69069069, 38, // 156
+ 0x342DA7F3, 0x00000000, 37, // 157
+ 0xCF6474A9, 0x00000000, 39, // 158
+ 0xCE168A77, 0xCE168A77, 39, // 159
+ 0xCCCCCCCD, 0x00000000, 39, // 160
+ 0xCB8727C1, 0x00000000, 39, // 161
+ 0xCA4587E7, 0x00000000, 39, // 162
+ 0xC907DA4F, 0x00000000, 39, // 163
+ 0xC7CE0C7D, 0x00000000, 39, // 164
+ 0x634C0635, 0x00000000, 38, // 165
+ 0x3159721F, 0x00000000, 37, // 166
+ 0x621B97C3, 0x00000000, 38, // 167
+ 0xC30C30C3, 0xC30C30C3, 39, // 168
+ 0x60F25DEB, 0x00000000, 38, // 169
+ 0xC0C0C0C1, 0x00000000, 39, // 170
+ 0x17F405FD, 0x17F405FD, 36, // 171
+ 0x2FA0BE83, 0x00000000, 37, // 172
+ 0xBD691047, 0xBD691047, 39, // 173
+ 0x2F149903, 0x00000000, 37, // 174
+ 0x5D9F7391, 0x00000000, 38, // 175
+ 0xBA2E8BA3, 0x00000000, 39, // 176
+ 0x5C90A1FD, 0x5C90A1FD, 38, // 177
+ 0xB81702E1, 0x00000000, 39, // 178
+ 0x5B87DDAD, 0x5B87DDAD, 38, // 179
+ 0x5B05B05B, 0x5B05B05B, 38, // 180
+ 0xB509E68B, 0x00000000, 39, // 181
+ 0x2D02D02D, 0x2D02D02D, 37, // 182
+ 0xB30F6353, 0x00000000, 39, // 183
+ 0xB21642C9, 0x00000000, 39, // 184
+ 0x1623FA77, 0x1623FA77, 36, // 185
+ 0xB02C0B03, 0x00000000, 39, // 186
+ 0xAF3ADDC7, 0x00000000, 39, // 187
+ 0xAE4C415D, 0x00000000, 39, // 188
+ 0x15AC056B, 0x15AC056B, 36, // 189
+ 0x2B1DA461, 0x2B1DA461, 37, // 190
+ 0xAB8F69E3, 0x00000000, 39, // 191
+ 0xAAAAAAAB, 0x00000000, 39, // 192
+ 0x15390949, 0x00000000, 36, // 193
+ 0xA8E83F57, 0xA8E83F57, 39, // 194
+ 0x15015015, 0x15015015, 36, // 195
+ 0x5397829D, 0x00000000, 38, // 196
+ 0xA655C439, 0xA655C439, 39, // 197
+ 0xA57EB503, 0x00000000, 39, // 198
+ 0x5254E78F, 0x00000000, 38, // 199
+ 0x51EB851F, 0x00000000, 38, // 200
+ 0x028C1979, 0x00000000, 33, // 201
+ 0xA237C32B, 0xA237C32B, 39, // 202
+ 0xA16B312F, 0x00000000, 39, // 203
+ 0xA0A0A0A1, 0x00000000, 39, // 204
+ 0x4FEC04FF, 0x00000000, 38, // 205
+ 0x9F1165E7, 0x9F1165E7, 39, // 206
+ 0x27932B49, 0x00000000, 37, // 207
+ 0x4EC4EC4F, 0x00000000, 38, // 208
+ 0x9CC8E161, 0x00000000, 39, // 209
+ 0x27027027, 0x27027027, 37, // 210
+ 0x9B4C6F9F, 0x00000000, 39, // 211
+ 0x9A90E7D9, 0x9A90E7D9, 39, // 212
+ 0x99D722DB, 0x00000000, 39, // 213
+ 0x991F1A51, 0x991F1A51, 39, // 214
+ 0x4C346405, 0x00000000, 38, // 215
+ 0x97B425ED, 0x97B425ED, 39, // 216
+ 0x4B809701, 0x4B809701, 38, // 217
+ 0x2593F69B, 0x2593F69B, 37, // 218
+ 0x12B404AD, 0x12B404AD, 36, // 219
+ 0x94F2094F, 0x94F2094F, 39, // 220
+ 0x25116025, 0x25116025, 37, // 221
+ 0x24E6A171, 0x24E6A171, 37, // 222
+ 0x24BC44E1, 0x24BC44E1, 37, // 223
+ 0x49249249, 0x49249249, 38, // 224
+ 0x91A2B3C5, 0x00000000, 39, // 225
+ 0x90FDBC09, 0x90FDBC09, 39, // 226
+ 0x905A3863, 0x905A3863, 39, // 227
+ 0x47DC11F7, 0x47DC11F7, 38, // 228
+ 0x478BBCED, 0x00000000, 38, // 229
+ 0x8E78356D, 0x8E78356D, 39, // 230
+ 0x46ED2901, 0x46ED2901, 38, // 231
+ 0x8D3DCB09, 0x00000000, 39, // 232
+ 0x2328A701, 0x2328A701, 37, // 233
+ 0x23023023, 0x23023023, 37, // 234
+ 0x45B81A25, 0x45B81A25, 38, // 235
+ 0x22B63CBF, 0x00000000, 37, // 236
+ 0x08A42F87, 0x08A42F87, 35, // 237
+ 0x44D72045, 0x00000000, 38, // 238
+ 0x891AC73B, 0x00000000, 39, // 239
+ 0x88888889, 0x00000000, 39, // 240
+ 0x10FEF011, 0x00000000, 36, // 241
+ 0x8767AB5F, 0x8767AB5F, 39, // 242
+ 0x86D90545, 0x00000000, 39, // 243
+ 0x4325C53F, 0x00000000, 38, // 244
+ 0x85BF3761, 0x85BF3761, 39, // 245
+ 0x85340853, 0x85340853, 39, // 246
+ 0x10953F39, 0x10953F39, 36, // 247
+ 0x42108421, 0x42108421, 38, // 248
+ 0x41CC9829, 0x41CC9829, 38, // 249
+ 0x10624DD3, 0x00000000, 36, // 250
+ 0x828CBFBF, 0x00000000, 39, // 251
+ 0x41041041, 0x41041041, 38, // 252
+ 0x81848DA9, 0x00000000, 39, // 253
+ 0x10204081, 0x10204081, 36, // 254
+ 0x80808081, 0x00000000, 39 // 255
+ };
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs
new file mode 100644
index 000000000..915147d2a
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureEngine.cs
@@ -0,0 +1,185 @@
+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;
+using Tango.RemoteDesktop.Utils;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents a generic screen capture engine.
+ /// The <see cref="TFrame"/> type will be the type of difference object delivered by the engine.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ /// <seealso cref="Tango.RemoteDesktop.IScreenCaptureEngine{TFrame}" />
+ public class ScreenCaptureEngine<TFrame> : IScreenCaptureEngine<TFrame> where TFrame : class, IFrame
+ {
+ private Thread _captureThread;
+ private Bitmap _previousBitmap;
+ private bool _isDisposed;
+
+ /// <summary>
+ /// Occurs when a new screen frame is available.
+ /// </summary>
+ public event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived;
+
+ /// <summary>
+ /// Gets or sets the screen capture method.
+ /// </summary>
+ public ICaptureMethod CaptureMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the screen capture region.
+ /// </summary>
+ public CaptureRegion CaptureRegion { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether this instance is currently capturing.
+ /// </summary>
+ public bool IsStarted { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the frame rate per second.
+ /// Default is 10.
+ /// </summary>
+ public int FrameRate { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to include the cursor when capturing.
+ /// </summary>
+ public bool CaptureCursor { get; set; }
+
+ /// <summary>
+ /// Gets or sets the bitmap comparer.
+ /// </summary>
+ public IBitmapComparer<TFrame> Comparer { get; set; }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether to enable image comparison.
+ /// </summary>
+ public bool EnableComparer { get; set; }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScreenCaptureEngine{TFrame}"/> class using the specified <see cref="IBitmapComparer{TFrame}"/>.
+ /// </summary>
+ /// <param name="comparer">The comparer.</param>
+ public ScreenCaptureEngine(IBitmapComparer<TFrame> comparer)
+ {
+ FrameRate = 10;
+ CaptureMethod = new CaptureMethods.DirectXScreenCapture();
+ CaptureRegion = new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds);
+ Comparer = comparer;
+ CaptureCursor = true;
+
+ if (Comparer != null)
+ {
+ EnableComparer = true;
+ }
+ }
+
+ /// <summary>
+ /// Start capturing.
+ /// </summary>
+ /// <exception cref="ObjectDisposedException">Screen capture engine cannot be started after disposed.</exception>
+ 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();
+ }
+ }
+
+ /// <summary>
+ /// Stop capturing.
+ /// </summary>
+ public void Stop()
+ {
+ if (IsStarted)
+ {
+ IsStarted = false;
+ _previousBitmap?.Dispose();
+ }
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public void Dispose()
+ {
+ if (!_isDisposed)
+ {
+ _isDisposed = true;
+ Stop();
+ CaptureMethod?.Dispose();
+ }
+ }
+
+ private void CaptureThreadMethod()
+ {
+ Stopwatch watch = new Stopwatch();
+
+ while (IsStarted)
+ {
+ watch.Restart();
+
+ 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, false);
+ }
+ else
+ {
+ var result = Comparer.CreateDifference(_previousBitmap, bitmap);
+ _previousBitmap.Dispose();
+ _previousBitmap = bitmap.Clone() as Bitmap;
+ OnFrameReceived(bitmap, result.Frame, result.ContainsDifference);
+ }
+ }
+ else
+ {
+ OnFrameReceived(bitmap, null, false);
+ }
+
+ int delay = Math.Max(5, (FrameRate * 10) - (int)watch.ElapsedMilliseconds);
+ Thread.Sleep(delay);
+ }
+ }
+
+ private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame, bool hasDifference)
+ {
+ FrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs<TFrame>()
+ {
+ Frame = new ScreenCaptureFrame<TFrame>(currentBitmap, diffFrame)
+ {
+ HasDifference = hasDifference
+ }
+ });
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrame.cs b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrame.cs
new file mode 100644
index 000000000..75e7a961f
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrame.cs
@@ -0,0 +1,91 @@
+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
+{
+ /// <summary>
+ /// Represents a screen capture frame for holding the original bitmap and difference frame.
+ /// </summary>
+ /// <typeparam name="TFrame">The type of the frame.</typeparam>
+ /// <seealso cref="Tango.RemoteDesktop.Frames.RasterFrame" />
+ /// <seealso cref="System.IDisposable" />
+ public class ScreenCaptureFrame<TFrame> : RasterFrame, IDisposable where TFrame : IFrame
+ {
+ private TFrame _diffFrame;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScreenCaptureFrame{TFrame}"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ public ScreenCaptureFrame(Bitmap bitmap) : base(bitmap)
+ {
+
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScreenCaptureFrame{TFrame}"/> class.
+ /// </summary>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="diffFrame">The difference frame.</param>
+ public ScreenCaptureFrame(Bitmap bitmap, TFrame diffFrame) : this(bitmap)
+ {
+ _diffFrame = diffFrame;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether a difference frame is available.
+ /// </summary>
+ public bool DifferenceAvailable
+ {
+ get { return _diffFrame != null; }
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether the difference frame is available and contains any differences.
+ /// </summary>
+ private bool _hasDifference;
+ public bool HasDifference
+ {
+ get
+ {
+ return DifferenceAvailable && _hasDifference;
+ }
+ set { _hasDifference = value; }
+ }
+
+ /// <summary>
+ /// Returns the difference frame.
+ /// </summary>
+ /// <returns></returns>
+ /// <exception cref="InvalidOperationException">No difference is available at this point. Please use the 'DifferenceAvailable' property before attempting to get the difference.</exception>
+ public TFrame ToDifference()
+ {
+ if (!DifferenceAvailable)
+ {
+ throw new InvalidOperationException("No difference is available at this point. Please use the 'DifferenceAvailable' property before attempting to get the difference.");
+ }
+
+ return _diffFrame;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public override void Dispose()
+ {
+ base.Dispose();
+
+ if (_diffFrame != null)
+ {
+ _diffFrame.Dispose();
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs
new file mode 100644
index 000000000..df53d6db9
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop
+{
+ /// <summary>
+ /// Represents the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event arguments.
+ /// </summary>
+ /// <typeparam name="T"></typeparam>
+ /// <seealso cref="System.EventArgs" />
+ public class ScreenCaptureFrameReceivedEventArgs<T> : EventArgs where T: IFrame
+ {
+ /// <summary>
+ /// Gets or sets the screen capture frame containing the original bitmap and difference frame.
+ /// </summary>
+ public ScreenCaptureFrame<T> Frame { get; set; }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/Tango.ScreenCapture.csproj b/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj
index 879e651a2..f760b43dc 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/Tango.ScreenCapture.csproj
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj
@@ -4,16 +4,14 @@
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
- <ProjectGuid>{B87CA1DE-ED08-42B6-8D9C-A1C1A602D03B}</ProjectGuid>
+ <ProjectGuid>{A78068D4-2061-4376-8EDE-583D8D880DEC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
- <RootNamespace>Tango.ScreenCapture</RootNamespace>
- <AssemblyName>Tango.ScreenCapture</AssemblyName>
+ <RootNamespace>Tango.RemoteDesktop</RootNamespace>
+ <AssemblyName>Tango.RemoteDesktop</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
- <NuGetPackageImportStamp>
- </NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@@ -53,6 +51,7 @@
<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" />
@@ -62,28 +61,46 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
- <Compile Include="BitBltScreenCapture.cs" />
+ <Compile Include="BitmapComparerResult.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="CaptureMethods\BitBltScreenCapture.cs" />
+ <Compile Include="CaptureMethods\DirectXScreenCapture.cs" />
+ <Compile Include="CaptureMethods\GdiScreenCapture.cs" />
+ <Compile Include="Clipping\ClipResult.cs" />
+ <Compile Include="Clipping\BitmapCliper.cs" />
+ <Compile Include="Comparers\VectorBitmapComparer.cs" />
+ <Compile Include="Encoders\GifEncoder.cs" />
+ <Compile Include="Encoders\Png8BitEncoder.cs" />
+ <Compile Include="Engines\RasterScreenCaptureEngine.cs" />
+ <Compile Include="Engines\VectorScreenCaptureEngine.cs" />
+ <Compile Include="Frames\VectorFrameColor.cs" />
+ <Compile Include="IScreenCaptureEngine.cs" />
+ <Compile Include="Quantization\ColorBgra.cs" />
+ <Compile Include="Quantization\OctreeQuantizer.cs" />
+ <Compile Include="Quantization\PaletteTable.cs" />
+ <Compile Include="Quantization\Quantizer.cs" />
+ <Compile Include="Quantization\Scanline.cs" />
+ <Compile Include="Quantization\Utility.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" />
+ <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/Visual_Studio/Tango.ScreenCapture/CursorUtils.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs
index db47839a3..9dad26984 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/CursorUtils.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs
@@ -6,8 +6,11 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.Utils
{
+ /// <summary>
+ /// Represents a static cursor helper.
+ /// </summary>
public static class CursorUtils
{
#region INTERNALS
@@ -204,9 +207,16 @@ namespace Tango.ScreenCapture
#endregion
+ /// <summary>
+ /// Applies the current user cursor icon at the specified position on the specified bitmap.
+ /// </summary>
+ /// <param name="graphics">The drawing graphics object.</param>
+ /// <param name="bitmap">The bitmap.</param>
+ /// <param name="left">The left cursor position.</param>
+ /// <param name="top">The top cursor position.</param>
[DebuggerHidden]
[DebuggerStepThrough]
- public static void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top)
+ public static void ApplyCursor(System.Drawing.Graphics graphics, System.Drawing.Bitmap bitmap, int left, int top)
{
try
{
@@ -221,14 +231,14 @@ namespace Tango.ScreenCapture
//User32.ICONINFO iconInfo;
//User32.GetIconInfo(iconPointer, out iconInfo);
- var hdc = g.GetHdc();
+ var hdc = graphics.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();
+ graphics.ReleaseHdc();
}
catch { }
}
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/DirectBitmap.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs
index fac39cc04..83b36fcfa 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/DirectBitmap.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs
@@ -7,8 +7,12 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.Utils
{
+ /// <summary>
+ /// Represents a bitmap object with fast Get/Set Pixel methods.
+ /// </summary>
+ /// <seealso cref="System.IDisposable" />
public class DirectBitmap : IDisposable
{
public Bitmap Bitmap { get; private set; }
@@ -19,6 +23,11 @@ namespace Tango.ScreenCapture
protected GCHandle BitsHandle { get; private set; }
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DirectBitmap"/> class.
+ /// </summary>
+ /// <param name="width">The width.</param>
+ /// <param name="height">The height.</param>
public DirectBitmap(int width, int height)
{
Width = width;
@@ -28,14 +37,26 @@ namespace Tango.ScreenCapture
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
}
- public void SetPixel(int x, int y, Color colour)
+ /// <summary>
+ /// Sets the pixel color.
+ /// </summary>
+ /// <param name="x">The x position.</param>
+ /// <param name="y">The y position.</param>
+ /// <param name="color">The color.</param>
+ public void SetPixel(int x, int y, Color color)
{
int index = x + (y * Width);
- int col = colour.ToArgb();
+ int col = color.ToArgb();
Bits[index] = col;
}
+ /// <summary>
+ /// Gets the pixel color.
+ /// </summary>
+ /// <param name="x">The x position.</param>
+ /// <param name="y">The y position.</param>
+ /// <returns></returns>
public Color GetPixel(int x, int y)
{
int index = x + (y * Width);
@@ -45,6 +66,9 @@ namespace Tango.ScreenCapture
return result;
}
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
public void Dispose()
{
if (Disposed) return;
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/FastBitmap.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs
index 5588daeda..f32bd4b39 100644
--- a/Software/Visual_Studio/Tango.ScreenCapture/FastBitmap.cs
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs
@@ -7,7 +7,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
-namespace Tango.ScreenCapture
+namespace Tango.RemoteDesktop.Utils
{
/// <summary>
/// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images
diff --git a/Software/Visual_Studio/Tango.ScreenCapture/CaptureRegion.cs b/Software/Visual_Studio/Tango.ScreenCapture/CaptureRegion.cs
deleted file mode 100644
index 69f29ed03..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/CaptureRegion.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/DXScreenCapture.cs b/Software/Visual_Studio/Tango.ScreenCapture/DXScreenCapture.cs
deleted file mode 100644
index 77428fd8d..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/DXScreenCapture.cs
+++ /dev/null
@@ -1,436 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/ImageComparer.cs b/Software/Visual_Studio/Tango.ScreenCapture/ImageComparer.cs
deleted file mode 100644
index fba20a00a..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/ImageComparer.cs
+++ /dev/null
@@ -1,162 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/ImageComparerResult.cs b/Software/Visual_Studio/Tango.ScreenCapture/ImageComparerResult.cs
deleted file mode 100644
index 556d5077e..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/ImageComparerResult.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/ScreenCaptureEngine.cs b/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureEngine.cs
deleted file mode 100644
index 9933512c3..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureEngine.cs
+++ /dev/null
@@ -1,119 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrame.cs b/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrame.cs
deleted file mode 100644
index 104bbccb1..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrame.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs b/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs
deleted file mode 100644
index b9b2edec4..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureFrameReceivedEventArgs.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/ScreenCaptureMethod.cs b/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureMethod.cs
deleted file mode 100644
index be8d25cef..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/ScreenCaptureMethod.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/VectorDifferenceImage.cs b/Software/Visual_Studio/Tango.ScreenCapture/VectorDifferenceImage.cs
deleted file mode 100644
index 7fa2c82df..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/VectorDifferenceImage.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/VectorImagePixel.cs b/Software/Visual_Studio/Tango.ScreenCapture/VectorImagePixel.cs
deleted file mode 100644
index 7cf621125..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/VectorImagePixel.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.config b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.config
deleted file mode 100644
index dced5aa43..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.config
+++ /dev/null
@@ -1,14 +0,0 @@
-<?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/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml
deleted file mode 100644
index 2e70522d4..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml
+++ /dev/null
@@ -1,9 +0,0 @@
-<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/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml.cs b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml.cs
deleted file mode 100644
index 909eaa541..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/App.xaml.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml
deleted file mode 100644
index 2a1c3a498..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml
+++ /dev/null
@@ -1,12 +0,0 @@
-<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/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs
deleted file mode 100644
index 2ebcc9d73..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/MainWindow.xaml.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs
deleted file mode 100644
index 05e4327a4..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-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/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs
deleted file mode 100644
index c8c627c30..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.Designer.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-//------------------------------------------------------------------------------
-// <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/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx
deleted file mode 100644
index af7dbebba..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Resources.resx
+++ /dev/null
@@ -1,117 +0,0 @@
-<?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/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs
deleted file mode 100644
index 6512fd02f..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.Designer.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-//------------------------------------------------------------------------------
-// <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/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings
deleted file mode 100644
index 033d7a5e9..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/Properties/Settings.settings
+++ /dev/null
@@ -1,7 +0,0 @@
-<?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/Visual_Studio/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj b/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj
deleted file mode 100644
index dcabe00bb..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/WpfApp1/WpfApp1.csproj
+++ /dev/null
@@ -1,109 +0,0 @@
-<?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/Visual_Studio/Tango.ScreenCapture/app.config b/Software/Visual_Studio/Tango.ScreenCapture/app.config
deleted file mode 100644
index 4e74e78b6..000000000
--- a/Software/Visual_Studio/Tango.ScreenCapture/app.config
+++ /dev/null
@@ -1,11 +0,0 @@
-<?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/Visual_Studio/Tango.sln b/Software/Visual_Studio/Tango.sln
index 9ad8f52bd..b15de90d6 100644
--- a/Software/Visual_Studio/Tango.sln
+++ b/Software/Visual_Studio/Tango.sln
@@ -359,6 +359,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.FSE.PPCConsole", "FSE
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.Console", "Tango.Console\Tango.Console.csproj", "{199E8359-CAD3-433D-9EED-2027652B24A4}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tango.RemoteDesktop", "Tango.RemoteDesktop\Tango.RemoteDesktop.csproj", "{A78068D4-2061-4376-8EDE-583D8D880DEC}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AppVeyor|Any CPU = AppVeyor|Any CPU
@@ -6371,6 +6373,46 @@ Global
{199E8359-CAD3-433D-9EED-2027652B24A4}.Release|x64.Build.0 = Release|Any CPU
{199E8359-CAD3-433D-9EED-2027652B24A4}.Release|x86.ActiveCfg = Release|Any CPU
{199E8359-CAD3-433D-9EED-2027652B24A4}.Release|x86.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|Any CPU.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|Any CPU.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|ARM.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|ARM.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|ARM64.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|ARM64.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|x64.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|x64.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|x86.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.AppVeyor|x86.Build.0 = Release|Any CPU
+ {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}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|ARM.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|ARM64.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|ARM64.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|x64.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Debug|x86.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|Any CPU.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|Any CPU.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|ARM.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|ARM.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|ARM64.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|ARM64.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|x64.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|x64.Build.0 = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|x86.ActiveCfg = Debug|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.DefaultBuild|x86.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
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|ARM.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|ARM.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|ARM64.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|ARM64.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|x64.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|x64.Build.0 = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|x86.ActiveCfg = Release|Any CPU
+ {A78068D4-2061-4376-8EDE-583D8D880DEC}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -6494,12 +6536,12 @@ Global
{866B916A-207C-43F0-B403-7C4A820C2E11} = {4EE6DBA1-71BC-49E2-8DC7-266487E61050}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6}
- BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear
- BuildVersion_UpdateAssemblyVersion = True
- BuildVersion_UpdateFileVersion = False
- BuildVersion_StartDate = 2000/1/1
- BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs
BuildVersion_UseGlobalSettings = False
+ BuildVersion_AssemblyInfoFilename = Properties\AssemblyInfo.cs
+ BuildVersion_StartDate = 2000/1/1
+ BuildVersion_UpdateFileVersion = False
+ BuildVersion_UpdateAssemblyVersion = True
+ BuildVersion_BuildVersioningStyle = None.None.Increment.DeltaBaseYearDayOfYear
+ SolutionGuid = {7986F7F4-A86A-4994-B1B6-0988D7F057B6}
EndGlobalSection
EndGlobal