aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/Tango.RemoteDesktop
diff options
context:
space:
mode:
Diffstat (limited to 'Software/Visual_Studio/Tango.RemoteDesktop')
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/BitmapComparerResult.cs25
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs68
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs161
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs41
-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.cs36
-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.csproj106
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs246
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs79
-rw-r--r--Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs864
41 files changed, 9029 insertions, 0 deletions
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.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
new file mode 100644
index 000000000..d8f1e214c
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.CaptureMethods
+{
+ /// <summary>
+ /// Represents a BitBlt screen capture method.
+ /// </summary>
+ /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" />
+ public class BitBltScreenCapture : ICaptureMethod
+ {
+ #region Win32 API Screen shot calls
+
+ // Win32 API calls necessary to support screen capture
+ [DllImport("gdi32", EntryPoint = "BitBlt", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
+ private static extern int BitBlt(int hDestDC, int x, int y, int nWidth, int nHeight, int hSrcDC, int xSrc,
+ int ySrc, int dwRop);
+
+ [DllImport("user32", EntryPoint = "GetDC", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
+ private static extern int GetDC(int hwnd);
+
+ [DllImport("user32", EntryPoint = "ReleaseDC", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
+ private static extern int ReleaseDC(int hwnd, int hdc);
+
+ #endregion
+
+ /// <summary>
+ /// Gets the desktop bitmap.
+ /// </summary>
+ /// <param name="region">The capture region.</param>
+ /// <returns></returns>
+ public Bitmap GetDesktopBitmap(CaptureRegion region)
+ {
+ const int SRCCOPY = 13369376;
+
+ Bitmap bitmap = new Bitmap(region.Width, region.Height);
+
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ // Get a device context to the windows desktop and our destination bitmaps
+ int hdcSrc = GetDC(0);
+ IntPtr hdcDest = g.GetHdc();
+
+ // Copy what is on the desktop to the bitmap
+ BitBlt(hdcDest.ToInt32(), 0, 0, region.Width, region.Height, hdcSrc, 0, 0, SRCCOPY);
+
+ // Release device contexts
+ g.ReleaseHdc(hdcDest);
+ ReleaseDC(0, hdcSrc);
+ }
+
+ return bitmap;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public void Dispose()
+ {
+ //Do nothing.
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs
new file mode 100644
index 000000000..1a661b27e
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SharpDX;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+using 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;
+ private Device device;
+ private Output1 output1;
+ private Texture2DDescription textureDesc;
+ private OutputDuplication duplicatedOutput;
+ 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);
+
+ // Create device from Adapter
+ device = new Device(adapter);
+
+ // Get DXGI.Output
+ var output = adapter.GetOutput(MonitorIndex);
+ output1 = output.QueryInterface<Output1>();
+
+ // Width/Height of desktop to capture
+ monitorWidth = ((SharpDX.Rectangle)output.Description.DesktopBounds).Width;
+ monitorHeight = ((SharpDX.Rectangle)output.Description.DesktopBounds).Height;
+
+ // Create Staging texture CPU-accessible
+ textureDesc = new Texture2DDescription
+ {
+ CpuAccessFlags = CpuAccessFlags.Read,
+ BindFlags = BindFlags.None,
+ Format = Format.B8G8R8A8_UNorm,
+ Width = monitorWidth,
+ Height = monitorHeight,
+ OptionFlags = ResourceOptionFlags.None,
+ MipLevels = 1,
+ ArraySize = 1,
+ SampleDescription = { Count = 1, Quality = 0 },
+ Usage = ResourceUsage.Staging
+ };
+
+ // Duplicate the output
+ duplicatedOutput = output1.DuplicateOutput(device);
+
+ _hasInstance = true;
+ }
+
+ /// <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);
+
+ SharpDX.DXGI.Resource screenResource;
+ OutputDuplicateFrameInformation duplicateFrameInformation;
+
+ // Try to get duplicated frame within given time
+ duplicatedOutput.AcquireNextFrame(10000, out duplicateFrameInformation, out screenResource);
+
+ // copy resource into memory that can be accessed by the CPU
+ using (var screenTexture2D = screenResource.QueryInterface<Texture2D>())
+ device.ImmediateContext.CopyResource(screenTexture2D, screenTexture);
+
+ // Get the desktop capture texture
+ var mapSource = device.ImmediateContext.MapSubresource(screenTexture, 0, MapMode.Read, MapFlags.None);
+
+ // Create Drawing.Bitmap
+ var bitmap = new Bitmap(monitorWidth, monitorHeight, System.Drawing.Imaging.PixelFormat.Format32bppRgb);
+ var boundsRect = new System.Drawing.Rectangle(0, 0, monitorWidth, monitorHeight);
+
+ // Copy pixels from screen capture Texture to GDI bitmap
+ var mapDest = bitmap.LockBits(boundsRect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat);
+ var sourcePtr = mapSource.DataPointer;
+ var destPtr = mapDest.Scan0;
+ for (int y = 0; y < region.Height; y++)
+ {
+ // Copy a single line
+ Utilities.CopyMemory(destPtr, sourcePtr, monitorWidth * 4);
+
+ // Advance pointers
+ sourcePtr = IntPtr.Add(sourcePtr, mapSource.RowPitch);
+ destPtr = IntPtr.Add(destPtr, mapDest.Stride);
+ }
+
+ // Release source and dest locks
+ bitmap.UnlockBits(mapDest);
+ device.ImmediateContext.UnmapSubresource(screenTexture, 0);
+
+ if (region.Left > 0 || region.Top > 0 || region.Width < monitorWidth || region.Height < monitorHeight)
+ {
+ var cropped = CropImage(bitmap, new System.Drawing.Rectangle(region.Left, region.Top, region.Width, region.Height));
+ bitmap.Dispose();
+ bitmap = cropped;
+ }
+
+ screenTexture.Dispose();
+ screenResource.Dispose();
+ duplicatedOutput.ReleaseFrame();
+
+ return bitmap;
+ }
+
+ private Bitmap CropImage(Bitmap img, System.Drawing.Rectangle cropArea)
+ {
+ Bitmap bmpImage = new Bitmap(img);
+ return bmpImage.Clone(cropArea, bmpImage.PixelFormat);
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public void Dispose()
+ {
+ _hasInstance = false;
+ duplicatedOutput.Dispose();
+ device.Dispose();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
new file mode 100644
index 000000000..92f4403fd
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Linq;
+using System.Text;
+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.Format32bppArgb);
+
+ using (Graphics g = Graphics.FromImage(bitmap))
+ {
+ g.CopyFromScreen(region.Left, region.Top, 0, 0, bitmap.Size, System.Drawing.CopyPixelOperation.SourceCopy);
+ }
+
+ 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.RemoteDesktop/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..7d2e42b5d
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Tango.RemoteDesktop")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Tango.RemoteDesktop")]
+[assembly: AssemblyCopyright("Copyright © 2020")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("a78068d4-2061-4376-8ede-583d8d880dec")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Software/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.RemoteDesktop/Tango.RemoteDesktop.csproj b/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj
new file mode 100644
index 000000000..f760b43dc
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProjectGuid>{A78068D4-2061-4376-8EDE-583D8D880DEC}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>Tango.RemoteDesktop</RootNamespace>
+ <AssemblyName>Tango.RemoteDesktop</AssemblyName>
+ <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ <Deterministic>true</Deterministic>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="PresentationCore" />
+ <Reference Include="PresentationFramework" />
+ <Reference Include="SharpDX">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.Direct3D11">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Direct3D11.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.DXGI">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.DXGI.dll</HintPath>
+ </Reference>
+ <Reference Include="SharpDX.Mathematics">
+ <HintPath>..\..\..\..\..\..\DATA\Development\WpfVideoTools\WpfVideoTools\Resources\Referenced Assemblies\SharpDX.Mathematics.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Drawing" />
+ <Reference Include="System.Windows" />
+ <Reference Include="System.Windows.Forms" />
+ <Reference Include="System.Xaml" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Net.Http" />
+ <Reference Include="System.Xml" />
+ <Reference Include="WindowsBase" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="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" />
+ <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>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+</Project> \ No newline at end of file
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs
new file mode 100644
index 000000000..9dad26984
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs
@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Utils
+{
+ /// <summary>
+ /// Represents a static cursor helper.
+ /// </summary>
+ public static class CursorUtils
+ {
+ #region INTERNALS
+
+ internal static class User32
+ {
+ public const Int32 CURSOR_SHOWING = 0x00000001;
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ICONINFO
+ {
+ public bool fIcon;
+ public Int32 xHotspot;
+ public Int32 yHotspot;
+ public IntPtr hbmMask;
+ public IntPtr hbmColor;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct POINT
+ {
+ public Int32 x;
+ public Int32 y;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CURSORINFO
+ {
+ public Int32 cbSize;
+ public Int32 flags;
+ public IntPtr hCursor;
+ public POINT ptScreenPos;
+ }
+
+ [DllImport("user32.dll")]
+ public static extern bool GetCursorInfo(out CURSORINFO pci);
+
+ [DllImport("user32.dll")]
+ public static extern IntPtr CopyIcon(IntPtr hIcon);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool DestroyIcon(IntPtr hIcon);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ public static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags);
+
+ [DllImport("user32.dll")]
+ public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon);
+
+ [DllImport("user32.dll")]
+ public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);
+ }
+
+ #endregion
+
+ #region Internal Classes
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct CURSORINFO
+ {
+ public Int32 cbSize;
+ public Int32 flags;
+ public IntPtr hCursor;
+ public POINTAPI ptScreenPos;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ struct POINTAPI
+ {
+ public int x;
+ public int y;
+ }
+
+ [DllImport("user32.dll")]
+ static extern bool GetCursorInfo(out CURSORINFO pci);
+
+ [DllImport("user32.dll")]
+ static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
+
+ const Int32 CURSOR_SHOWING = 0x00000001;
+
+ internal struct SIZE
+ {
+ public int cx;
+ public int cy;
+ }
+
+ internal class GDIStuff
+ {
+ #region Class Variables
+ public const int SRCCOPY = 13369376;
+ #endregion
+
+
+ #region Class Functions
+ [DllImport("gdi32.dll", EntryPoint = "CreateDC")]
+ public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData);
+
+ [DllImport("gdi32.dll", EntryPoint = "DeleteDC")]
+ public static extern IntPtr DeleteDC(IntPtr hDc);
+
+ [DllImport("gdi32.dll", EntryPoint = "DeleteObject")]
+ public static extern IntPtr DeleteObject(IntPtr hDc);
+
+ [DllImport("gdi32.dll", EntryPoint = "BitBlt")]
+ public static extern bool BitBlt(IntPtr hdcDest, int xDest,
+ int yDest, int wDest,
+ int hDest, IntPtr hdcSource,
+ int xSrc, int ySrc, int RasterOp);
+
+ [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")]
+ public static extern IntPtr CreateCompatibleBitmap
+ (IntPtr hdc, int nWidth, int nHeight);
+
+ [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")]
+ public static extern IntPtr CreateCompatibleDC(IntPtr hdc);
+
+ [DllImport("gdi32.dll", EntryPoint = "SelectObject")]
+ public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp);
+ #endregion
+ }
+
+ internal class Win32Stuff
+ {
+
+ #region Class Variables
+
+ public const int SM_CXSCREEN = 0;
+ public const int SM_CYSCREEN = 1;
+
+ public const Int32 CURSOR_SHOWING = 0x00000001;
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct ICONINFO
+ {
+ public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies
+ public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot
+ public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot
+ public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon,
+ public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this
+ }
+ [StructLayout(LayoutKind.Sequential)]
+ public struct POINT
+ {
+ public Int32 x;
+ public Int32 y;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct CURSORINFO
+ {
+ public Int32 cbSize; // Specifies the size, in bytes, of the structure.
+ public Int32 flags; // Specifies the cursor state. This parameter can be one of the following values:
+ public IntPtr hCursor; // Handle to the cursor.
+ public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor.
+ }
+
+ #endregion
+
+
+ #region Class Functions
+
+ [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
+ public static extern IntPtr GetDesktopWindow();
+
+ [DllImport("user32.dll", EntryPoint = "GetDC")]
+ public static extern IntPtr GetDC(IntPtr ptr);
+
+ [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
+ public static extern int GetSystemMetrics(int abc);
+
+ [DllImport("user32.dll", EntryPoint = "GetWindowDC")]
+ public static extern IntPtr GetWindowDC(Int32 ptr);
+
+ [DllImport("user32.dll", EntryPoint = "ReleaseDC")]
+ public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
+
+
+ [DllImport("user32.dll", EntryPoint = "GetCursorInfo")]
+ public static extern bool GetCursorInfo(out CURSORINFO pci);
+
+ [DllImport("user32.dll", EntryPoint = "CopyIcon")]
+ public static extern IntPtr CopyIcon(IntPtr hIcon);
+
+ [DllImport("user32.dll", SetLastError = true)]
+ static extern bool DestroyIcon(IntPtr hIcon);
+
+ [DllImport("user32.dll", EntryPoint = "GetIconInfo")]
+ public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);
+
+
+ #endregion
+ }
+
+ #endregion
+
+ /// <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 graphics, 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 = 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);
+ }
+ }
+ graphics.ReleaseHdc();
+ }
+ catch { }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs
new file mode 100644
index 000000000..83b36fcfa
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Utils
+{
+ /// <summary>
+ /// 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; }
+ public Int32[] Bits { get; private set; }
+ public bool Disposed { get; private set; }
+ public int Height { get; private set; }
+ public int Width { get; private set; }
+
+ protected GCHandle BitsHandle { get; private set; }
+
+ /// <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;
+ Height = height;
+ Bits = new Int32[width * height];
+ BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
+ Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
+ }
+
+ /// <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 = 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);
+ int col = Bits[index];
+ Color result = Color.FromArgb(col);
+
+ return result;
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources.
+ /// </summary>
+ public void Dispose()
+ {
+ if (Disposed) return;
+ Disposed = true;
+ BitsHandle.Free();
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs
new file mode 100644
index 000000000..f32bd4b39
--- /dev/null
+++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs
@@ -0,0 +1,864 @@
+using System;
+using System.Collections.Generic;
+using System.Drawing;
+using System.Drawing.Imaging;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Tango.RemoteDesktop.Utils
+{
+ /// <summary>
+ /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images
+ /// </summary>
+ public unsafe class FastBitmap : IDisposable
+ {
+ /// <summary>
+ /// Specifies the number of bytes available per pixel of the bitmap object being manipulated
+ /// </summary>
+ public const int BytesPerPixel = 4;
+
+ /// <summary>
+ /// The Bitmap object encapsulated on this FastBitmap
+ /// </summary>
+ private readonly Bitmap _bitmap;
+
+ /// <summary>
+ /// The BitmapData resulted from the lock operation
+ /// </summary>
+ private BitmapData _bitmapData;
+
+ /// <summary>
+ /// The first pixel of the bitmap
+ /// </summary>
+ private int* _scan0;
+
+ /// <summary>
+ /// Gets the width of this FastBitmap object
+ /// </summary>
+ public int Width { get; }
+
+ /// <summary>
+ /// Gets the height of this FastBitmap object
+ /// </summary>
+ public int Height { get; }
+
+ /// <summary>
+ /// Gets the pointer to the first pixel of the bitmap
+ /// </summary>
+ public IntPtr Scan0 => _bitmapData.Scan0;
+
+ /// <summary>
+ /// Gets the stride width (in int32-sized values) of the bitmap
+ /// </summary>
+ public int Stride { get; private set; }
+
+ /// <summary>
+ /// Gets the stride width (in bytes) of the bitmap
+ /// </summary>
+ public int StrideInBytes { get; private set; }
+
+ /// <summary>
+ /// Gets a boolean value that states whether this FastBitmap is currently locked in memory
+ /// </summary>
+ public bool Locked { get; private set; }
+
+ /// <summary>
+ /// Gets an array of 32-bit color pixel values that represent this FastBitmap
+ /// </summary>
+ /// <exception cref="Exception">The locking operation required to extract the values off from the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public int[] DataArray
+ {
+ get
+ {
+ bool unlockAfter = false;
+ if (!Locked)
+ {
+ Lock();
+ unlockAfter = true;
+ }
+
+ // Declare an array to hold the bytes of the bitmap
+ int bytes = Math.Abs(_bitmapData.Stride) * _bitmap.Height;
+ int[] argbValues = new int[bytes / BytesPerPixel];
+
+ // Copy the RGB values into the array
+ Marshal.Copy(_bitmapData.Scan0, argbValues, 0, bytes / BytesPerPixel);
+
+ if (unlockAfter)
+ {
+ Unlock();
+ }
+
+ return argbValues;
+ }
+ }
+
+ /// <summary>
+ /// Creates a new instance of the FastBitmap class with a specified Bitmap.
+ /// The bitmap provided must have a 32bpp depth
+ /// </summary>
+ /// <param name="bitmap">The Bitmap object to encapsulate on this FastBitmap object</param>
+ /// <exception cref="ArgumentException">The bitmap provided does not have a 32bpp pixel format</exception>
+ public FastBitmap(Bitmap bitmap)
+ {
+ if (Image.GetPixelFormatSize(bitmap.PixelFormat) != 32)
+ {
+ throw new ArgumentException(@"The provided bitmap must have a 32bpp depth", nameof(bitmap));
+ }
+
+ _bitmap = bitmap;
+
+ Width = bitmap.Width;
+ Height = bitmap.Height;
+ }
+
+ /// <summary>
+ /// Disposes of this fast bitmap object and releases any pending resources.
+ /// The underlying bitmap is not disposes, and is unlocked, if currently locked
+ /// </summary>
+ public void Dispose()
+ {
+ if (Locked)
+ {
+ Unlock();
+ }
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked,
+ /// an exception is thrown
+ /// </summary>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked</exception>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public FastBitmapLocker Lock()
+ {
+ return Lock((FastBitmapLockFormat)_bitmap.PixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked,
+ /// an exception is thrown.
+ ///
+ /// The provided pixel format should be a 32bpp format.
+ /// </summary>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked</exception>
+ /// <exception cref="Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat)
+ {
+ if (Locked)
+ {
+ throw new InvalidOperationException("Unlock must be called before a Lock operation");
+ }
+
+ return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations
+ /// </summary>
+ /// <param name="lockMode">The lock mode to use on the bitmap</param>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception>
+ /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception>
+ private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat)
+ {
+ var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height);
+
+ return Lock(lockMode, rect, pixelFormat);
+ }
+
+ /// <summary>
+ /// Locks the bitmap to start the bitmap operations
+ /// </summary>
+ /// <param name="lockMode">The lock mode to use on the bitmap</param>
+ /// <param name="rect">The rectangle to lock</param>
+ /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param>
+ /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns>
+ /// <exception cref="System.ArgumentException">The provided region is invalid</exception>
+ /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception>
+ /// <exception cref="InvalidOperationException">The bitmap region is already locked</exception>
+ /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception>
+ private FastBitmapLocker Lock(ImageLockMode lockMode, Rectangle rect, PixelFormat pixelFormat)
+ {
+ // Lock the bitmap's bits
+ _bitmapData = _bitmap.LockBits(rect, lockMode, pixelFormat);
+
+ _scan0 = (int*)_bitmapData.Scan0;
+ Stride = _bitmapData.Stride / BytesPerPixel;
+ StrideInBytes = _bitmapData.Stride;
+
+ Locked = true;
+
+ return new FastBitmapLocker(this);
+ }
+
+ /// <summary>
+ /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked
+ /// beforehand, an exception is thrown
+ /// </summary>
+ /// <exception cref="InvalidOperationException">The bitmap is already unlocked</exception>
+ /// <exception cref="System.Exception">The unlocking operation in the underlying bitmap failed</exception>
+ public void Unlock()
+ {
+ if (!Locked)
+ {
+ throw new InvalidOperationException("Lock must be called before an Unlock operation");
+ }
+
+ _bitmap.UnlockBits(_bitmapData);
+
+ Locked = false;
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, Color color)
+ {
+ SetPixel(x, y, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, int color)
+ {
+ SetPixel(x, y, unchecked((uint)color));
+ }
+
+ /// <summary>
+ /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to set</param>
+ /// <param name="y">The Y coordinate of the pixel to set</param>
+ /// <param name="color">The new color of the pixel to set</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public void SetPixel(int x, int y, uint color)
+ {
+ if (!Locked)
+ {
+ throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made");
+ }
+
+ if (x < 0 || x >= Width)
+ {
+ throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width");
+ }
+ if (y < 0 || y >= Height)
+ {
+ throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height");
+ }
+
+ *(uint*)(_scan0 + x + y * Stride) = color;
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands,
+ /// an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public Color GetPixel(int x, int y)
+ {
+ return Color.FromArgb(GetPixelInt(x, y));
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates as an integer value. If the bitmap
+ /// was not locked beforehands, an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public int GetPixelInt(int x, int y)
+ {
+ if (!Locked)
+ {
+ throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made");
+ }
+
+ if (x < 0 || x >= Width)
+ {
+ throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width");
+ }
+ if (y < 0 || y >= Height)
+ {
+ throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height");
+ }
+
+ return *(_scan0 + x + y * Stride);
+ }
+
+ /// <summary>
+ /// Gets the pixel color at the given coordinates as an unsigned integer value.
+ /// If the bitmap was not locked beforehands, an exception is thrown
+ /// </summary>
+ /// <param name="x">The X coordinate of the pixel to get</param>
+ /// <param name="y">The Y coordinate of the pixel to get</param>
+ /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception>
+ /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception>
+ public uint GetPixelUInt(int x, int y)
+ {
+ if (!Locked)
+ {
+ throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made");
+ }
+
+ if (x < 0 || x >= Width)
+ {
+ throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width");
+ }
+ if (y < 0 || y >= Height)
+ {
+ throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height");
+ }
+
+ return *((uint*)_scan0 + x + y * Stride);
+ }
+
+ /// <summary>
+ /// Copies the contents of the given array of colors into this FastBitmap.
+ /// Throws an ArgumentException if the count of colors on the array mismatches the pixel count from this FastBitmap
+ /// </summary>
+ /// <param name="colors">The array of colors to copy</param>
+ /// <param name="ignoreZeroes">Whether to ignore zeroes when copying the data</param>
+ public void CopyFromArray(int[] colors, bool ignoreZeroes = false)
+ {
+ if (colors.Length != Width * Height)
+ {
+ throw new ArgumentException(@"The number of colors of the given array mismatch the pixel count of the bitmap", nameof(colors));
+ }
+
+ // Simply copy the argb values array
+ // ReSharper disable once InconsistentNaming
+ int* s0t = _scan0;
+
+ fixed (int* source = colors)
+ {
+ // ReSharper disable once InconsistentNaming
+ int* s0s = source;
+
+ int count = Width * Height;
+
+ if (!ignoreZeroes)
+ {
+ // Unfold the loop
+ const int sizeBlock = 8;
+ int rem = count % sizeBlock;
+
+ count /= sizeBlock;
+
+ while (count-- > 0)
+ {
+ *(s0t++) = *(s0s++);
+ *(s0t++) = *(s0s++);
+ *(s0t++) = *(s0s++);
+ *(s0t++) = *(s0s++);
+
+ *(s0t++) = *(s0s++);
+ *(s0t++) = *(s0s++);
+ *(s0t++) = *(s0s++);
+ *(s0t++) = *(s0s++);
+ }
+
+ while (rem-- > 0)
+ {
+ *(s0t++) = *(s0s++);
+ }
+ }
+ else
+ {
+ while (count-- > 0)
+ {
+ if (*(s0s) == 0) { s0t++; s0s++; continue; }
+ *(s0t++) = *(s0s++);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears the bitmap with the given color
+ /// </summary>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public void Clear(Color color)
+ {
+ Clear(color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears the bitmap with the given color
+ /// </summary>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public void Clear(int color)
+ {
+ bool unlockAfter = false;
+ if (!Locked)
+ {
+ Lock();
+ unlockAfter = true;
+ }
+
+ // Clear all the pixels
+ int count = Width * Height;
+ int* curScan = _scan0;
+
+ // Uniform color pixel values can be mem-set straight away
+ int component = (color & 0xFF);
+ if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && component == ((color >> 24) & 0xFF))
+ {
+ memset(_scan0, component, (ulong)(Height * Stride * BytesPerPixel));
+ }
+ else
+ {
+ // Defines the ammount of assignments that the main while() loop is performing per loop.
+ // The value specified here must match the number of assignment statements inside that loop
+ const int assignsPerLoop = 8;
+
+ int rem = count % assignsPerLoop;
+ count /= assignsPerLoop;
+
+ while (count-- > 0)
+ {
+ *(curScan++) = color;
+ *(curScan++) = color;
+ *(curScan++) = color;
+ *(curScan++) = color;
+
+ *(curScan++) = color;
+ *(curScan++) = color;
+ *(curScan++) = color;
+ *(curScan++) = color;
+ }
+ while (rem-- > 0)
+ {
+ *(curScan++) = color;
+ }
+
+ if (unlockAfter)
+ {
+ Unlock();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Clears a square region of this image w/ a given color
+ /// </summary>
+ /// <param name="region"></param>
+ /// <param name="color"></param>
+ public void ClearRegion(Rectangle region, Color color)
+ {
+ ClearRegion(region, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears a square region of this image w/ a given color
+ /// </summary>
+ /// <param name="region"></param>
+ /// <param name="color"></param>
+ public void ClearRegion(Rectangle region, int color)
+ {
+ var thisReg = new Rectangle(0, 0, Width, Height);
+ if (!region.IntersectsWith(thisReg))
+ return;
+
+ // If the region covers the entire image, use faster Clear().
+ if (region == thisReg)
+ {
+ Clear(color);
+ return;
+ }
+
+ int minX = region.X;
+ int maxX = region.X + region.Width;
+
+ int minY = region.Y;
+ int maxY = region.Y + region.Height;
+
+ // Bail out of optimization if there's too few rows to make this worth it
+ if (maxY - minY < 16)
+ {
+ for (int y = minY; y < maxY; y++)
+ {
+ for (int x = minX; x < maxX; x++)
+ {
+ *(_scan0 + x + y * Stride) = color;
+ }
+ }
+ return;
+ }
+
+ ulong strideWidth = (ulong)region.Width * BytesPerPixel;
+
+ // Uniform color pixel values can be mem-set straight away
+ int component = (color & 0xFF);
+ if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) &&
+ component == ((color >> 24) & 0xFF))
+ {
+ for (int y = minY; y < maxY; y++)
+ {
+ memset(_scan0 + minX + y * Stride, component, strideWidth);
+ }
+ }
+ else
+ {
+ // Prepare a horizontal slice of pixels that will be copied over each horizontal row down.
+ var row = new int[region.Width];
+
+ fixed (int* pRow = row)
+ {
+ int count = region.Width;
+ int rem = count % 8;
+ count /= 8;
+ int* pSrc = pRow;
+ while (count-- > 0)
+ {
+ *pSrc++ = color;
+ *pSrc++ = color;
+ *pSrc++ = color;
+ *pSrc++ = color;
+
+ *pSrc++ = color;
+ *pSrc++ = color;
+ *pSrc++ = color;
+ *pSrc++ = color;
+ }
+ while (rem-- > 0)
+ {
+ *pSrc++ = color;
+ }
+
+ var sx = _scan0 + minX;
+ for (int y = minY; y < maxY; y++)
+ {
+ memcpy(sx + y * Stride, pRow, strideWidth);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Copies a region of the source bitmap into this fast bitmap
+ /// </summary>
+ /// <param name="source">The source image to copy</param>
+ /// <param name="srcRect">The region on the source bitmap that will be copied over</param>
+ /// <param name="destRect">The region on this fast bitmap that will be changed</param>
+ /// <exception cref="ArgumentException">The provided source bitmap is the same bitmap locked in this FastBitmap</exception>
+ public void CopyRegion(Bitmap source, Rectangle srcRect, Rectangle destRect)
+ {
+ // Throw exception when trying to copy same bitmap over
+ if (source == _bitmap)
+ {
+ throw new ArgumentException(@"Copying regions across the same bitmap is not supported", nameof(source));
+ }
+
+ var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height);
+ var destBitmapRect = new Rectangle(0, 0, Width, Height);
+
+ // Check if the rectangle configuration doesn't generate invalid states or does not affect the target image
+ if (srcRect.Width <= 0 || srcRect.Height <= 0 || destRect.Width <= 0 || destRect.Height <= 0 ||
+ !srcBitmapRect.IntersectsWith(srcRect) || !destRect.IntersectsWith(destBitmapRect))
+ return;
+
+ // Find the areas of the first and second bitmaps that are going to be affected
+ srcBitmapRect = Rectangle.Intersect(srcRect, srcBitmapRect);
+
+ // Clip the source rectangle on top of the destination rectangle in a way that clips out the regions of the original bitmap
+ // that will not be drawn on the destination bitmap for being out of bounds
+ srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(srcRect.X, srcRect.Y, destRect.Width, destRect.Height));
+
+ destBitmapRect = Rectangle.Intersect(destRect, destBitmapRect);
+
+ // Clip the source bitmap region yet again here
+ srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(-destRect.X + srcRect.X, -destRect.Y + srcRect.Y, Width, Height));
+
+ // Calculate the rectangle containing the maximum possible area that is supposed to be affected by the copy region operation
+ int copyWidth = Math.Min(srcBitmapRect.Width, destBitmapRect.Width);
+ int copyHeight = Math.Min(srcBitmapRect.Height, destBitmapRect.Height);
+
+ if (copyWidth == 0 || copyHeight == 0)
+ return;
+
+ int srcStartX = srcBitmapRect.Left;
+ int srcStartY = srcBitmapRect.Top;
+
+ int destStartX = destBitmapRect.Left;
+ int destStartY = destBitmapRect.Top;
+
+ using (var fastSource = source.FastLock())
+ {
+ ulong strideWidth = (ulong)copyWidth * BytesPerPixel;
+
+ // Perform copies of whole pixel rows
+ for (int y = 0; y < copyHeight; y++)
+ {
+ int destX = destStartX;
+ int destY = destStartY + y;
+
+ int srcX = srcStartX;
+ int srcY = srcStartY + y;
+
+ long offsetSrc = (srcX + srcY * fastSource.Stride);
+ long offsetDest = (destX + destY * Stride);
+
+ memcpy(_scan0 + offsetDest, fastSource._scan0 + offsetSrc, strideWidth);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Performs a copy operation of the pixels from the Source bitmap to the Target bitmap.
+ /// If the dimensions or pixel depths of both images don't match, the copy is not performed
+ /// </summary>
+ /// <param name="source">The bitmap to copy the pixels from</param>
+ /// <param name="target">The bitmap to copy the pixels to</param>
+ /// <returns>Whether the copy proceedure was successful</returns>
+ /// <exception cref="ArgumentException">The provided source and target bitmaps are the same</exception>
+ public static bool CopyPixels(Bitmap source, Bitmap target)
+ {
+ if (source == target)
+ {
+ throw new ArgumentException(@"Copying pixels across the same bitmap is not supported", nameof(source));
+ }
+
+ if (source.Width != target.Width || source.Height != target.Height || source.PixelFormat != target.PixelFormat)
+ return false;
+
+ using (FastBitmap fastSource = source.FastLock(),
+ fastTarget = target.FastLock())
+ {
+ memcpy(fastTarget.Scan0, fastSource.Scan0, (ulong)(fastSource.Height * fastSource.Stride * BytesPerPixel));
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Clears the given bitmap with the given color
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clear</param>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public static void ClearBitmap(Bitmap bitmap, Color color)
+ {
+ ClearBitmap(bitmap, color.ToArgb());
+ }
+
+ /// <summary>
+ /// Clears the given bitmap with the given color
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clear</param>
+ /// <param name="color">The color to clear the bitmap with</param>
+ public static void ClearBitmap(Bitmap bitmap, int color)
+ {
+ using (var fb = bitmap.FastLock())
+ {
+ fb.Clear(color);
+ }
+ }
+
+ /// <summary>
+ /// Copies a region of the source bitmap to a target bitmap
+ /// </summary>
+ /// <param name="source">The source image to copy</param>
+ /// <param name="target">The target image to be altered</param>
+ /// <param name="srcRect">The region on the source bitmap that will be copied over</param>
+ /// <param name="destRect">The region on the target bitmap that will be changed</param>
+ /// <exception cref="ArgumentException">The provided source and target bitmaps are the same bitmap</exception>
+ public static void CopyRegion(Bitmap source, Bitmap target, Rectangle srcRect, Rectangle destRect)
+ {
+ var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height);
+ var destBitmapRect = new Rectangle(0, 0, target.Width, target.Height);
+
+ // If the copy operation results in an entire copy, use CopyPixels instead
+ if (srcBitmapRect == srcRect && destBitmapRect == destRect && srcBitmapRect == destBitmapRect)
+ {
+ CopyPixels(source, target);
+ return;
+ }
+
+ using (var fastTarget = target.FastLock())
+ {
+ fastTarget.CopyRegion(source, srcRect, destRect);
+ }
+ }
+
+ /// <summary>
+ /// Returns a bitmap that is a slice of the original provided 32bpp Bitmap.
+ /// The region must have a width and a height > 0, and must lie inside the source bitmap's area
+ /// </summary>
+ /// <param name="source">The source bitmap to slice</param>
+ /// <param name="region">The region of the source bitmap to slice</param>
+ /// <returns>A Bitmap that represents the rectangle region slice of the source bitmap</returns>
+ /// <exception cref="ArgumentException">The provided bimap is not 32bpp</exception>
+ /// <exception cref="ArgumentException">The provided region is invalid</exception>
+ public static Bitmap SliceBitmap(Bitmap source, Rectangle region)
+ {
+ if (region.Width <= 0 || region.Height <= 0)
+ {
+ throw new ArgumentException(@"The provided region must have a width and a height > 0", nameof(region));
+ }
+
+ var sliceRectangle = Rectangle.Intersect(new Rectangle(Point.Empty, source.Size), region);
+
+ if (sliceRectangle.IsEmpty)
+ {
+ throw new ArgumentException(@"The provided region must not lie outside of the bitmap's region completely", nameof(region));
+ }
+
+ var slicedBitmap = new Bitmap(sliceRectangle.Width, sliceRectangle.Height);
+ CopyRegion(source, slicedBitmap, sliceRectangle, new Rectangle(0, 0, sliceRectangle.Width, sliceRectangle.Height));
+
+ return slicedBitmap;
+ }
+
+#if NETSTANDARD
+ public static void memcpy(IntPtr dest, IntPtr src, ulong count)
+ {
+ Buffer.MemoryCopy(src.ToPointer(), dest.ToPointer(), count, count);
+ }
+
+ public static void memcpy(void* dest, void* src, ulong count)
+ {
+ Buffer.MemoryCopy(src, dest, count, count);
+ }
+
+ public static void memset(void* dest, int value, ulong count)
+ {
+ Unsafe.InitBlock(dest, (byte)value, (uint)count);
+ }
+#else
+ /// <summary>
+ /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count);
+
+ /// <summary>
+ /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memcpy(void* dest, void* src, ulong count);
+
+ /// <summary>
+ /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed
+ /// </summary>
+ [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)]
+ public static extern IntPtr memset(void* dest, int value, ulong count);
+#endif
+
+ /// <summary>
+ /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls
+ /// </summary>
+ public struct FastBitmapLocker : IDisposable
+ {
+ /// <summary>
+ /// Gets the fast bitmap instance attached to this locker
+ /// </summary>
+ public FastBitmap FastBitmap { get; }
+
+ /// <summary>
+ /// Initializes a new instance of the FastBitmapLocker struct with an initial fast bitmap object.
+ /// The fast bitmap object passed will be unlocked after calling Dispose() on this struct
+ /// </summary>
+ /// <param name="fastBitmap">A fast bitmap to attach to this locker which will be released after a call to Dispose</param>
+ public FastBitmapLocker(FastBitmap fastBitmap)
+ {
+ FastBitmap = fastBitmap;
+ }
+
+ /// <summary>
+ /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap
+ /// </summary>
+ public void Dispose()
+ {
+ if (FastBitmap.Locked)
+ FastBitmap.Unlock();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Describes a pixel format to use when locking a bitmap using <see cref="FastBitmap"/>.
+ /// </summary>
+ public enum FastBitmapLockFormat
+ {
+ /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, and blue components. The remaining 8 bits are not used.</summary>
+ Format32bppRgb = 139273,
+ /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component.</summary>
+ Format32bppPArgb = 925707,
+ /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.</summary>
+ Format32bppArgb = 2498570,
+ }
+
+ /// <summary>
+ /// Static class that contains fast bitmap extension methdos for the Bitmap class
+ /// </summary>
+ public static class FastBitmapExtensions
+ {
+ /// <summary>
+ /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels
+ /// </summary>
+ /// <param name="bitmap">The bitmap to lock</param>
+ /// <returns>A locked FastBitmap</returns>
+ public static FastBitmap FastLock(this Bitmap bitmap)
+ {
+ var fast = new FastBitmap(bitmap);
+ fast.Lock();
+
+ return fast;
+ }
+
+ /// <summary>
+ /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels
+ /// </summary>
+ /// <param name="bitmap">The bitmap to lock</param>
+ /// <param name="lockFormat">The underlying pixel format to use when locking the bitmap</param>
+ /// <returns>A locked FastBitmap</returns>
+ public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat)
+ {
+ var fast = new FastBitmap(bitmap);
+ fast.Lock(lockFormat);
+
+ return fast;
+ }
+
+ /// <summary>
+ /// Returns a deep clone of this Bitmap object, with all the data copied over.
+ /// After a deep clone, the new bitmap is completely independent from the original
+ /// </summary>
+ /// <param name="bitmap">The bitmap to clone</param>
+ /// <returns>A deep clone of this Bitmap object, with all the data copied over</returns>
+ public static Bitmap DeepClone(this Bitmap bitmap)
+ {
+ return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat);
+ }
+ }
+}