diff options
| author | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-03-02 23:30:34 +0200 |
|---|---|---|
| committer | Roy Ben Shabat <Roy.mail.net@gmail.com> | 2020-03-02 23:30:34 +0200 |
| commit | 0dcd742a3c35527386a93e1b1ef761c2aeff8308 (patch) | |
| tree | d5adb3fee35e73af95fa5d68b5316d25522471de /Software/Experiments | |
| parent | 1a7fb274158f8a0e279aef26206a65fefac8c4c3 (diff) | |
| download | Tango-0dcd742a3c35527386a93e1b1ef761c2aeff8308.tar.gz Tango-0dcd742a3c35527386a93e1b1ef761c2aeff8308.zip | |
Implemented Tango.RemoteDesktop.
Implemented png 8 bit quantization.
Implemented RasterFrame bounds clipping.
Refactored VectorFrame to use indexed colors.
Diffstat (limited to 'Software/Experiments')
42 files changed, 6718 insertions, 99 deletions
diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs new file mode 100644 index 000000000..66a2f8660 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/BitmapComparerResult.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop +{ + /// <summary> + /// Represents an <see cref="IBitmapComparer{TFrame}.CreateDifference(System.Drawing.Bitmap, System.Drawing.Bitmap)"/> result. + /// </summary> + /// <typeparam name="TFrame">The type of the frame.</typeparam> + public class BitmapComparerResult<TFrame> where TFrame : IFrame + { + /// <summary> + /// Gets or sets the difference frame. + /// </summary> + public TFrame Frame { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the <see cref="Frame"/> contains any differences. + /// </summary> + public bool ContainsDifference { get; set; } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs index c41ccbe03..d8f1e214c 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/BitBltScreenCapture.cs @@ -8,6 +8,10 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.CaptureMethods { + /// <summary> + /// Represents a BitBlt screen capture method. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" /> public class BitBltScreenCapture : ICaptureMethod { #region Win32 API Screen shot calls @@ -25,6 +29,11 @@ namespace Tango.RemoteDesktop.CaptureMethods #endregion + /// <summary> + /// Gets the desktop bitmap. + /// </summary> + /// <param name="region">The capture region.</param> + /// <returns></returns> public Bitmap GetDesktopBitmap(CaptureRegion region) { const int SRCCOPY = 13369376; @@ -48,6 +57,9 @@ namespace Tango.RemoteDesktop.CaptureMethods return bitmap; } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public void Dispose() { //Do nothing. diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs index 50cb9dc76..1a661b27e 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/DirectXScreenCapture.cs @@ -7,11 +7,16 @@ using System.Threading.Tasks; using SharpDX; using SharpDX.Direct3D11; using SharpDX.DXGI; +using Tango.RemoteDesktop.Utils; using Device = SharpDX.Direct3D11.Device; using MapFlags = SharpDX.Direct3D11.MapFlags; namespace Tango.RemoteDesktop.CaptureMethods { + /// <summary> + /// Represents a high performance DirectX screen capture method. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" /> public class DirectXScreenCapture : ICaptureMethod { private static bool _hasInstance; @@ -22,10 +27,20 @@ namespace Tango.RemoteDesktop.CaptureMethods private int monitorWidth; private int monitorHeight; + /// <summary> + /// Gets or sets the index of the graphics adapter. + /// </summary> public int AdapterIndex { get; set; } + /// <summary> + /// Gets or sets the index of the monitor within the graphics adapter. + /// </summary> public int MonitorIndex { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="DirectXScreenCapture"/> class. + /// </summary> + /// <exception cref="InvalidOperationException">An instance of the DirectX screen capture method already exists. Please dispose it before attempting to create a new one.</exception> public DirectXScreenCapture() { if (_hasInstance) @@ -69,6 +84,11 @@ namespace Tango.RemoteDesktop.CaptureMethods _hasInstance = true; } + /// <summary> + /// Gets the desktop bitmap. + /// </summary> + /// <param name="region">The capture region.</param> + /// <returns></returns> public virtual Bitmap GetDesktopBitmap(CaptureRegion region) { var screenTexture = new Texture2D(device, textureDesc); @@ -128,6 +148,9 @@ namespace Tango.RemoteDesktop.CaptureMethods return bmpImage.Clone(cropArea, bmpImage.PixelFormat); } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public void Dispose() { _hasInstance = false; diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs index a9d228e23..92f4403fd 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureMethods/GdiScreenCapture.cs @@ -7,11 +7,20 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.CaptureMethods { + /// <summary> + /// Represents a standard GDI screen capture method. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.ICaptureMethod" /> public class GdiScreenCapture : ICaptureMethod { + /// <summary> + /// Gets the desktop bitmap. + /// </summary> + /// <param name="region">The capture region.</param> + /// <returns></returns> public Bitmap GetDesktopBitmap(CaptureRegion region) { - var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb); + var bitmap = new Bitmap(region.Width, region.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb); using (Graphics g = Graphics.FromImage(bitmap)) { @@ -21,6 +30,9 @@ namespace Tango.RemoteDesktop.CaptureMethods return bitmap; } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public void Dispose() { //Do nothing diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs index 18e48de4e..511a61e88 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/CaptureRegion.cs @@ -8,18 +8,43 @@ using System.Windows; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents a capture rectangle region. + /// </summary> public class CaptureRegion { + /// <summary> + /// Gets or sets the region left corner. + /// </summary> public int Left { get; set; } + + /// <summary> + /// Gets or sets the region top corner. + /// </summary> public int Top { get; set; } + + /// <summary> + /// Gets or sets the region width. + /// </summary> public int Width { get; set; } + + /// <summary> + /// Gets or sets the region height. + /// </summary> public int Height { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="CaptureRegion"/> class. + /// </summary> public CaptureRegion() { } + /// <summary> + /// Initializes a new instance of the <see cref="CaptureRegion"/> class using a <see cref="Rectangle"/> object. + /// </summary> + /// <param name="rect">The rectangle..</param> public CaptureRegion(Rectangle rect) { Left = rect.Left; @@ -28,6 +53,10 @@ namespace Tango.RemoteDesktop Height = rect.Height; } + /// <summary> + /// Initializes a new instance of the <see cref="CaptureRegion"/> class using a <see cref="Rect"/> object. + /// </summary> + /// <param name="rect">The rectangle.</param> public CaptureRegion(Rect rect) { Left = (int)rect.Left; @@ -36,6 +65,13 @@ namespace Tango.RemoteDesktop Height = (int)rect.Height; } + /// <summary> + /// Initializes a new instance of the <see cref="CaptureRegion"/> class. + /// </summary> + /// <param name="left">The left.</param> + /// <param name="top">The top.</param> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> public CaptureRegion(int left, int top, int width, int height) { Left = left; @@ -44,6 +80,10 @@ namespace Tango.RemoteDesktop Height = height; } + /// <summary> + /// Creates a capture region from by the bounds of the primary screen. + /// </summary> + /// <returns></returns> public static CaptureRegion PrimaryScreenBounds() { return new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs new file mode 100644 index 000000000..4f93eeb66 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/BitmapCliper.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Clipping +{ + public class BitmapCliper + { + public static ClipResult ClipBitmap(Bitmap img) + { + //get image data + BitmapData bd = img.LockBits(new Rectangle(Point.Empty, img.Size), + ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + int[] rgbValues = new int[img.Height * img.Width]; + Marshal.Copy(bd.Scan0, rgbValues, 0, rgbValues.Length); + img.UnlockBits(bd); + + + #region determine bounds + int left = bd.Width; + int top = bd.Height; + int right = 0; + int bottom = 0; + + //determine top + for (int i = 0; i < rgbValues.Length; i++) + { + int color = rgbValues[i] & 0x00ffffff; + if (color != 0x00ffffff) + { + int r = i / bd.Width; + int c = i % bd.Width; + + if (left > c) + { + left = c; + } + if (right < c) + { + right = c; + } + bottom = r; + top = r; + break; + } + } + + //determine bottom + for (int i = rgbValues.Length - 1; i >= 0; i--) + { + int color = rgbValues[i] & 0x00ffffff; + if (color != 0x00ffffff) + { + int r = i / bd.Width; + int c = i % bd.Width; + + if (left > c) + { + left = c; + } + if (right < c) + { + right = c; + } + bottom = r; + break; + } + } + + if (bottom > top) + { + for (int r = top + 1; r < bottom; r++) + { + //determine left + for (int c = 0; c < left; c++) + { + int color = rgbValues[r * bd.Width + c] & 0x00ffffff; + if (color != 0x00ffffff) + { + if (left > c) + { + left = c; + break; + } + } + } + + //determine right + for (int c = bd.Width - 1; c > right; c--) + { + int color = rgbValues[r * bd.Width + c] & 0x00ffffff; + if (color != 0x00ffffff) + { + if (right < c) + { + right = c; + break; + } + } + } + } + } + + int width = right - left + 1; + int height = bottom - top + 1; + #endregion + + if (width < 0 || height < 0) + { + return new ClipResult() + { + Bitmap = new Bitmap(1, 1), + Bounds = new Rectangle(0, 0, 1, 1) + }; + } + + //copy image data + int[] imgData = new int[width * height]; + for (int r = top; r <= bottom; r++) + { + Array.Copy(rgbValues, r * bd.Width + left, imgData, (r - top) * width, width); + } + + //create new image + Bitmap newImage = new Bitmap(width, height, PixelFormat.Format32bppArgb); + BitmapData nbd + = newImage.LockBits(new Rectangle(0, 0, width, height), + ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); + Marshal.Copy(imgData, 0, nbd.Scan0, imgData.Length); + newImage.UnlockBits(nbd); + + return new ClipResult() + { + Bitmap = newImage, + Bounds = new Rectangle(left, top, width, height) + }; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/ClipResult.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Clipping/ClipResult.cs new file mode 100644 index 000000000..bd0a52679 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/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/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs index fc9de34e1..34250cc59 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/RasterBitmapComparer.cs @@ -9,9 +9,24 @@ using Tango.RemoteDesktop.Frames; namespace Tango.RemoteDesktop.Comparers { + /// <summary> + /// Represents a raster <see cref="IBitmapComparer{TFrame}"/> comparer which will compare and return a <see cref="RasterFrame"/> difference. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.IBitmapComparer{Tango.RemoteDesktop.Frames.RasterFrame}" /> public class RasterBitmapComparer : IBitmapComparer<RasterFrame> { - unsafe public RasterFrame CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap) + /// <summary> + /// Creates the difference as <see cref="RasterFrame"/>. + /// </summary> + /// <param name="previousBitmap">The previous bitmap.</param> + /// <param name="currentBitmap">The current bitmap.</param> + /// <returns></returns> + /// <exception cref="InvalidOperationException"> + /// Cannot compare image. They are the same instance + /// or + /// Cannot compare image of different size. + /// </exception> + unsafe public BitmapComparerResult<RasterFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap) { if (previousBitmap == null | currentBitmap == null) throw new InvalidOperationException("Cannot compare image. They are the same instance"); @@ -19,6 +34,8 @@ namespace Tango.RemoteDesktop.Comparers if (previousBitmap.Height != currentBitmap.Height || previousBitmap.Width != currentBitmap.Width) throw new InvalidOperationException("Cannot compare image of different size."); + bool hasDifference = false; + Color matchColor = Color.Transparent; Bitmap diffImage = currentBitmap.Clone() as Bitmap; @@ -73,6 +90,11 @@ namespace Tango.RemoteDesktop.Comparers diffPtr[0] = (same == 4) ? swapColor[x] : tmp[x]; diffPtr++; // advance diff image ptr } + + if (same != 4) + { + hasDifference = true; + } } // at the end of each column, skip extra padding @@ -88,7 +110,11 @@ namespace Tango.RemoteDesktop.Comparers currentBitmap.UnlockBits(data2); diffImage.UnlockBits(diffData); - return new RasterFrame(diffImage); + return new BitmapComparerResult<RasterFrame>() + { + Frame = new RasterFrame(diffImage), + ContainsDifference = hasDifference + }; } } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs index 9ffd04c0b..2500e8ac7 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Comparers/VectorBitmapComparer.cs @@ -5,16 +5,34 @@ using System.Drawing.Imaging; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Windows.Media.Imaging; using Tango.RemoteDesktop.Frames; namespace Tango.RemoteDesktop.Comparers { + /// <summary> + /// Represents a vector <see cref="IBitmapComparer{TFrame}"/> comparer which will compare and return a <see cref="VectorFrame"/> difference. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.IBitmapComparer{Tango.RemoteDesktop.Frames.VectorFrame}" /> public class VectorBitmapComparer : IBitmapComparer<VectorFrame> { - public unsafe VectorFrame CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap) + /// <summary> + /// Creates the difference as <see cref="VectorFrame"/>. + /// </summary> + /// <param name="previousBitmap">The previous bitmap.</param> + /// <param name="currentBitmap">The current bitmap.</param> + /// <returns></returns> + /// <exception cref="InvalidOperationException"> + /// Cannot compare image. They are the same instance + /// or + /// Cannot compare image of different size. + /// </exception> + public unsafe BitmapComparerResult<VectorFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap) { VectorFrame vector = new VectorFrame(previousBitmap.Width, previousBitmap.Height); + bool hasDifference = false; + if (previousBitmap == null | currentBitmap == null) throw new InvalidOperationException("Cannot compare image. They are the same instance"); @@ -57,14 +75,36 @@ namespace Tango.RemoteDesktop.Comparers if (same != 4) { - vector.Pixels.Add(new VectorFramePixel() + int colorIndex = vector.Colors.Count; + + VectorFrameColor color = new VectorFrameColor() { - X = (ushort)j, - Y = (ushort)i, B = tmp[0], G = tmp[1], R = tmp[2], + Index = colorIndex + }; + + VectorFrameColor existingColor; + + if (vector.Colors.TryGetValue(color.ToInt32(), out existingColor)) + { + colorIndex = existingColor.Index; + } + else + { + vector.Colors.Add(color.ToInt32(), color); + } + + vector.Pixels.Add(new VectorFramePixel() + { + X = j, + Y = i, + ColorIndex = colorIndex, + Color = color }); + + hasDifference = true; } } @@ -79,7 +119,11 @@ namespace Tango.RemoteDesktop.Comparers previousBitmap.UnlockBits(data1); currentBitmap.UnlockBits(data2); - return vector; + return new BitmapComparerResult<VectorFrame>() + { + Frame = vector, + ContainsDifference = hasDifference + }; } } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs index 55fba9d59..18d144518 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/BitmapEncoder.cs @@ -8,12 +8,24 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.Encoders { + /// <summary> + /// Represents an <see cref="IFrame"/> BMP encoder. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" /> public class BitmapEncoder : FrameEncoder { + /// <summary> + /// Initializes a new instance of the <see cref="BitmapEncoder"/> class. + /// </summary> + /// <param name="frame">The frame.</param> public BitmapEncoder(IFrame frame) : base(frame) { } + /// <summary> + /// Returns a stream containing the encoded frame. + /// </summary> + /// <returns></returns> public override MemoryStream ToStream() { MemoryStream ms = new MemoryStream(); diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs new file mode 100644 index 000000000..abf69d9a8 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/GifEncoder.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Encoders +{ + public class GifEncoder : JpegEncoder + { + public GifEncoder(IFrame frame) : base(frame) + { + + } + + public override MemoryStream ToStream() + { + return ToStream(100); + } + + public override MemoryStream ToStream(long quality) + { + MemoryStream ms = new MemoryStream(); + var gif = MakeTransparentGif(Frame.ToBitmap(), Color.Transparent); + gif.Save(ms, ImageFormat.Gif); + gif.Dispose(); + ms.Position = 0; + return ms; + } + + public static Bitmap MakeTransparentGif(Bitmap bitmap, Color color) + { + byte R = color.R; + byte G = color.G; + byte B = color.B; + MemoryStream fin = new MemoryStream(); + bitmap.Save(fin, System.Drawing.Imaging.ImageFormat.Gif); + MemoryStream fout = new MemoryStream((int)fin.Length); + int count = 0; + byte[] buf = new byte[256]; + byte transparentIdx = 0; + fin.Seek(0, SeekOrigin.Begin); + //header + count = fin.Read(buf, 0, 13); + if ((buf[0] != 71) || (buf[1] != 73) || (buf[2] != 70)) return null; //GIF + fout.Write(buf, 0, 13); + int i = 0; + if ((buf[10] & 0x80) > 0) + { + i = 1 << ((buf[10] & 7) + 1) == 256 ? 256 : 0; + } + for (; i != 0; i--) + { + fin.Read(buf, 0, 3); + if ((buf[0] == R) && (buf[1] == G) && (buf[2] == B)) + { + transparentIdx = (byte)(256 - i); + } + fout.Write(buf, 0, 3); + } + bool gcePresent = false; + while (true) + { + fin.Read(buf, 0, 1); + fout.Write(buf, 0, 1); + if (buf[0] != 0x21) break; + fin.Read(buf, 0, 1); + fout.Write(buf, 0, 1); + gcePresent = (buf[0] == 0xf9); + while (true) + { + fin.Read(buf, 0, 1); + fout.Write(buf, 0, 1); + if (buf[0] == 0) break; + count = buf[0]; + if (fin.Read(buf, 0, count) != count) return null; + if (gcePresent) + { + if (count == 4) + { + buf[0] |= 0x01; + buf[3] = transparentIdx; + } + } + fout.Write(buf, 0, count); + } + } + while (count > 0) + { + count = fin.Read(buf, 0, 1); + fout.Write(buf, 0, 1); + } + fin.Close(); + fout.Flush(); + return new Bitmap(fout); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs index 467e37662..744849977 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/JpegEncoder.cs @@ -8,18 +8,35 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.Encoders { + /// <summary> + /// Represents an <see cref="IFrame"/> JPEG encoder. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" /> public class JpegEncoder : FrameEncoder { + /// <summary> + /// Initializes a new instance of the <see cref="JpegEncoder"/> class. + /// </summary> + /// <param name="frame">The frame.</param> public JpegEncoder(IFrame frame) : base(frame) { } + /// <summary> + /// Returns a stream containing the encoded frame. + /// </summary> + /// <returns></returns> public override MemoryStream ToStream() { return ToStream(100); } - public MemoryStream ToStream(long quality) + /// <summary> + /// Returns a stream containing the encoded frame with the specified quality. + /// </summary> + /// <param name="quality">The quality.</param> + /// <returns></returns> + public virtual MemoryStream ToStream(long quality) { var encoderParameters = new EncoderParameters(1); encoderParameters.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); @@ -29,6 +46,11 @@ namespace Tango.RemoteDesktop.Encoders return ms; } + /// <summary> + /// Returns a byte array containing the encoded frame with the specified quality. + /// </summary> + /// <param name="quality">The quality.</param> + /// <returns></returns> public byte[] ToArray(long quality) { using (MemoryStream ms = ToStream(quality)) diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs new file mode 100644 index 000000000..a3993d76d --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/Png8BitEncoder.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Tango.RemoteDesktop.Quantization; + +namespace Tango.RemoteDesktop.Encoders +{ + public class Png8BitEncoder : FrameEncoder + { + public Png8BitEncoder(IFrame frame) : base(frame) + { + } + + public override MemoryStream ToStream() + { + MemoryStream ms = new MemoryStream(); + //var quantizer = new OctreeQuantizer(256, true); + //var quantized = quantizer.Quantize(Frame.ToBitmap()); + + ImageCodecInfo icf = GetEncoder(ImageFormat.Png); + EncoderParameters parms = new EncoderParameters(1); + EncoderParameter parm = new EncoderParameter(System.Drawing.Imaging.Encoder.ColorDepth, 8); + parms.Param[0] = parm; + Frame.ToBitmap().Save(ms, icf, parms); + + ms.Position = 0; + //quantized.Dispose(); + + return ms; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs index dead40302..96b870a77 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Encoders/PngEncoder.cs @@ -8,12 +8,24 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.Encoders { + /// <summary> + /// Represents an <see cref="IFrame"/> PNG encoder. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.FrameEncoder" /> public class PngEncoder : FrameEncoder { + /// <summary> + /// Initializes a new instance of the <see cref="PngEncoder"/> class. + /// </summary> + /// <param name="frame">The frame.</param> public PngEncoder(IFrame frame) : base(frame) { } + /// <summary> + /// Returns a stream containing the encoded frame. + /// </summary> + /// <returns></returns> public override MemoryStream ToStream() { MemoryStream ms = new MemoryStream(); diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs index 3cd2a4e84..363cc1a35 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/RasterScreenCaptureEngine.cs @@ -8,8 +8,16 @@ using Tango.RemoteDesktop.Frames; namespace Tango.RemoteDesktop.Engines { + /// <summary> + /// Represents a raster screen capture engine. + /// The difference <see cref="IFrame"/> delivered by the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event is of type <see cref="RasterFrame"/>. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.ScreenCaptureEngine{Tango.RemoteDesktop.Frames.RasterFrame}" /> public class RasterScreenCaptureEngine : ScreenCaptureEngine<RasterFrame> { + /// <summary> + /// Initializes a new instance of the <see cref="RasterScreenCaptureEngine"/> class. + /// </summary> public RasterScreenCaptureEngine() : base(new RasterBitmapComparer()) { diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs index e065cc6e5..7e14f220f 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Engines/VectorScreenCaptureEngine.cs @@ -8,6 +8,11 @@ using Tango.RemoteDesktop.Frames; namespace Tango.RemoteDesktop.Engines { + /// <summary> + /// Represents a vector screen capture engine. + /// The difference <see cref="IFrame"/> delivered by the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event is of type <see cref="VectorFrame"/>. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.ScreenCaptureEngine{Tango.RemoteDesktop.Frames.VectorFrame}" /> public class VectorScreenCaptureEngine : ScreenCaptureEngine<VectorFrame> { public VectorScreenCaptureEngine() : base(new VectorBitmapComparer()) diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs index 95e244a39..93f38bac0 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frame.cs @@ -10,21 +10,44 @@ using System.Windows.Media.Imaging; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents an image frame base class. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.IFrame" /> public abstract class Frame : IFrame { + /// <summary> + /// Gets the frame width. + /// </summary> public abstract int Width { get; } + + /// <summary> + /// Gets the frame height. + /// </summary> public abstract int Height { get; } + /// <summary> + /// Returns a GDI bitmap representing the frame. + /// </summary> + /// <returns></returns> public abstract Bitmap ToBitmap(); - public void Apply(Bitmap bitmap) + /// <summary> + /// Applies this frame onto an existing bitmap. + /// </summary> + /// <param name="bitmap">The bitmap.</param> + public virtual void Apply(Bitmap bitmap) { using (Graphics g = Graphics.FromImage(bitmap)) { - g.DrawImage(ToBitmap(), new Point(0, 0)); + g.DrawImage(ToBitmap(), new Rectangle(0, 0, bitmap.Width, bitmap.Height)); } } + /// <summary> + /// Returns a WPF BitmapSource representing the frame. + /// </summary> + /// <returns></returns> public BitmapSource ToBitmapSource() { MemoryStream ms = new MemoryStream(); @@ -38,11 +61,19 @@ namespace Tango.RemoteDesktop return bitmapImage; } - public IFrameEncoder Encode<T>() where T : IFrameEncoder + /// <summary> + /// Returns an instance of <see cref="IFrameEncoder" /> ready to encode this frame. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <returns></returns> + public IFrameEncoder ToEncoder<T>() where T : IFrameEncoder { return Activator.CreateInstance(typeof(T), new object[] { this }) as IFrameEncoder; } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public abstract void Dispose(); } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs index 183dcd1e8..a1413d9c9 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/FrameEncoder.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Drawing; +using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Text; @@ -8,17 +9,36 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents a base <see cref="IFrameEncoder"/>. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.IFrameEncoder" /> public abstract class FrameEncoder : IFrameEncoder { + /// <summary> + /// Gets the frame instance. + /// </summary> public IFrame Frame { get; private set; } + /// <summary> + /// Initializes a new instance of the <see cref="FrameEncoder"/> class using an existing <see cref="IFrame"/>. + /// </summary> + /// <param name="frame">The frame.</param> public FrameEncoder(IFrame frame) { Frame = frame; } + /// <summary> + /// Returns a stream containing the encoded frame. + /// </summary> + /// <returns></returns> public abstract MemoryStream ToStream(); + /// <summary> + /// Returns a byte array containing the encoded frame. + /// </summary> + /// <returns></returns> public byte[] ToArray() { using (var ms = ToStream()) @@ -26,5 +46,16 @@ namespace Tango.RemoteDesktop return ms.ToArray(); } } + + /// <summary> + /// Gets the image encoder by the specified image format. + /// </summary> + /// <param name="format">The format.</param> + /// <returns></returns> + protected ImageCodecInfo GetEncoder(ImageFormat format) + { + ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders(); + return codecs.Single(codec => codec.FormatID == format.Guid); + } } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs index 45b84d1ef..bcb372bd3 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/RasterFrame.cs @@ -7,36 +7,128 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Media.Imaging; +using Tango.RemoteDesktop.Clipping; namespace Tango.RemoteDesktop.Frames { + /// <summary> + /// Represents a raster frame encapsulating a standard GDI bitmap. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.Frame" /> public class RasterFrame : Frame { private Bitmap _bitmap; + /// <summary> + /// Gets the left position of this frame. + /// Will always be 0 unless <see cref="OptimizeBounds"/> was called. + /// </summary> + public int Left { get; private set; } + + /// <summary> + /// Gets the top position of this frame. + /// Will always be 0 unless <see cref="OptimizeBounds"/> was called. + /// </summary> + public int Top { get; private set; } + + /// <summary> + /// Gets the frame width. + /// </summary> public override int Width { get { return _bitmap.Width; } } + /// <summary> + /// Gets the frame height. + /// </summary> public override int Height { get { return _bitmap.Height; } } + /// <summary> + /// Initializes a new instance of the <see cref="RasterFrame"/> class. + /// </summary> + /// <param name="bitmap">The bitmap.</param> public RasterFrame(Bitmap bitmap) { _bitmap = bitmap; } - public override void Dispose() + /// <summary> + /// Initializes a new instance of the <see cref="RasterFrame"/> class. + /// </summary> + /// <param name="bitmap">The bitmap.</param> + /// <param name="left">The left position of the frame.</param> + /// <param name="top">The top position of the frame.</param> + public RasterFrame(Bitmap bitmap, int left, int top) : this(bitmap) { - _bitmap.Dispose(); + Left = left; + Top = top; } + /// <summary> + /// Returns a GDI bitmap representing the frame. + /// </summary> + /// <returns></returns> public override Bitmap ToBitmap() { return _bitmap; } + + /// <summary> + /// Scales the frame by the specified coefficient. + /// </summary> + /// <param name="coefficient">The coefficient.</param> + /// <returns></returns> + public RasterFrame Scale(float coefficient) + { + var scaled = new Bitmap(_bitmap, new Size((int)(Width * coefficient), (int)(Height * coefficient))); + _bitmap.Dispose(); + _bitmap = scaled; + return this; + } + + /// <summary> + /// Applies this frame onto an existing bitmap. + /// </summary> + /// <param name="bitmap">The bitmap.</param> + public override void Apply(Bitmap bitmap) + { + if (Top == 0 && Left == 0) + { + base.Apply(bitmap); + } + else + { + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.DrawImage(_bitmap, new Rectangle(Left, Top, Width, Height)); + } + } + } + + /// <summary> + /// Optimizes the bounds of this frame by removing unnecessary margins. + /// </summary> + /// <returns></returns> + public RasterFrame OptimizeBounds() + { + var result = BitmapCliper.ClipBitmap(_bitmap); + _bitmap.Dispose(); + Left = result.Bounds.Left; + Top = result.Bounds.Top; + _bitmap = result.Bitmap; + return this; + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + public override void Dispose() + { + _bitmap.Dispose(); + } } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs index 72bcf53ea..89287ad42 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrame.cs @@ -9,70 +9,117 @@ using Tango.RemoteDesktop.Utils; namespace Tango.RemoteDesktop.Frames { + /// <summary> + /// Represents a vector frame encapsulating a set of <see cref="VectorFramePixel"/>. + /// </summary> + /// <seealso cref="Tango.RemoteDesktop.Frame" /> public class VectorFrame : Frame { private int _width; + /// <summary> + /// Gets the frame width. + /// </summary> public override int Width { get { return _width; } } private int _height; + /// <summary> + /// Gets the frame height. + /// </summary> public override int Height { get { return _height; } } + /// <summary> + /// Gets or sets the colors dictionary. + /// </summary> + public Dictionary<int, VectorFrameColor> Colors { get; set; } + + /// <summary> + /// Gets the pixels. + /// </summary> + /// <value> + /// The pixels. + /// </value> public List<VectorFramePixel> Pixels { get; private set; } + /// <summary> + /// Initializes a new instance of the <see cref="VectorFrame"/> class. + /// </summary> + /// <param name="width">The frame width.</param> + /// <param name="height">The frame height.</param> public VectorFrame(int width, int height) { _width = width; _height = height; Pixels = new List<VectorFramePixel>(); + Colors = new Dictionary<int, VectorFrameColor>(); } + /// <summary> + /// Returns a GDI bitmap representing the frame. + /// </summary> + /// <returns></returns> public override Bitmap ToBitmap() { DirectBitmap directBitmap = new DirectBitmap(Width, Height); foreach (var pixel in Pixels) { - directBitmap.SetPixel((int)pixel.X, (int)pixel.Y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); + directBitmap.SetPixel((int)pixel.X, (int)pixel.Y, pixel.Color.ToColor()); } directBitmap.Dispose(); return directBitmap.Bitmap; } - public void ApplyUsingFastBitmap(Bitmap bitmap) + /// <summary> + /// Applies this frame onto an existing bitmap. + /// </summary> + /// <param name="bitmap">The bitmap.</param> + public override void Apply(Bitmap bitmap) { using (FastBitmap fast = bitmap.FastLock()) { foreach (var pixel in Pixels) { - fast.SetPixel((int)pixel.X, (int)pixel.Y, Color.FromArgb(pixel.R, pixel.G, pixel.B)); + fast.SetPixel((int)pixel.X, (int)pixel.Y, pixel.Color.ToColor()); } } } + /// <summary> + /// Returns a byte array containing the binary serialized version of this frame. + /// </summary> + /// <returns></returns> public byte[] Serialize() { using (MemoryStream ms = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(ms)) { - writer.Write(Width); - writer.Write(Height); - writer.Write(Pixels.Count); + writer.Write((UInt16)Width); + writer.Write((UInt16)Height); + writer.Write((UInt16)Colors.Count); - foreach (var pixel in Pixels) + if (Colors.Count > UInt16.MaxValue) { - writer.Write(pixel.X); - writer.Write(pixel.Y); + throw new InvalidOperationException("Number of color exceeded the maximum colors allowed."); + } - writer.Write(pixel.R); - writer.Write(pixel.G); - writer.Write(pixel.B); + foreach (var color in Colors) + { + writer.Write(color.Value.R); + writer.Write(color.Value.G); + writer.Write(color.Value.B); + } + + foreach (var pixel in Pixels) + { + writer.Write((UInt16)pixel.X); + writer.Write((UInt16)pixel.ColorIndex); } ms.Position = 0; @@ -81,6 +128,10 @@ namespace Tango.RemoteDesktop.Frames } } + /// <summary> + /// Calculates the size of <see cref="Serialize"/> byte output. + /// </summary> + /// <returns></returns> public int CalculateSize() { int size = 0; @@ -92,6 +143,11 @@ namespace Tango.RemoteDesktop.Frames return size; } + /// <summary> + /// Creates a new instance of <see cref="VectorFrame"/> from the specified serialized byte array. + /// </summary> + /// <param name="data">The byte array.</param> + /// <returns></returns> public static VectorFrame Deserialize(byte[] data) { using (MemoryStream ms = new MemoryStream(data)) @@ -99,20 +155,30 @@ namespace Tango.RemoteDesktop.Frames using (BinaryReader reader = new BinaryReader(ms)) { ms.Position = 0; - var vector = new VectorFrame(reader.ReadInt32(), reader.ReadInt32()); + var vector = new VectorFrame(reader.ReadUInt16(), reader.ReadUInt16()); - int count = reader.ReadInt32(); + int colorCount = reader.ReadUInt16(); //Number of colors in color table. - for (int i = 0; i < count; i++) + for (int i = 0; i < colorCount; i++) { - VectorFramePixel pixel = new VectorFramePixel(); - pixel.X = reader.ReadUInt16(); - pixel.Y = reader.ReadUInt16(); + VectorFrameColor color = new VectorFrameColor() + { + R = reader.ReadByte(), + G = reader.ReadByte(), + B = reader.ReadByte(), + Index = i, + }; - pixel.R = reader.ReadByte(); - pixel.G = reader.ReadByte(); - pixel.B = reader.ReadByte(); + vector.Colors.Add(color.ToInt32(), color); + } + for (int i = 0; i < vector.Height; i++) + { + VectorFramePixel pixel = new VectorFramePixel(); + pixel.X = reader.ReadUInt16(); + pixel.Y = i; + pixel.ColorIndex = reader.ReadUInt16(); + pixel.Color = vector.Colors.ElementAt(pixel.ColorIndex).Value; vector.Pixels.Add(pixel); } @@ -121,10 +187,41 @@ namespace Tango.RemoteDesktop.Frames } } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public override void Dispose() { Pixels.Clear(); Pixels = null; } + + #region Color Helpers + + /// <summary> + /// Converts the specified color to integer. + /// </summary> + /// <param name="color">The color.</param> + /// <returns></returns> + public static int ColorToInteger(byte r, byte g, byte b) + { + return (int)((255 << 24) | (r << 16) | (g << 8) | (b << 0)); + } + + /// <summary> + /// Converts the specified integer to color. + /// </summary> + /// <param name="integer">The integer.</param> + /// <returns></returns> + public static Color IntegerToColor(int integer) + { + byte a = (byte)(integer >> 24); + byte r = (byte)(integer >> 16); + byte g = (byte)(integer >> 8); + byte b = (byte)(integer >> 0); + return Color.FromArgb(a, r, g, b); + } + + #endregion } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs new file mode 100644 index 000000000..4bdb9ab68 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFrameColor.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Frames +{ + public struct VectorFrameColor + { + /// <summary> + /// Gets or sets the pixel color red component. + /// </summary> + public byte R { get; set; } + + /// <summary> + /// Gets or sets the pixel color green component. + /// </summary> + public byte G { get; set; } + + /// <summary> + /// Gets or sets the pixel color blue component. + /// </summary> + public byte B { get; set; } + + /// <summary> + /// Gets or sets the color index within the dictionary. + /// </summary> + public int Index { get; set; } + + /// <summary> + /// Determines whether the specified <see cref="System.Object" />, is equal to this instance. + /// </summary> + /// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param> + /// <returns> + /// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>. + /// </returns> + public override bool Equals(object obj) + { + var other = (VectorFrameColor)obj; + return R == other.R && G == other.G && B == other.B; + } + + /// <summary> + /// Returns a hash code for this instance. + /// </summary> + /// <returns> + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// </returns> + public override int GetHashCode() + { + var hashCode = -1520100960; + hashCode = hashCode * -1521134295 + R.GetHashCode(); + hashCode = hashCode * -1521134295 + G.GetHashCode(); + hashCode = hashCode * -1521134295 + B.GetHashCode(); + return hashCode; + } + + /// <summary> + /// Returns a 32 bit integer representing this color. + /// </summary> + /// <returns></returns> + public int ToInt32() + { + return (int)((255 << 24) | (R << 16) | (G << 8) | (B << 0)); + } + + /// <summary> + /// Creates a color from the specified 32 bit integer. + /// </summary> + /// <param name="integer">The integer.</param> + /// <returns></returns> + public static VectorFrameColor FromInt32(int integer) + { + byte a = (byte)(integer >> 24); + byte r = (byte)(integer >> 16); + byte g = (byte)(integer >> 8); + byte b = (byte)(integer >> 0); + return new VectorFrameColor() + { + R = r, + G = g, + B = g, + }; + } + + public Color ToColor() + { + return Color.FromArgb(R, G, B); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs index 048b7b94b..df1a71232 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Frames/VectorFramePixel.cs @@ -1,17 +1,34 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Tango.RemoteDesktop.Frames { + /// <summary> + /// Represents a <see cref="VectorFrame"/> pixel. + /// </summary> public struct VectorFramePixel { - public ushort X { get; set; } - public ushort Y { get; set; } - public byte R { get; set; } - public byte G { get; set; } - public byte B { get; set; } + /// <summary> + /// Gets or sets the x position. + /// </summary> + public int X { get; set; } + /// <summary> + /// Gets or sets the y position. + /// </summary> + public int Y { get; set; } + + /// <summary> + /// Gets or sets the index of the color. + /// </summary> + public int ColorIndex { get; set; } + + /// <summary> + /// Gets or sets the pixel color (not for serialization!). + /// </summary> + public VectorFrameColor Color { get; set; } } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs index f20edac98..6e1f8e999 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IBitmapComparer.cs @@ -7,8 +7,18 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop { - public interface IBitmapComparer<T> where T : IFrame + /// <summary> + /// Represents a bitmap comparison engine which returns a difference object of type <see cref="TFrame"/> between a previous and current bitmaps. + /// </summary> + /// <typeparam name="TFrame">Type of frame</typeparam> + public interface IBitmapComparer<TFrame> where TFrame : IFrame { - T CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap); + /// <summary> + /// Creates the difference result from two bitmaps. + /// </summary> + /// <param name="previousBitmap">The previous bitmap.</param> + /// <param name="currentBitmap">The current bitmap.</param> + /// <returns></returns> + BitmapComparerResult<TFrame> CreateDifference(Bitmap previousBitmap, Bitmap currentBitmap); } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs index b93df2109..0ac8add89 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ICaptureMethod.cs @@ -7,8 +7,17 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents a screen capture method. + /// </summary> + /// <seealso cref="System.IDisposable" /> public interface ICaptureMethod : IDisposable { + /// <summary> + /// Gets the desktop bitmap. + /// </summary> + /// <param name="region">The capture region.</param> + /// <returns></returns> Bitmap GetDesktopBitmap(CaptureRegion region); } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs index a217d7189..f5a69218d 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrame.cs @@ -38,10 +38,16 @@ namespace Tango.RemoteDesktop BitmapSource ToBitmapSource(); /// <summary> - /// Returns an instance of <see cref="IFrameEncoder"/> ready encode this frame. + /// Applies this frame onto an existing bitmap. + /// </summary> + /// <param name="bitmap">The bitmap.</param> + void Apply(Bitmap bitmap); + + /// <summary> + /// Returns an instance of <see cref="IFrameEncoder"/> ready to encode this frame. /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> - IFrameEncoder Encode<T>() where T : IFrameEncoder; + IFrameEncoder ToEncoder<T>() where T : IFrameEncoder; } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs index 946c90479..2df616ab8 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IFrameEncoder.cs @@ -7,10 +7,26 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents an <see cref="IFrame"/> encoder. + /// </summary> public interface IFrameEncoder { + /// <summary> + /// Gets the frame instance. + /// </summary> IFrame Frame { get; } + + /// <summary> + /// Returns a byte array containing the encoded frame. + /// </summary> + /// <returns></returns> byte[] ToArray(); + + /// <summary> + /// Returns a stream containing the encoded frame. + /// </summary> + /// <returns></returns> MemoryStream ToStream(); } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs index 17623180c..6b5db2fbe 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/IScreenCaptureEngine.cs @@ -6,17 +6,68 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop { - public interface IScreenCaptureEngine<TFrame> : IDisposable where TFrame : IFrame + /// <summary> + /// Represents a screen capture engine. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public interface IScreenCaptureEngine : IDisposable { - event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived; + /// <summary> + /// Gets or sets the screen capture method. + /// </summary> ICaptureMethod CaptureMethod { get; set; } + + /// <summary> + /// Gets or sets the screen capture region. + /// </summary> CaptureRegion CaptureRegion { get; set; } + + /// <summary> + /// Gets a value indicating whether this instance is currently capturing. + /// </summary> bool IsStarted { get; } - TimeSpan Interval { get; set; } + + /// <summary> + /// Gets or sets the frame rate per second. + /// </summary> + int FrameRate { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to include the cursor when capturing. + /// </summary> bool CaptureCursor { get; set; } - IBitmapComparer<TFrame> Comparer { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable image comparison. + /// </summary> bool EnableComparer { get; set; } + + /// <summary> + /// Start capturing. + /// </summary> void Start(); + + /// <summary> + /// Stop capturing. + /// </summary> void Stop(); } + + /// <summary> + /// Represents a screen capture engine. + /// </summary> + /// <typeparam name="TFrame">The type of the frame.</typeparam> + /// <seealso cref="System.IDisposable" /> + public interface IScreenCaptureEngine<TFrame> : IScreenCaptureEngine where TFrame : IFrame + { + /// <summary> + /// Occurs when a new screen frame is available. + /// </summary> + event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived; + + /// <summary> + /// Gets or sets the bitmap comparer. + /// </summary> + IBitmapComparer<TFrame> Comparer { get; set; } + } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs new file mode 100644 index 000000000..87c9e9637 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/ColorBgra.cs @@ -0,0 +1,1851 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace Tango.RemoteDesktop.Quantization +{ + /// <summary> + /// This is our pixel format that we will work with. It is always 32-bits / 4-bytes and is + /// always laid out in BGRA order. + /// Generally used with the Surface class. + /// </summary> + [Serializable] + [StructLayout(LayoutKind.Explicit)] + internal struct ColorBgra + { + [FieldOffset(0)] + public byte B; + + [FieldOffset(1)] + public byte G; + + [FieldOffset(2)] + public byte R; + + [FieldOffset(3)] + public byte A; + + /// <summary> + /// Lets you change B, G, R, and A at the same time. + /// </summary> + [NonSerialized] + [FieldOffset(0)] + public uint Bgra; + + public const int BlueChannel = 0; + public const int GreenChannel = 1; + public const int RedChannel = 2; + public const int AlphaChannel = 3; + + public const int SizeOf = 4; + + public static ColorBgra ParseHexString(string hexString) + { + uint value = Convert.ToUInt32(hexString, 16); + return ColorBgra.FromUInt32(value); + } + + public string ToHexString() + { + int rgbNumber = (this.R << 16) | (this.G << 8) | this.B; + string colorString = Convert.ToString(rgbNumber, 16); + + while (colorString.Length < 6) + { + colorString = "0" + colorString; + } + + string alphaString = System.Convert.ToString(this.A, 16); + + while (alphaString.Length < 2) + { + alphaString = "0" + alphaString; + } + + colorString = alphaString + colorString; + + return colorString.ToUpper(); + } + + /// <summary> + /// Gets or sets the byte value of the specified color channel. + /// </summary> + public unsafe byte this[int channel] + { + get + { + if (channel < 0 || channel > 3) + { + throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]"); + } + + fixed (byte *p = &B) + { + return p[channel]; + } + } + + set + { + if (channel < 0 || channel > 3) + { + throw new ArgumentOutOfRangeException("channel", channel, "valid range is [0,3]"); + } + + fixed (byte *p = &B) + { + p[channel] = value; + } + } + } + + /// <summary> + /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored. + /// </summary> + /// <returns>A value in the range 0 to 1 inclusive.</returns> + public double GetIntensity() + { + return ((0.114 * (double)B) + (0.587 * (double)G) + (0.299 * (double)R)) / 255.0; + } + + /// <summary> + /// Gets the luminance intensity of the pixel based on the values of the red, green, and blue components. Alpha is ignored. + /// </summary> + /// <returns>A value in the range 0 to 255 inclusive.</returns> + public byte GetIntensityByte() + { + return (byte)((7471 * B + 38470 * G + 19595 * R) >> 16); + } + + /// <summary> + /// Returns the maximum value out of the B, G, and R values. Alpha is ignored. + /// </summary> + /// <returns></returns> + public byte GetMaxColorChannelValue() + { + return Math.Max(this.B, Math.Max(this.G, this.R)); + } + + /// <summary> + /// Returns the average of the B, G, and R values. Alpha is ignored. + /// </summary> + /// <returns></returns> + public byte GetAverageColorChannelValue() + { + return (byte)((this.B + this.G + this.R) / 3); + } + + /// <summary> + /// Compares two ColorBgra instance to determine if they are equal. + /// </summary> + public static bool operator == (ColorBgra lhs, ColorBgra rhs) + { + return lhs.Bgra == rhs.Bgra; + } + + /// <summary> + /// Compares two ColorBgra instance to determine if they are not equal. + /// </summary> + public static bool operator != (ColorBgra lhs, ColorBgra rhs) + { + return lhs.Bgra != rhs.Bgra; + } + + /// <summary> + /// Compares two ColorBgra instance to determine if they are equal. + /// </summary> + public override bool Equals(object obj) + { + + if (obj != null && obj is ColorBgra && ((ColorBgra)obj).Bgra == this.Bgra) + { + return true; + } + else + { + return false; + } + } + + /// <summary> + /// Returns a hash code for this color value. + /// </summary> + /// <returns></returns> + public override int GetHashCode() + { + unchecked + { + return (int)Bgra; + } + } + + /// <summary> + /// Gets the equivalent GDI+ PixelFormat. + /// </summary> + /// <remarks> + /// This property always returns PixelFormat.Format32bppArgb. + /// </remarks> + public static PixelFormat PixelFormat + { + get + { + return PixelFormat.Format32bppArgb; + } + } + + /// <summary> + /// Returns a new ColorBgra with the same color values but with a new alpha component value. + /// </summary> + public ColorBgra NewAlpha(byte newA) + { + return ColorBgra.FromBgra(B, G, R, newA); + } + + /// <summary> + /// Creates a new ColorBgra instance with the given color and alpha values. + /// </summary> + [Obsolete ("Use FromBgra() instead (make sure to swap the order of your b and r parameters)")] + public static ColorBgra FromRgba(byte r, byte g, byte b, byte a) + { + return FromBgra(b, g, r, a); + } + + /// <summary> + /// Creates a new ColorBgra instance with the given color values, and 255 for alpha. + /// </summary> + [Obsolete ("Use FromBgr() instead (make sure to swap the order of your b and r parameters)")] + public static ColorBgra FromRgb(byte r, byte g, byte b) + { + return FromBgr(b, g, r); + } + + /// <summary> + /// Creates a new ColorBgra instance with the given color and alpha values. + /// </summary> + public static ColorBgra FromBgra(byte b, byte g, byte r, byte a) + { + ColorBgra color = new ColorBgra(); + color.Bgra = BgraToUInt32(b, g, r, a); + return color; + } + + /// <summary> + /// Creates a new ColorBgra instance with the given color and alpha values. + /// </summary> + public static ColorBgra FromBgraClamped(int b, int g, int r, int a) + { + return FromBgra( + Utility.ClampToByte(b), + Utility.ClampToByte(g), + Utility.ClampToByte(r), + Utility.ClampToByte(a)); + } + + /// <summary> + /// Creates a new ColorBgra instance with the given color and alpha values. + /// </summary> + public static ColorBgra FromBgraClamped(float b, float g, float r, float a) + { + return FromBgra( + Utility.ClampToByte(b), + Utility.ClampToByte(g), + Utility.ClampToByte(r), + Utility.ClampToByte(a)); + } + + /// <summary> + /// Packs color and alpha values into a 32-bit integer. + /// </summary> + public static UInt32 BgraToUInt32(byte b, byte g, byte r, byte a) + { + return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24); + } + + /// <summary> + /// Packs color and alpha values into a 32-bit integer. + /// </summary> + public static UInt32 BgraToUInt32(int b, int g, int r, int a) + { + return (uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24); + } + + /// <summary> + /// Creates a new ColorBgra instance with the given color values, and 255 for alpha. + /// </summary> + public static ColorBgra FromBgr(byte b, byte g, byte r) + { + return FromBgra(b, g, r, 255); + } + + /// <summary> + /// Constructs a new ColorBgra instance with the given 32-bit value. + /// </summary> + public static ColorBgra FromUInt32(UInt32 bgra) + { + ColorBgra color = new ColorBgra(); + color.Bgra = bgra; + return color; + } + + /// <summary> + /// Constructs a new ColorBgra instance given a 32-bit signed integer that represents an R,G,B triple. + /// Alpha will be initialized to 255. + /// </summary> + public static ColorBgra FromOpaqueInt32(Int32 bgr) + { + if (bgr < 0 || bgr > 0xffffff) + { + throw new ArgumentOutOfRangeException("bgr", "must be in the range [0, 0xffffff]"); + } + + ColorBgra color = new ColorBgra(); + color.Bgra = (uint)bgr; + color.A = 255; + + return color; + } + + public static int ToOpaqueInt32(ColorBgra color) + { + if (color.A != 255) + { + throw new InvalidOperationException("Alpha value must be 255 for this to work"); + } + + return (int)(color.Bgra & 0xffffff); + } + + /// <summary> + /// Constructs a new ColorBgra instance from the values in the given Color instance. + /// </summary> + public static ColorBgra FromColor(Color c) + { + return FromBgra(c.B, c.G, c.R, c.A); + } + + /// <summary> + /// Converts this ColorBgra instance to a Color instance. + /// </summary> + public Color ToColor() + { + return Color.FromArgb(A, R, G, B); + } + + /// <summary> + /// Smoothly blends between two colors. + /// </summary> + public static ColorBgra Blend(ColorBgra ca, ColorBgra cb, byte cbAlpha) + { + uint caA = (uint)Utility.FastScaleByteByByte((byte)(255 - cbAlpha), ca.A); + uint cbA = (uint)Utility.FastScaleByteByByte(cbAlpha, cb.A); + uint cbAT = caA + cbA; + + uint r; + uint g; + uint b; + + if (cbAT == 0) + { + r = 0; + g = 0; + b = 0; + } + else + { + r = ((ca.R * caA) + (cb.R * cbA)) / cbAT; + g = ((ca.G * caA) + (cb.G * cbA)) / cbAT; + b = ((ca.B * caA) + (cb.B * cbA)) / cbAT; + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)cbAT); + } + + /// <summary> + /// Linearly interpolates between two color values. + /// </summary> + /// <param name="from">The color value that represents 0 on the lerp number line.</param> + /// <param name="to">The color value that represents 1 on the lerp number line.</param> + /// <param name="frac">A value in the range [0, 1].</param> + /// <remarks> + /// This method does a simple lerp on each color value and on the alpha channel. It does + /// not properly take into account the alpha channel's effect on color blending. + /// </remarks> + public static ColorBgra Lerp(ColorBgra from, ColorBgra to, float frac) + { + ColorBgra ret = new ColorBgra(); + + ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac)); + ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac)); + ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac)); + ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac)); + + return ret; + } + + /// <summary> + /// Linearly interpolates between two color values. + /// </summary> + /// <param name="from">The color value that represents 0 on the lerp number line.</param> + /// <param name="to">The color value that represents 1 on the lerp number line.</param> + /// <param name="frac">A value in the range [0, 1].</param> + /// <remarks> + /// This method does a simple lerp on each color value and on the alpha channel. It does + /// not properly take into account the alpha channel's effect on color blending. + /// </remarks> + public static ColorBgra Lerp(ColorBgra from, ColorBgra to, double frac) + { + ColorBgra ret = new ColorBgra(); + + ret.B = (byte)Utility.ClampToByte(Utility.Lerp(from.B, to.B, frac)); + ret.G = (byte)Utility.ClampToByte(Utility.Lerp(from.G, to.G, frac)); + ret.R = (byte)Utility.ClampToByte(Utility.Lerp(from.R, to.R, frac)); + ret.A = (byte)Utility.ClampToByte(Utility.Lerp(from.A, to.A, frac)); + + return ret; + } + + /// <summary> + /// Blends four colors together based on the given weight values. + /// </summary> + /// <returns>The blended color.</returns> + /// <remarks> + /// The weights should be 16-bit fixed point numbers that add up to 65536 ("1.0"). + /// 4W16IP means "4 colors, weights, 16-bit integer precision" + /// </remarks> + public static ColorBgra BlendColors4W16IP(ColorBgra c1, uint w1, ColorBgra c2, uint w2, ColorBgra c3, uint w3, ColorBgra c4, uint w4) + { +#if DEBUG + /* + if ((w1 + w2 + w3 + w4) != 65536) + { + throw new ArgumentOutOfRangeException("w1 + w2 + w3 + w4 must equal 65536!"); + } + * */ +#endif + + const uint ww = 32768; + uint af = (c1.A * w1) + (c2.A * w2) + (c3.A * w3) + (c4.A * w4); + uint a = (af + ww) >> 16; + + uint b; + uint g; + uint r; + + if (a == 0) + { + b = 0; + g = 0; + r = 0; + } + else + { + b = (uint)((((long)c1.A * c1.B * w1) + ((long)c2.A * c2.B * w2) + ((long)c3.A * c3.B * w3) + ((long)c4.A * c4.B * w4)) / af); + g = (uint)((((long)c1.A * c1.G * w1) + ((long)c2.A * c2.G * w2) + ((long)c3.A * c3.G * w3) + ((long)c4.A * c4.G * w4)) / af); + r = (uint)((((long)c1.A * c1.R * w1) + ((long)c2.A * c2.R * w2) + ((long)c3.A * c3.R * w3) + ((long)c4.A * c4.R * w4)) / af); + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a); + } + + /// <summary> + /// Blends the colors based on the given weight values. + /// </summary> + /// <param name="c">The array of color values.</param> + /// <param name="w">The array of weight values.</param> + /// <returns> + /// The weights should be fixed point numbers. + /// The total summation of the weight values will be treated as "1.0". + /// Each color will be blended in proportionally to its weight value respective to + /// the total summation of the weight values. + /// </returns> + /// <remarks> + /// "WAIP" stands for "weights, arbitrary integer precision"</remarks> + public static ColorBgra BlendColorsWAIP(ColorBgra[] c, uint[] w) + { + if (c.Length != w.Length) + { + throw new ArgumentException("c.Length != w.Length"); + } + + if (c.Length == 0) + { + return ColorBgra.FromUInt32(0); + } + + long wsum = 0; + long asum = 0; + + for (int i = 0; i < w.Length; ++i) + { + wsum += w[i]; + asum += c[i].A * w[i]; + } + + uint a = (uint)((asum + (wsum >> 1)) / wsum); + + long b; + long g; + long r; + + if (a == 0) + { + b = 0; + g = 0; + r = 0; + } + else + { + b = 0; + g = 0; + r = 0; + + for (int i = 0; i < c.Length; ++i) + { + b += (long)c[i].A * c[i].B * w[i]; + g += (long)c[i].A * c[i].G * w[i]; + r += (long)c[i].A * c[i].R * w[i]; + } + + b /= asum; + g /= asum; + r /= asum; + } + + return ColorBgra.FromUInt32((uint)b + ((uint)g << 8) + ((uint)r << 16) + ((uint)a << 24)); + } + + /// <summary> + /// Blends the colors based on the given weight values. + /// </summary> + /// <param name="c">The array of color values.</param> + /// <param name="w">The array of weight values.</param> + /// <returns> + /// Each color will be blended in proportionally to its weight value respective to + /// the total summation of the weight values. + /// </returns> + /// <remarks> + /// "WAIP" stands for "weights, floating-point"</remarks> + public static ColorBgra BlendColorsWFP(ColorBgra[] c, double[] w) + { + if (c.Length != w.Length) + { + throw new ArgumentException("c.Length != w.Length"); + } + + if (c.Length == 0) + { + return ColorBgra.Transparent; + } + + double wsum = 0; + double asum = 0; + + for (int i = 0; i < w.Length; ++i) + { + wsum += w[i]; + asum += (double)c[i].A * w[i]; + } + + double a = asum / wsum; + double aMultWsum = a * wsum; + + double b; + double g; + double r; + + if (asum == 0) + { + b = 0; + g = 0; + r = 0; + } + else + { + b = 0; + g = 0; + r = 0; + + for (int i = 0; i < c.Length; ++i) + { + b += (double)c[i].A * c[i].B * w[i]; + g += (double)c[i].A * c[i].G * w[i]; + r += (double)c[i].A * c[i].R * w[i]; + } + + b /= aMultWsum; + g /= aMultWsum; + r /= aMultWsum; + } + + return ColorBgra.FromBgra((byte)b, (byte)g, (byte)r, (byte)a); + } + + public static ColorBgra Blend(ColorBgra[] colors) + { + unsafe + { + fixed (ColorBgra* pColors = colors) + { + return Blend(pColors, colors.Length); + } + } + } + + /// <summary> + /// Smoothly blends the given colors together, assuming equal weighting for each one. + /// </summary> + /// <param name="colors"></param> + /// <param name="colorCount"></param> + /// <returns></returns> + public unsafe static ColorBgra Blend(ColorBgra* colors, int count) + { + if (count < 0) + { + throw new ArgumentOutOfRangeException("count must be 0 or greater"); + } + + if (count == 0) + { + return ColorBgra.Transparent; + } + + ulong aSum = 0; + + for (int i = 0; i < count; ++i) + { + aSum += (ulong)colors[i].A; + } + + byte b = 0; + byte g = 0; + byte r = 0; + byte a = (byte)(aSum / (ulong)count); + + if (aSum != 0) + { + ulong bSum = 0; + ulong gSum = 0; + ulong rSum = 0; + + for (int i = 0; i < count; ++i) + { + bSum += (ulong)(colors[i].A * colors[i].B); + gSum += (ulong)(colors[i].A * colors[i].G); + rSum += (ulong)(colors[i].A * colors[i].R); + } + + b = (byte)(bSum / aSum); + g = (byte)(gSum / aSum); + r = (byte)(rSum / aSum); + } + + return ColorBgra.FromBgra(b, g, r, a); + } + + public override string ToString() + { + return "B: " + B + ", G: " + G + ", R: " + R + ", A: " + A; + } + + /// <summary> + /// Casts a ColorBgra to a UInt32. + /// </summary> + public static explicit operator UInt32(ColorBgra color) + { + return color.Bgra; + } + + /// <summary> + /// Casts a UInt32 to a ColorBgra. + /// </summary> + public static explicit operator ColorBgra(UInt32 uint32) + { + return ColorBgra.FromUInt32(uint32); + } + + // Colors: copied from System.Drawing.Color's list (don't worry I didn't type it in + // manually, I used a code generator w/ reflection ...) + + public static ColorBgra Transparent + { + get + { + return ColorBgra.FromBgra(255, 255, 255, 0); + } + } + + public static ColorBgra AliceBlue + { + get + { + return ColorBgra.FromBgra(255, 248, 240, 255); + } + } + + public static ColorBgra AntiqueWhite + { + get + { + return ColorBgra.FromBgra(215, 235, 250, 255); + } + } + + public static ColorBgra Aqua + { + get + { + return ColorBgra.FromBgra(255, 255, 0, 255); + } + } + + public static ColorBgra Aquamarine + { + get + { + return ColorBgra.FromBgra(212, 255, 127, 255); + } + } + + public static ColorBgra Azure + { + get + { + return ColorBgra.FromBgra(255, 255, 240, 255); + } + } + + public static ColorBgra Beige + { + get + { + return ColorBgra.FromBgra(220, 245, 245, 255); + } + } + + public static ColorBgra Bisque + { + get + { + return ColorBgra.FromBgra(196, 228, 255, 255); + } + } + + public static ColorBgra Black + { + get + { + return ColorBgra.FromBgra(0, 0, 0, 255); + } + } + + public static ColorBgra BlanchedAlmond + { + get + { + return ColorBgra.FromBgra(205, 235, 255, 255); + } + } + + public static ColorBgra Blue + { + get + { + return ColorBgra.FromBgra(255, 0, 0, 255); + } + } + + public static ColorBgra BlueViolet + { + get + { + return ColorBgra.FromBgra(226, 43, 138, 255); + } + } + + public static ColorBgra Brown + { + get + { + return ColorBgra.FromBgra(42, 42, 165, 255); + } + } + + public static ColorBgra BurlyWood + { + get + { + return ColorBgra.FromBgra(135, 184, 222, 255); + } + } + + public static ColorBgra CadetBlue + { + get + { + return ColorBgra.FromBgra(160, 158, 95, 255); + } + } + + public static ColorBgra Chartreuse + { + get + { + return ColorBgra.FromBgra(0, 255, 127, 255); + } + } + + public static ColorBgra Chocolate + { + get + { + return ColorBgra.FromBgra(30, 105, 210, 255); + } + } + + public static ColorBgra Coral + { + get + { + return ColorBgra.FromBgra(80, 127, 255, 255); + } + } + + public static ColorBgra CornflowerBlue + { + get + { + return ColorBgra.FromBgra(237, 149, 100, 255); + } + } + + public static ColorBgra Cornsilk + { + get + { + return ColorBgra.FromBgra(220, 248, 255, 255); + } + } + + public static ColorBgra Crimson + { + get + { + return ColorBgra.FromBgra(60, 20, 220, 255); + } + } + + public static ColorBgra Cyan + { + get + { + return ColorBgra.FromBgra(255, 255, 0, 255); + } + } + + public static ColorBgra DarkBlue + { + get + { + return ColorBgra.FromBgra(139, 0, 0, 255); + } + } + + public static ColorBgra DarkCyan + { + get + { + return ColorBgra.FromBgra(139, 139, 0, 255); + } + } + + public static ColorBgra DarkGoldenrod + { + get + { + return ColorBgra.FromBgra(11, 134, 184, 255); + } + } + + public static ColorBgra DarkGray + { + get + { + return ColorBgra.FromBgra(169, 169, 169, 255); + } + } + + public static ColorBgra DarkGreen + { + get + { + return ColorBgra.FromBgra(0, 100, 0, 255); + } + } + + public static ColorBgra DarkKhaki + { + get + { + return ColorBgra.FromBgra(107, 183, 189, 255); + } + } + + public static ColorBgra DarkMagenta + { + get + { + return ColorBgra.FromBgra(139, 0, 139, 255); + } + } + + public static ColorBgra DarkOliveGreen + { + get + { + return ColorBgra.FromBgra(47, 107, 85, 255); + } + } + + public static ColorBgra DarkOrange + { + get + { + return ColorBgra.FromBgra(0, 140, 255, 255); + } + } + + public static ColorBgra DarkOrchid + { + get + { + return ColorBgra.FromBgra(204, 50, 153, 255); + } + } + + public static ColorBgra DarkRed + { + get + { + return ColorBgra.FromBgra(0, 0, 139, 255); + } + } + + public static ColorBgra DarkSalmon + { + get + { + return ColorBgra.FromBgra(122, 150, 233, 255); + } + } + + public static ColorBgra DarkSeaGreen + { + get + { + return ColorBgra.FromBgra(139, 188, 143, 255); + } + } + + public static ColorBgra DarkSlateBlue + { + get + { + return ColorBgra.FromBgra(139, 61, 72, 255); + } + } + + public static ColorBgra DarkSlateGray + { + get + { + return ColorBgra.FromBgra(79, 79, 47, 255); + } + } + + public static ColorBgra DarkTurquoise + { + get + { + return ColorBgra.FromBgra(209, 206, 0, 255); + } + } + + public static ColorBgra DarkViolet + { + get + { + return ColorBgra.FromBgra(211, 0, 148, 255); + } + } + + public static ColorBgra DeepPink + { + get + { + return ColorBgra.FromBgra(147, 20, 255, 255); + } + } + + public static ColorBgra DeepSkyBlue + { + get + { + return ColorBgra.FromBgra(255, 191, 0, 255); + } + } + + public static ColorBgra DimGray + { + get + { + return ColorBgra.FromBgra(105, 105, 105, 255); + } + } + + public static ColorBgra DodgerBlue + { + get + { + return ColorBgra.FromBgra(255, 144, 30, 255); + } + } + + public static ColorBgra Firebrick + { + get + { + return ColorBgra.FromBgra(34, 34, 178, 255); + } + } + + public static ColorBgra FloralWhite + { + get + { + return ColorBgra.FromBgra(240, 250, 255, 255); + } + } + + public static ColorBgra ForestGreen + { + get + { + return ColorBgra.FromBgra(34, 139, 34, 255); + } + } + + public static ColorBgra Fuchsia + { + get + { + return ColorBgra.FromBgra(255, 0, 255, 255); + } + } + + public static ColorBgra Gainsboro + { + get + { + return ColorBgra.FromBgra(220, 220, 220, 255); + } + } + + public static ColorBgra GhostWhite + { + get + { + return ColorBgra.FromBgra(255, 248, 248, 255); + } + } + + public static ColorBgra Gold + { + get + { + return ColorBgra.FromBgra(0, 215, 255, 255); + } + } + + public static ColorBgra Goldenrod + { + get + { + return ColorBgra.FromBgra(32, 165, 218, 255); + } + } + + public static ColorBgra Gray + { + get + { + return ColorBgra.FromBgra(128, 128, 128, 255); + } + } + + public static ColorBgra Green + { + get + { + return ColorBgra.FromBgra(0, 128, 0, 255); + } + } + + public static ColorBgra GreenYellow + { + get + { + return ColorBgra.FromBgra(47, 255, 173, 255); + } + } + + public static ColorBgra Honeydew + { + get + { + return ColorBgra.FromBgra(240, 255, 240, 255); + } + } + + public static ColorBgra HotPink + { + get + { + return ColorBgra.FromBgra(180, 105, 255, 255); + } + } + + public static ColorBgra IndianRed + { + get + { + return ColorBgra.FromBgra(92, 92, 205, 255); + } + } + + public static ColorBgra Indigo + { + get + { + return ColorBgra.FromBgra(130, 0, 75, 255); + } + } + + public static ColorBgra Ivory + { + get + { + return ColorBgra.FromBgra(240, 255, 255, 255); + } + } + + public static ColorBgra Khaki + { + get + { + return ColorBgra.FromBgra(140, 230, 240, 255); + } + } + + public static ColorBgra Lavender + { + get + { + return ColorBgra.FromBgra(250, 230, 230, 255); + } + } + + public static ColorBgra LavenderBlush + { + get + { + return ColorBgra.FromBgra(245, 240, 255, 255); + } + } + + public static ColorBgra LawnGreen + { + get + { + return ColorBgra.FromBgra(0, 252, 124, 255); + } + } + + public static ColorBgra LemonChiffon + { + get + { + return ColorBgra.FromBgra(205, 250, 255, 255); + } + } + + public static ColorBgra LightBlue + { + get + { + return ColorBgra.FromBgra(230, 216, 173, 255); + } + } + + public static ColorBgra LightCoral + { + get + { + return ColorBgra.FromBgra(128, 128, 240, 255); + } + } + + public static ColorBgra LightCyan + { + get + { + return ColorBgra.FromBgra(255, 255, 224, 255); + } + } + + public static ColorBgra LightGoldenrodYellow + { + get + { + return ColorBgra.FromBgra(210, 250, 250, 255); + } + } + + public static ColorBgra LightGreen + { + get + { + return ColorBgra.FromBgra(144, 238, 144, 255); + } + } + + public static ColorBgra LightGray + { + get + { + return ColorBgra.FromBgra(211, 211, 211, 255); + } + } + + public static ColorBgra LightPink + { + get + { + return ColorBgra.FromBgra(193, 182, 255, 255); + } + } + + public static ColorBgra LightSalmon + { + get + { + return ColorBgra.FromBgra(122, 160, 255, 255); + } + } + + public static ColorBgra LightSeaGreen + { + get + { + return ColorBgra.FromBgra(170, 178, 32, 255); + } + } + + public static ColorBgra LightSkyBlue + { + get + { + return ColorBgra.FromBgra(250, 206, 135, 255); + } + } + + public static ColorBgra LightSlateGray + { + get + { + return ColorBgra.FromBgra(153, 136, 119, 255); + } + } + + public static ColorBgra LightSteelBlue + { + get + { + return ColorBgra.FromBgra(222, 196, 176, 255); + } + } + + public static ColorBgra LightYellow + { + get + { + return ColorBgra.FromBgra(224, 255, 255, 255); + } + } + + public static ColorBgra Lime + { + get + { + return ColorBgra.FromBgra(0, 255, 0, 255); + } + } + + public static ColorBgra LimeGreen + { + get + { + return ColorBgra.FromBgra(50, 205, 50, 255); + } + } + + public static ColorBgra Linen + { + get + { + return ColorBgra.FromBgra(230, 240, 250, 255); + } + } + + public static ColorBgra Magenta + { + get + { + return ColorBgra.FromBgra(255, 0, 255, 255); + } + } + + public static ColorBgra Maroon + { + get + { + return ColorBgra.FromBgra(0, 0, 128, 255); + } + } + + public static ColorBgra MediumAquamarine + { + get + { + return ColorBgra.FromBgra(170, 205, 102, 255); + } + } + + public static ColorBgra MediumBlue + { + get + { + return ColorBgra.FromBgra(205, 0, 0, 255); + } + } + + public static ColorBgra MediumOrchid + { + get + { + return ColorBgra.FromBgra(211, 85, 186, 255); + } + } + + public static ColorBgra MediumPurple + { + get + { + return ColorBgra.FromBgra(219, 112, 147, 255); + } + } + + public static ColorBgra MediumSeaGreen + { + get + { + return ColorBgra.FromBgra(113, 179, 60, 255); + } + } + + public static ColorBgra MediumSlateBlue + { + get + { + return ColorBgra.FromBgra(238, 104, 123, 255); + } + } + + public static ColorBgra MediumSpringGreen + { + get + { + return ColorBgra.FromBgra(154, 250, 0, 255); + } + } + + public static ColorBgra MediumTurquoise + { + get + { + return ColorBgra.FromBgra(204, 209, 72, 255); + } + } + + public static ColorBgra MediumVioletRed + { + get + { + return ColorBgra.FromBgra(133, 21, 199, 255); + } + } + + public static ColorBgra MidnightBlue + { + get + { + return ColorBgra.FromBgra(112, 25, 25, 255); + } + } + + public static ColorBgra MintCream + { + get + { + return ColorBgra.FromBgra(250, 255, 245, 255); + } + } + + public static ColorBgra MistyRose + { + get + { + return ColorBgra.FromBgra(225, 228, 255, 255); + } + } + + public static ColorBgra Moccasin + { + get + { + return ColorBgra.FromBgra(181, 228, 255, 255); + } + } + + public static ColorBgra NavajoWhite + { + get + { + return ColorBgra.FromBgra(173, 222, 255, 255); + } + } + + public static ColorBgra Navy + { + get + { + return ColorBgra.FromBgra(128, 0, 0, 255); + } + } + + public static ColorBgra OldLace + { + get + { + return ColorBgra.FromBgra(230, 245, 253, 255); + } + } + + public static ColorBgra Olive + { + get + { + return ColorBgra.FromBgra(0, 128, 128, 255); + } + } + + public static ColorBgra OliveDrab + { + get + { + return ColorBgra.FromBgra(35, 142, 107, 255); + } + } + + public static ColorBgra Orange + { + get + { + return ColorBgra.FromBgra(0, 165, 255, 255); + } + } + + public static ColorBgra OrangeRed + { + get + { + return ColorBgra.FromBgra(0, 69, 255, 255); + } + } + + public static ColorBgra Orchid + { + get + { + return ColorBgra.FromBgra(214, 112, 218, 255); + } + } + + public static ColorBgra PaleGoldenrod + { + get + { + return ColorBgra.FromBgra(170, 232, 238, 255); + } + } + + public static ColorBgra PaleGreen + { + get + { + return ColorBgra.FromBgra(152, 251, 152, 255); + } + } + + public static ColorBgra PaleTurquoise + { + get + { + return ColorBgra.FromBgra(238, 238, 175, 255); + } + } + + public static ColorBgra PaleVioletRed + { + get + { + return ColorBgra.FromBgra(147, 112, 219, 255); + } + } + + public static ColorBgra PapayaWhip + { + get + { + return ColorBgra.FromBgra(213, 239, 255, 255); + } + } + + public static ColorBgra PeachPuff + { + get + { + return ColorBgra.FromBgra(185, 218, 255, 255); + } + } + + public static ColorBgra Peru + { + get + { + return ColorBgra.FromBgra(63, 133, 205, 255); + } + } + + public static ColorBgra Pink + { + get + { + return ColorBgra.FromBgra(203, 192, 255, 255); + } + } + + public static ColorBgra Plum + { + get + { + return ColorBgra.FromBgra(221, 160, 221, 255); + } + } + + public static ColorBgra PowderBlue + { + get + { + return ColorBgra.FromBgra(230, 224, 176, 255); + } + } + + public static ColorBgra Purple + { + get + { + return ColorBgra.FromBgra(128, 0, 128, 255); + } + } + + public static ColorBgra Red + { + get + { + return ColorBgra.FromBgra(0, 0, 255, 255); + } + } + + public static ColorBgra RosyBrown + { + get + { + return ColorBgra.FromBgra(143, 143, 188, 255); + } + } + + public static ColorBgra RoyalBlue + { + get + { + return ColorBgra.FromBgra(225, 105, 65, 255); + } + } + + public static ColorBgra SaddleBrown + { + get + { + return ColorBgra.FromBgra(19, 69, 139, 255); + } + } + + public static ColorBgra Salmon + { + get + { + return ColorBgra.FromBgra(114, 128, 250, 255); + } + } + + public static ColorBgra SandyBrown + { + get + { + return ColorBgra.FromBgra(96, 164, 244, 255); + } + } + + public static ColorBgra SeaGreen + { + get + { + return ColorBgra.FromBgra(87, 139, 46, 255); + } + } + + public static ColorBgra SeaShell + { + get + { + return ColorBgra.FromBgra(238, 245, 255, 255); + } + } + + public static ColorBgra Sienna + { + get + { + return ColorBgra.FromBgra(45, 82, 160, 255); + } + } + + public static ColorBgra Silver + { + get + { + return ColorBgra.FromBgra(192, 192, 192, 255); + } + } + + public static ColorBgra SkyBlue + { + get + { + return ColorBgra.FromBgra(235, 206, 135, 255); + } + } + + public static ColorBgra SlateBlue + { + get + { + return ColorBgra.FromBgra(205, 90, 106, 255); + } + } + + public static ColorBgra SlateGray + { + get + { + return ColorBgra.FromBgra(144, 128, 112, 255); + } + } + + public static ColorBgra Snow + { + get + { + return ColorBgra.FromBgra(250, 250, 255, 255); + } + } + + public static ColorBgra SpringGreen + { + get + { + return ColorBgra.FromBgra(127, 255, 0, 255); + } + } + + public static ColorBgra SteelBlue + { + get + { + return ColorBgra.FromBgra(180, 130, 70, 255); + } + } + + public static ColorBgra Tan + { + get + { + return ColorBgra.FromBgra(140, 180, 210, 255); + } + } + + public static ColorBgra Teal + { + get + { + return ColorBgra.FromBgra(128, 128, 0, 255); + } + } + + public static ColorBgra Thistle + { + get + { + return ColorBgra.FromBgra(216, 191, 216, 255); + } + } + + public static ColorBgra Tomato + { + get + { + return ColorBgra.FromBgra(71, 99, 255, 255); + } + } + + public static ColorBgra Turquoise + { + get + { + return ColorBgra.FromBgra(208, 224, 64, 255); + } + } + + public static ColorBgra Violet + { + get + { + return ColorBgra.FromBgra(238, 130, 238, 255); + } + } + + public static ColorBgra Wheat + { + get + { + return ColorBgra.FromBgra(179, 222, 245, 255); + } + } + + public static ColorBgra White + { + get + { + return ColorBgra.FromBgra(255, 255, 255, 255); + } + } + + public static ColorBgra WhiteSmoke + { + get + { + return ColorBgra.FromBgra(245, 245, 245, 255); + } + } + + public static ColorBgra Yellow + { + get + { + return ColorBgra.FromBgra(0, 255, 255, 255); + } + } + + public static ColorBgra YellowGreen + { + get + { + return ColorBgra.FromBgra(50, 205, 154, 255); + } + } + + public static ColorBgra Zero + { + get + { + return (ColorBgra)0; + } + } + + private static Dictionary<string, ColorBgra> predefinedColors; + + /// <summary> + /// Gets a hashtable that contains a list of all the predefined colors. + /// These are the same color values that are defined as public static properties + /// in System.Drawing.Color. The hashtable uses strings for the keys, and + /// ColorBgras for the values. + /// </summary> + public static Dictionary<string, ColorBgra> PredefinedColors + { + get + { + if (predefinedColors != null) + { + Type colorBgraType = typeof(ColorBgra); + PropertyInfo[] propInfos = colorBgraType.GetProperties(BindingFlags.Static | BindingFlags.Public); + Hashtable colors = new Hashtable(); + + foreach (PropertyInfo pi in propInfos) + { + if (pi.PropertyType == colorBgraType) + { + colors.Add(pi.Name, (ColorBgra)pi.GetValue(null, null)); + } + } + } + + return new Dictionary<string, ColorBgra>(predefinedColors); + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs new file mode 100644 index 000000000..a9629f2d8 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/OctreeQuantizer.cs @@ -0,0 +1,569 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp + +using Tango.RemoteDesktop.Quantization; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; + +namespace Tango.RemoteDesktop.Quantization +{ + internal unsafe class OctreeQuantizer + : Quantizer + { + private bool enableTransparency; + private Octree octree; + private int maxColors; + + /// <summary> + /// Construct the octree quantizer + /// </summary> + /// <remarks> + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree, + /// the second pass quantizes a color based on the nodes in the tree + /// </remarks> + /// <param name="maxColors">The maximum number of colors to return</param> + /// <param name="maxColorBits">The number of significant bits</param> + /// <param name="enableTransparency">If true, then one color slot in the palette will be reserved for transparency. + /// Any color passed through QuantizePixel which does not have an alpha of 255 will use this color slot. + /// If false, then all colors should have an alpha of 255. Otherwise the results may be unpredictable.</param> + public OctreeQuantizer(int maxColors, bool enableTransparency) + : base(false) + { + if (maxColors > 256) + { + throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors should be 256 or less"); + } + + if (maxColors < 2) + { + throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colors must be 2 or more"); + } + + this.octree = new Octree(8); // 8-bits per color + this.enableTransparency = enableTransparency; + this.maxColors = maxColors - (this.enableTransparency ? 1 : 0); // subtract 1 if enableTransparency is true + } + + /// <summary> + /// Process the pixel in the first pass of the algorithm + /// </summary> + /// <param name="pixel">The pixel to quantize</param> + /// <remarks> + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// </remarks> + protected override void InitialQuantizePixel(ColorBgra *pixel) + { + this.octree.AddColor(pixel); + } + + /// <summary> + /// Override this to process the pixel in the second pass of the algorithm + /// </summary> + /// <param name="pixel">The pixel to quantize</param> + /// <returns>The quantized value</returns> + protected override byte QuantizePixel(ColorBgra *pixel) + { + byte paletteIndex = 0; + + if (!this.enableTransparency || pixel->A == 255) + { + paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); + } + else + { + paletteIndex = (byte)this.maxColors; // maxColors will have a maximum value of 255 is enableTransparency is true + } + + return paletteIndex; + } + + /// <summary> + /// Retrieve the palette for the quantized image + /// </summary> + /// <param name="original">Any old palette, this is overwritten</param> + /// <returns>The new color palette</returns> + protected override ColorPalette GetPalette(ColorPalette original) + { + // First off convert the octree to _maxColors colors + List<Color> palette = this.octree.Palletize(maxColors); + + // Then convert the palette based on those colors + for (int index = 0; index < palette.Count; index++) + { + original.Entries[index] = palette[index]; + } + + // Fill the rest of the palette with transparent + for (int i = palette.Count; i < original.Entries.Length; ++i) + { + original.Entries[i] = Color.FromArgb(255, 0, 0, 0); + } + + // Add the transparent color + if (this.enableTransparency) + { + original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0); + } + + return original; + } + + /// <summary> + /// Class which does the actual quantization + /// </summary> + private class Octree + { + /// <summary> + /// Construct the octree + /// </summary> + /// <param name="maxColorBits">The maximum number of significant bits in the image</param> + public Octree(int maxColorBits) + { + _maxColorBits = maxColorBits; + _leafCount = 0; + _reducibleNodes = new OctreeNode[9]; + _root = new OctreeNode(0, _maxColorBits, this); + _previousColor = 0; + _previousNode = null; + } + + /// <summary> + /// Add a given color value to the octree + /// </summary> + /// <param name="pixel"></param> + public void AddColor(ColorBgra *pixel) + { + // Check if this request is for the same color as the last + if (_previousColor == pixel->Bgra) + { + // If so, check if I have a previous node setup. This will only ocurr if the first color in the image + // happens to be black, with an alpha component of zero. + if (null == _previousNode) + { + _previousColor = pixel->Bgra; + _root.AddColor(pixel, _maxColorBits, 0, this); + } + else + { + // Just update the previous node + _previousNode.Increment(pixel); + } + } + else + { + _previousColor = pixel->Bgra; + _root.AddColor(pixel, _maxColorBits, 0, this); + } + } + + /// <summary> + /// Reduce the depth of the tree + /// </summary> + public void Reduce() + { + int index; + + // Find the deepest level containing at least one reducible node + for (index = _maxColorBits - 1; + (index > 0) && (null == _reducibleNodes[index]); + index--) + { + // intentionally blank + } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = _reducibleNodes[index]; + _reducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + _leafCount -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + _previousNode = null; + } + + /// <summary> + /// Get/Set the number of leaves in the tree + /// </summary> + public int Leaves + { + get + { + return _leafCount; + } + + set + { + _leafCount = value; + } + } + + /// <summary> + /// Return the array of reducible nodes + /// </summary> + protected OctreeNode[] ReducibleNodes + { + get + { + return _reducibleNodes; + } + } + + /// <summary> + /// Keep track of the previous node that was quantized + /// </summary> + /// <param name="node">The node last quantized</param> + protected void TrackPrevious(OctreeNode node) + { + _previousNode = node; + } + + private Color[] _palette; + private PaletteTable paletteTable; + + /// <summary> + /// Convert the nodes in the octree to a palette with a maximum of colorCount colors + /// </summary> + /// <param name="colorCount">The maximum number of colors</param> + /// <returns>A list with the palettized colors</returns> + public List<Color> Palletize(int colorCount) + { + while (Leaves > colorCount) + { + Reduce(); + } + + // Now palettize the nodes + List<Color> palette = new List<Color>(Leaves); + int paletteIndex = 0; + + _root.ConstructPalette(palette, ref paletteIndex); + + // And return the palette + this._palette = palette.ToArray(); + this.paletteTable = null; + + return palette; + } + + /// <summary> + /// Get the palette index for the passed color + /// </summary> + /// <param name="pixel"></param> + /// <returns></returns> + public int GetPaletteIndex(ColorBgra *pixel) + { + int ret = -1; + + ret = _root.GetPaletteIndex(pixel, 0); + + if (ret < 0) + { + if (this.paletteTable == null) + { + this.paletteTable = new PaletteTable(this._palette); + } + + ret = this.paletteTable.FindClosestPaletteIndex(pixel->ToColor()); + } + + return ret; + } + + /// <summary> + /// Mask used when getting the appropriate pixels for a given node + /// </summary> + private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// <summary> + /// The root of the octree + /// </summary> + private OctreeNode _root; + + /// <summary> + /// Number of leaves in the tree + /// </summary> + private int _leafCount; + + /// <summary> + /// Array of reducible nodes + /// </summary> + private OctreeNode[] _reducibleNodes; + + /// <summary> + /// Maximum number of significant bits in the image + /// </summary> + private int _maxColorBits; + + /// <summary> + /// Store the last node quantized + /// </summary> + private OctreeNode _previousNode; + + /// <summary> + /// Cache the previous color quantized + /// </summary> + private uint _previousColor; + + /// <summary> + /// Class which encapsulates each node in the tree + /// </summary> + protected class OctreeNode + { + /// <summary> + /// Construct the node + /// </summary> + /// <param name="level">The level in the tree = 0 - 7</param> + /// <param name="colorBits">The number of significant color bits in the image</param> + /// <param name="octree">The tree to which this node belongs</param> + public OctreeNode(int level, int colorBits, Octree octree) + { + // Construct the new node + _leaf = (level == colorBits); + + _red = 0; + _green = 0; + _blue = 0; + _pixelCount = 0; + + // If a leaf, increment the leaf count + if (_leaf) + { + octree.Leaves++; + _nextReducible = null; + _children = null; + } + else + { + // Otherwise add this to the reducible nodes + _nextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + _children = new OctreeNode[8]; + } + } + + /// <summary> + /// Add a color into the tree + /// </summary> + /// <param name="pixel">The color</param> + /// <param name="colorBits">The number of significant color bits</param> + /// <param name="level">The level in the tree</param> + /// <param name="octree">The tree to which this node belongs</param> + public void AddColor(ColorBgra *pixel, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (_leaf) + { + Increment(pixel); + + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int shift = 7 - level; + int index = ((pixel->R & mask[level]) >> (shift - 2)) | + ((pixel->G & mask[level]) >> (shift - 1)) | + ((pixel->B & mask[level]) >> (shift)); + + OctreeNode child = _children[index]; + + if (null == child) + { + // Create a new child node & store in the array + child = new OctreeNode(level + 1, colorBits, octree); + _children[index] = child; + } + + // Add the color to the child node + child.AddColor(pixel, colorBits, level + 1, octree); + } + + } + + /// <summary> + /// Get/Set the next reducible node + /// </summary> + public OctreeNode NextReducible + { + get + { + return _nextReducible; + } + + set + { + _nextReducible = value; + } + } + + /// <summary> + /// Return the child nodes + /// </summary> + public OctreeNode[] Children + { + get + { + return _children; + } + } + + /// <summary> + /// Reduce this node by removing all of its children + /// </summary> + /// <returns>The number of leaves removed</returns> + public int Reduce() + { + int children = 0; + _red = 0; + _green = 0; + _blue = 0; + + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + if (null != _children[index]) + { + _red += _children[index]._red; + _green += _children[index]._green; + _blue += _children[index]._blue; + _pixelCount += _children[index]._pixelCount; + ++children; + _children[index] = null; + } + } + + // Now change this to a leaf node + _leaf = true; + + // Return the number of nodes to decrement the leaf count by + return(children - 1); + } + + /// <summary> + /// Traverse the tree, building up the color palette + /// </summary> + /// <param name="palette">The palette</param> + /// <param name="paletteIndex">The current palette index</param> + public void ConstructPalette(List<Color> palette, ref int paletteIndex) + { + if (_leaf) + { + // Consume the next palette index + _paletteIndex = paletteIndex++; + + // And set the color of the palette entry + int r = _red / _pixelCount; + int g = _green / _pixelCount; + int b = _blue / _pixelCount; + + palette.Add(Color.FromArgb(r, g, b)); + } + else + { + // Loop through children looking for leaves + for (int index = 0; index < 8; index++) + { + if (null != _children[index]) + { + _children[index].ConstructPalette(palette, ref paletteIndex); + } + } + } + } + + /// <summary> + /// Return the palette index for the passed color + /// </summary> + public int GetPaletteIndex(ColorBgra *pixel, int level) + { + int paletteIndex = _paletteIndex; + + if (!_leaf) + { + int shift = 7 - level; + int index = ((pixel->R & mask[level]) >> (shift - 2)) | + ((pixel->G & mask[level]) >> (shift - 1)) | + ((pixel->B & mask[level]) >> (shift)); + + if (null != _children[index]) + { + paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1); + } + else + { + paletteIndex = -1; + } + } + + return paletteIndex; + } + + /// <summary> + /// Increment the pixel count and add to the color information + /// </summary> + public void Increment(ColorBgra *pixel) + { + ++_pixelCount; + _red += pixel->R; + _green += pixel->G; + _blue += pixel->B; + } + + /// <summary> + /// Flag indicating that this is a leaf node + /// </summary> + private bool _leaf; + + /// <summary> + /// Number of pixels in this node + /// </summary> + private int _pixelCount; + + /// <summary> + /// Red component + /// </summary> + private int _red; + + /// <summary> + /// Green Component + /// </summary> + private int _green; + + /// <summary> + /// Blue component + /// </summary> + private int _blue; + + /// <summary> + /// Pointers to any child nodes + /// </summary> + private OctreeNode[] _children; + + /// <summary> + /// Pointer to next reducible node + /// </summary> + private OctreeNode _nextReducible; + + /// <summary> + /// The index of this node in the palette + /// </summary> + private int _paletteIndex; + } + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs new file mode 100644 index 000000000..5c24fe80f --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/PaletteTable.cs @@ -0,0 +1,72 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Collections; +using System.Drawing; + +namespace Tango.RemoteDesktop.Quantization +{ + internal sealed class PaletteTable + { + private Color[] palette; + + public Color this[int index] + { + get + { + return this.palette[index]; + } + + set + { + this.palette[index] = value; + } + } + + private int GetDistanceSquared(Color a, Color b) + { + int dsq = 0; // delta squared + int v; + + v = a.B - b.B; + dsq += v * v; + v = a.G - b.G; + dsq += v * v; + v = a.R - b.R; + dsq += v * v; + + return dsq; + } + + public int FindClosestPaletteIndex(Color pixel) + { + int dsqBest = int.MaxValue; + int ret = 0; + + for (int i = 0; i < this.palette.Length; ++i) + { + int dsq = GetDistanceSquared(this.palette[i], pixel); + + if (dsq < dsqBest) + { + dsqBest = dsq; + ret = i; + } + } + + return ret; + } + + public PaletteTable(Color[] palette) + { + this.palette = (Color[])palette.Clone(); + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs new file mode 100644 index 000000000..e63dc1ad8 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Quantizer.cs @@ -0,0 +1,335 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +// Based on: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/colorquant.asp + +using Tango.RemoteDesktop.Quantization; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.Runtime.InteropServices; + +namespace Tango.RemoteDesktop.Quantization +{ + internal unsafe abstract class Quantizer + { + /// <summary> + /// Flag used to indicate whether a single pass or two passes are needed for quantization. + /// </summary> + private bool singlePass; + + protected int ditherLevel; + public int DitherLevel + { + get + { + return this.ditherLevel; + } + + set + { + this.ditherLevel = value; + } + } + + /// <summary> + /// Construct the quantizer + /// </summary> + /// <param name="singlePass">If true, the quantization only needs to loop through the source pixels once</param> + /// <remarks> + /// If you construct this class with a true value for singlePass, then the code will, when quantizing your image, + /// only call the 'QuantizeImage' function. If two passes are required, the code will call 'InitialQuantizeImage' + /// and then 'QuantizeImage'. + /// </remarks> + public Quantizer(bool singlePass) + { + this.singlePass = singlePass; + } + + /// <summary> + /// Quantize an image and return the resulting output bitmap + /// </summary> + /// <param name="source">The image to quantize</param> + /// <returns>A quantized version of the image</returns> + public Bitmap Quantize(Image source) + { + // Get the size of the source image + int height = source.Height; + int width = source.Width; + + // And construct a rectangle from these dimensions + Rectangle bounds = new Rectangle(0, 0, width, height); + + // First off take a 32bpp version of the image + Bitmap img32bpp; + + if (source is Bitmap && source.PixelFormat == PixelFormat.Format32bppArgb) + { + img32bpp = (Bitmap)source; + } + else + { + img32bpp = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + // Now lock the bitmap into memory + using (Graphics g = Graphics.FromImage(img32bpp)) + { + g.PageUnit = GraphicsUnit.Pixel; + + // Draw the source image onto the copy bitmap, + // which will effect a widening as appropriate. + g.DrawImage(source, 0, 0, bounds.Width, bounds.Height); + } + } + + // And construct an 8bpp version + Bitmap output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + + // Define a pointer to the bitmap data + BitmapData sourceData = null; + + try + { + // Get the source image bits and lock into memory + sourceData = img32bpp.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + + // Call the FirstPass function if not a single pass algorithm. + // For something like an octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!singlePass) + { + FirstPass(sourceData, width, height); + } + + // Then set the color palette on the output bitmap. I'm passing in the current palette + // as there's no way to construct a new, empty palette. + output.Palette = this.GetPalette(output.Palette); + + // Then call the second pass which actually does the conversion + SecondPass(sourceData, output, width, height, bounds); + } + + finally + { + // Ensure that the bits are unlocked + img32bpp.UnlockBits(sourceData); + } + + if (img32bpp != source) + { + img32bpp.Dispose(); + img32bpp = null; + } + + // Last but not least, return the output bitmap + return output; + } + + /// <summary> + /// Execute the first pass through the pixels in the image + /// </summary> + /// <param name="sourceData">The source data</param> + /// <param name="width">The width in pixels of the image</param> + /// <param name="height">The height in pixels of the image</param> + protected virtual void FirstPass(BitmapData sourceData, int width, int height) + { + // Define the source data pointers. The source row is a byte to + // keep addition of the stride value easier (as this is in bytes) + byte* pSourceRow = (byte*)sourceData.Scan0.ToPointer(); + Int32* pSourcePixel; + + // Loop through each row + for (int row = 0; row < height; row++) + { + // Set the source pixel to the first pixel in this row + pSourcePixel = (Int32*)pSourceRow; + + // And loop through each column + for (int col = 0; col < width; col++, pSourcePixel++) + { + InitialQuantizePixel((ColorBgra *)pSourcePixel); + } + + // Add the stride to the source row + pSourceRow += sourceData.Stride; + } + } + + /// <summary> + /// Execute a second pass through the bitmap + /// </summary> + /// <param name="sourceData">The source bitmap, locked into memory</param> + /// <param name="output">The output bitmap</param> + /// <param name="width">The width in pixels of the image</param> + /// <param name="height">The height in pixels of the image</param> + /// <param name="bounds">The bounding rectangle</param> + protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds) + { + BitmapData outputData = null; + Color[] pallete = output.Palette.Entries; + int weight = ditherLevel; + + try + { + // Lock the output bitmap into memory + outputData = output.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); + + // Define the source data pointers. The source row is a byte to + // keep addition of the stride value easier (as this is in bytes) + byte* pSourceRow = (byte *)sourceData.Scan0.ToPointer(); + Int32* pSourcePixel = (Int32 *)pSourceRow; + + // Now define the destination data pointers + byte* pDestinationRow = (byte *)outputData.Scan0.ToPointer(); + byte* pDestinationPixel = pDestinationRow; + + int[] errorThisRowR = new int[width + 1]; + int[] errorThisRowG = new int[width + 1]; + int[] errorThisRowB = new int[width + 1]; + + for (int row = 0; row < height; row++) + { + int[] errorNextRowR = new int[width + 1]; + int[] errorNextRowG = new int[width + 1]; + int[] errorNextRowB = new int[width + 1]; + + int ptrInc; + + if ((row & 1) == 0) + { + pSourcePixel = (Int32*)pSourceRow; + pDestinationPixel = pDestinationRow; + ptrInc = +1; + } + else + { + pSourcePixel = (Int32*)pSourceRow + width - 1; + pDestinationPixel = pDestinationRow + width - 1; + ptrInc = -1; + } + + // Loop through each pixel on this scan line + for (int col = 0; col < width; ++col) + { + // Quantize the pixel + ColorBgra srcPixel = *(ColorBgra *)pSourcePixel; + ColorBgra target = new ColorBgra(); + + target.B = Utility.ClampToByte(srcPixel.B - ((errorThisRowB[col] * weight) / 8)); + target.G = Utility.ClampToByte(srcPixel.G - ((errorThisRowG[col] * weight) / 8)); + target.R = Utility.ClampToByte(srcPixel.R - ((errorThisRowR[col] * weight) / 8)); + target.A = srcPixel.A; + + byte pixelValue = QuantizePixel(&target); + *pDestinationPixel = pixelValue; + + ColorBgra actual = ColorBgra.FromColor(pallete[pixelValue]); + + int errorR = actual.R - target.R; + int errorG = actual.G - target.G; + int errorB = actual.B - target.B; + + // Floyd-Steinberg Error Diffusion: + // a) 7/16 error goes to x+1 + // b) 5/16 error goes to y+1 + // c) 3/16 error goes to x-1,y+1 + // d) 1/16 error goes to x+1,y+1 + + const int a = 7; + const int b = 5; + const int c = 3; + + int errorRa = (errorR * a) / 16; + int errorRb = (errorR * b) / 16; + int errorRc = (errorR * c) / 16; + int errorRd = errorR - errorRa - errorRb - errorRc; + + int errorGa = (errorG * a) / 16; + int errorGb = (errorG * b) / 16; + int errorGc = (errorG * c) / 16; + int errorGd = errorG - errorGa - errorGb - errorGc; + + int errorBa = (errorB * a) / 16; + int errorBb = (errorB * b) / 16; + int errorBc = (errorB * c) / 16; + int errorBd = errorB - errorBa - errorBb - errorBc; + + errorThisRowR[col + 1] += errorRa; + errorThisRowG[col + 1] += errorGa; + errorThisRowB[col + 1] += errorBa; + + errorNextRowR[width - col] += errorRb; + errorNextRowG[width - col] += errorGb; + errorNextRowB[width - col] += errorBb; + + if (col != 0) + { + errorNextRowR[width - (col - 1)] += errorRc; + errorNextRowG[width - (col - 1)] += errorGc; + errorNextRowB[width - (col - 1)] += errorBc; + } + + errorNextRowR[width - (col + 1)] += errorRd; + errorNextRowG[width - (col + 1)] += errorGd; + errorNextRowB[width - (col + 1)] += errorBd; + + // unchecked is necessary because otherwise it throws a fit if ptrInc is negative. + unchecked + { + pSourcePixel += ptrInc; + pDestinationPixel += ptrInc; + } + } + + // Add the stride to the source row + pSourceRow += sourceData.Stride; + + // And to the destination row + pDestinationRow += outputData.Stride; + + errorThisRowB = errorNextRowB; + errorThisRowG = errorNextRowG; + errorThisRowR = errorNextRowR; + } + } + + finally + { + // Ensure that I unlock the output bits + output.UnlockBits(outputData); + } + } + + /// <summary> + /// Override this to process the pixel in the first pass of the algorithm + /// </summary> + /// <param name="pixel">The pixel to quantize</param> + /// <remarks> + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// </remarks> + protected virtual void InitialQuantizePixel(ColorBgra *pixel) + { + } + + /// <summary> + /// Override this to process the pixel in the second pass of the algorithm + /// </summary> + /// <param name="pixel">The pixel to quantize</param> + /// <returns>The quantized value</returns> + protected abstract byte QuantizePixel(ColorBgra *pixel); + + /// <summary> + /// Retrieve the palette for the quantized image + /// </summary> + /// <param name="original">Any old palette, this is overrwritten</param> + /// <returns>The new color palette</returns> + protected abstract ColorPalette GetPalette(ColorPalette original); + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs new file mode 100644 index 000000000..4ce8ac958 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Scanline.cs @@ -0,0 +1,88 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Drawing; + +namespace Tango.RemoteDesktop.Quantization +{ + internal struct Scanline + { + private int x; + private int y; + private int length; + + public int X + { + get + { + return x; + } + } + + public int Y + { + get + { + return y; + } + } + + public int Length + { + get + { + return length; + } + } + + public override int GetHashCode() + { + unchecked + { + return length.GetHashCode() + x.GetHashCode() + y.GetHashCode(); + } + } + + public override bool Equals(object obj) + { + if (obj is Scanline) + { + Scanline rhs = (Scanline)obj; + return x == rhs.x && y == rhs.y && length == rhs.length; + } + else + { + return false; + } + } + + public static bool operator== (Scanline lhs, Scanline rhs) + { + return lhs.x == rhs.x && lhs.y == rhs.y && lhs.length == rhs.length; + } + + public static bool operator!= (Scanline lhs, Scanline rhs) + { + return !(lhs == rhs); + } + + public override string ToString() + { + return "(" + x + "," + y + "):[" + length.ToString() + "]"; + } + + public Scanline(int x, int y, int length) + { + this.x = x; + this.y = y; + this.length = length; + } + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs new file mode 100644 index 000000000..74119e9c5 --- /dev/null +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Quantization/Utility.cs @@ -0,0 +1,2531 @@ +///////////////////////////////////////////////////////////////////////////////// +// Paint.NET // +// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors. // +// Portions Copyright (C) Microsoft Corporation. All Rights Reserved. // +// See src/Resources/Files/License.txt for full licensing and attribution // +// details. // +// . // +///////////////////////////////////////////////////////////////////////////////// + +using Microsoft.Win32; +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Drawing2D; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Net; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; +using System.Threading; + +namespace Tango.RemoteDesktop.Quantization +{ + /// <summary> + /// Defines miscellaneous constants and static functions. + /// </summary> + /// // TODO: refactor into mini static classes + internal sealed class Utility + { + private Utility() + { + } + + internal static bool IsNumber(float x) + { + return x >= float.MinValue && x <= float.MaxValue; + } + + internal static bool IsNumber(double x) + { + return x >= double.MinValue && x <= double.MaxValue; + } + + internal static int Min(int val0, params int[] vals) + { + int min = val0; + + for (int i = 0; i < vals.Length; ++i) + { + if (vals[i] < min) + { + min = vals[i]; + } + } + + return min; + } + + internal static int Max(int val0, params int[] vals) + { + int max = val0; + + for (int i = 0; i < vals.Length; ++i) + { + if (vals[i] > max) + { + max = vals[i]; + } + } + + return max; + } + + public static PointF[] GetRgssOffsets(int quality) + { + unsafe + { + int sampleCount = quality * quality; + PointF[] samplesArray = new PointF[sampleCount]; + + fixed (PointF* pSamplesArray = samplesArray) + { + GetRgssOffsets(pSamplesArray, sampleCount, quality); + } + + return samplesArray; + } + } + + public static unsafe void GetRgssOffsets(PointF* samplesArray, int sampleCount, int quality) + { + if (sampleCount < 1) + { + throw new ArgumentOutOfRangeException("sampleCount", "sampleCount must be [0, int.MaxValue]"); + } + + if (sampleCount != quality * quality) + { + throw new ArgumentOutOfRangeException("sampleCount != (quality * quality)"); + } + + if (sampleCount == 1) + { + samplesArray[0] = new PointF(0.0f, 0.0f); + } + else + { + for (int i = 0; i < sampleCount; ++i) + { + double y = (i + 1d) / (sampleCount + 1d); + double x = y * quality; + + x -= (int)x; + + samplesArray[i] = new PointF((float)(x - 0.5d), (float)(y - 0.5d)); + } + } + } + + public static bool IsObsolete(Type type, bool inherit) + { + object[] attrs = type.GetCustomAttributes(typeof(ObsoleteAttribute), inherit); + return (attrs.Length != 0); + } + + public static void DrawDropShadow1px(Graphics g, Rectangle rect) + { + Brush b0 = new SolidBrush(Color.FromArgb(15, Color.Black)); + Brush b1 = new SolidBrush(Color.FromArgb(47, Color.Black)); + Pen p2 = new Pen(Color.FromArgb(63, Color.Black)); + + g.FillRectangle(b0, rect.Left, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Left + 1, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Left, rect.Top + 1, 1, 1); + + g.FillRectangle(b0, rect.Right - 1, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Right - 2, rect.Top, 1, 1); + g.FillRectangle(b1, rect.Right - 1, rect.Top + 1, 1, 1); + + g.FillRectangle(b0, rect.Left, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Left + 1, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Left, rect.Bottom - 2, 1, 1); + + g.FillRectangle(b0, rect.Right - 1, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Right - 2, rect.Bottom - 1, 1, 1); + g.FillRectangle(b1, rect.Right - 1, rect.Bottom - 2, 1, 1); + + g.DrawLine(p2, rect.Left + 2, rect.Top, rect.Right - 3, rect.Top); + g.DrawLine(p2, rect.Left, rect.Top + 2, rect.Left, rect.Bottom - 3); + g.DrawLine(p2, rect.Left + 2, rect.Bottom - 1, rect.Right - 3, rect.Bottom - 1); + g.DrawLine(p2, rect.Right - 1, rect.Top + 2, rect.Right - 1, rect.Bottom - 3); + + b0.Dispose(); + b0 = null; + b1.Dispose(); + b1 = null; + p2.Dispose(); + p2 = null; + } + + public static void DrawColorRectangle(Graphics g, Rectangle rect, Color color, bool drawBorder) + { + int inflateAmt = drawBorder ? -2 : 0; + Rectangle colorRectangle = Rectangle.Inflate(rect, inflateAmt, inflateAmt); + Brush colorBrush = new LinearGradientBrush(colorRectangle, Color.FromArgb(255, color), color, 90.0f, false); + HatchBrush backgroundBrush = new HatchBrush(HatchStyle.LargeCheckerBoard, Color.FromArgb(191, 191, 191), Color.FromArgb(255, 255, 255)); + + if (drawBorder) + { + g.DrawRectangle(Pens.Black, rect.Left, rect.Top, rect.Width - 1, rect.Height - 1); + g.DrawRectangle(Pens.White, rect.Left + 1, rect.Top + 1, rect.Width - 3, rect.Height - 3); + } + + PixelOffsetMode oldPOM = g.PixelOffsetMode; + g.PixelOffsetMode = PixelOffsetMode.Half; + g.FillRectangle(backgroundBrush, colorRectangle); + g.FillRectangle(colorBrush, colorRectangle); + g.PixelOffsetMode = oldPOM; + + backgroundBrush.Dispose(); + colorBrush.Dispose(); + } + + public static Size ComputeThumbnailSize(Size originalSize, int maxEdgeLength) + { + Size thumbSize; + + if (originalSize.Width > originalSize.Height) + { + int longSide = Math.Min(originalSize.Width, maxEdgeLength); + thumbSize = new Size(longSide, Math.Max(1, (originalSize.Height * longSide) / originalSize.Width)); + } + else if (originalSize.Height > originalSize.Width) + { + int longSide = Math.Min(originalSize.Height, maxEdgeLength); + thumbSize = new Size(Math.Max(1, (originalSize.Width * longSide) / originalSize.Height), longSide); + } + else // if (docSize.Width == docSize.Height) + { + int longSide = Math.Min(originalSize.Width, maxEdgeLength); + thumbSize = new Size(longSide, longSide); + } + + return thumbSize; + } + + public static Font CreateFont(string name, float size, FontStyle style) + { + Font returnFont; + + try + { + returnFont = new Font(name, size, style); + } + + catch (Exception) + { + returnFont = new Font(FontFamily.GenericSansSerif, size); + } + + return returnFont; + } + + public static Font CreateFont(string name, float size, string backupName, float backupSize, FontStyle style) + { + Font returnFont; + + try + { + returnFont = new Font(name, size, style); + } + + catch (Exception) + { + returnFont = CreateFont(backupName, backupSize, style); + } + + return returnFont; + } + + public static readonly Color TransparentKey = Color.FromArgb(192, 192, 192); + + private static bool allowGCFullCollect = true; + public static bool AllowGCFullCollect + { + get + { + return allowGCFullCollect; + } + + set + { + allowGCFullCollect = value; + } + } + + public static void GCFullCollect() + { + if (AllowGCFullCollect) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + } + + private static int defaultSimplificationFactor = 50; + public static int DefaultSimplificationFactor + { + get + { + return defaultSimplificationFactor; + } + + set + { + defaultSimplificationFactor = value; + } + } + + public static void SplitRectangle(Rectangle rect, Rectangle[] rects) + { + int height = rect.Height; + + for (int i = 0; i < rects.Length; ++i) + { + Rectangle newRect = Rectangle.FromLTRB(rect.Left, + rect.Top + ((height * i) / rects.Length), + rect.Right, + rect.Top + ((height * (i + 1)) / rects.Length)); + + rects[i] = newRect; + } + } + + public static long TicksToMs(long ticks) + { + return ticks / 10000; + } + + public static string GetStaticName(Type type) + { + PropertyInfo pi = type.GetProperty("StaticName", BindingFlags.Static | BindingFlags.Public | BindingFlags.GetProperty); + return (string)pi.GetValue(null, null); + } + + public static readonly float[][] Identity5x5F = new float[][] { + new float[] { 1, 0, 0, 0, 0 }, + new float[] { 0, 1, 0, 0, 0 }, + new float[] { 0, 0, 1, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }; + + public static readonly ColorMatrix IdentityColorMatrix = new ColorMatrix(Identity5x5F); + + [ThreadStatic] + private static Matrix identityMatrix = null; + public static Matrix IdentityMatrix + { + get + { + if (identityMatrix == null) + { + identityMatrix = new Matrix(); + identityMatrix.Reset(); + } + + return identityMatrix; + } + } + + /// <summary> + /// Rounds an integer to the smallest power of 2 that is greater + /// than or equal to it. + /// </summary> + public static int Log2RoundUp(int x) + { + if (x == 0) + { + return 1; + } + + if (x == 1) + { + return 1; + } + + return 1 << (1 + HighestBit(x - 1)); + } + + private static int HighestBit(int x) + { + if (x == 0) + { + return 0; + } + + int b = 0; + int hi = 0; + + while (b <= 30) + { + if ((x & (1 << b)) != 0) + { + hi = b; + } + + ++b; + } + + return hi; + } + + private int CountBits(int x) + { + uint y = (uint)x; + int count = 0; + + for (int bit = 0; bit < 32; ++bit) + { + if ((y & ((uint)1 << bit)) != 0) + { + ++count; + } + } + + return count; + } + + public static string RemoveSpaces(string s) + { + StringBuilder sb = new StringBuilder(); + + foreach (char c in s) + { + if (!char.IsWhiteSpace(c)) + { + sb.Append(c); + } + } + + return sb.ToString(); + } + + public static int Max(int[,] array) + { + int max = int.MinValue; + + for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); ++i) + { + for (int j = array.GetLowerBound(1); j <= array.GetUpperBound(1); ++j) + { + if (array[i,j] > max) + { + max = array[i,j]; + } + } + } + + return max; + } + + public static int Sum(int[][] array) + { + int sum = 0; + + for (int i = 0; i < array.Length; ++i) + { + int[] row = array[i]; + + for (int j = 0; j < row.Length; ++j) + { + sum += row[j]; + } + } + + return sum; + } + + public static Icon ImageToIcon(Image image) + { + return ImageToIcon(image, Utility.TransparentKey); + } + + public static Icon ImageToIcon(Image image, bool disposeImage) + { + return ImageToIcon(image, Utility.TransparentKey, disposeImage); + } + + public static Icon ImageToIcon(Image image, Color seeThru) + { + return ImageToIcon(image, seeThru, false); + } + + /// <summary> + /// Converts an Image to an Icon. + /// </summary> + /// <param name="image">The Image to convert to an icon. Must be an appropriate icon size (32x32, 16x16, etc).</param> + /// <param name="seeThru">The color that will be treated as transparent in the icon.</param> + /// <param name="disposeImage">Whether or not to dispose the passed-in Image.</param> + /// <returns>An Icon representation of the Image.</returns> + public static Icon ImageToIcon(Image image, Color seeThru, bool disposeImage) + { + Bitmap bitmap = new Bitmap(image); + + for (int y = 0; y < bitmap.Height; ++y) + { + for (int x = 0; x < bitmap.Width; ++x) + { + if (bitmap.GetPixel(x, y) == seeThru) + { + bitmap.SetPixel(x, y, Color.FromArgb(0)); + } + } + } + + Icon icon = Icon.FromHandle(bitmap.GetHicon()); + bitmap.Dispose(); + + if (disposeImage) + { + image.Dispose(); + } + + return icon; + } + + public static Icon BitmapToIcon(Bitmap bitmap, bool disposeBitmap) + { + Icon icon = Icon.FromHandle(bitmap.GetHicon()); + + if (disposeBitmap) + { + bitmap.Dispose(); + } + + return icon; + } + + public static Point GetRectangleCenter(Rectangle rect) + { + return new Point((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2); + } + + public static PointF GetRectangleCenter(RectangleF rect) + { + return new PointF((rect.Left + rect.Right) / 2, (rect.Top + rect.Bottom) / 2); + } + + public static Scanline[] GetRectangleScans(Rectangle rect) + { + Scanline[] scans = new Scanline[rect.Height]; + + for (int y = 0; y < rect.Height; ++y) + { + scans[y] = new Scanline(rect.X, rect.Y + y, rect.Width); + } + + return scans; + } + + public static Scanline[] GetRegionScans(Rectangle[] region) + { + int scanCount = 0; + + for (int i = 0; i < region.Length; ++i) + { + scanCount += region[i].Height; + } + + Scanline[] scans = new Scanline[scanCount]; + int scanIndex = 0; + + foreach (Rectangle rect in region) + { + for (int y = 0; y < rect.Height; ++y) + { + scans[scanIndex] = new Scanline(rect.X, rect.Y + y, rect.Width); + ++scanIndex; + } + } + + return scans; + } + + public static Rectangle[] ScanlinesToRectangles(Scanline[] scans) + { + return ScanlinesToRectangles(scans, 0, scans.Length); + } + + public static Rectangle[] ScanlinesToRectangles(Scanline[] scans, int startIndex, int length) + { + Rectangle[] rects = new Rectangle[length]; + + for (int i = 0; i < length; ++i) + { + Scanline scan = scans[i + startIndex]; + rects[i] = new Rectangle(scan.X, scan.Y, scan.Length, 1); + } + + return rects; + } + + public static RectangleF RectangleFromCenter(PointF center, float halfSize) + { + RectangleF ret = new RectangleF(center.X, center.Y, 0, 0); + ret.Inflate(halfSize, halfSize); + return ret; + } + + public static List<PointF> PointListToPointFList(List<Point> ptList) + { + List<PointF> ret = new List<PointF>(ptList.Count); + + for (int i = 0; i < ptList.Count; ++i) + { + ret.Add((PointF)ptList[i]); + } + + return ret; + } + + public static PointF[] PointArrayToPointFArray(Point[] ptArray) + { + PointF[] ret = new PointF[ptArray.Length]; + + for (int i = 0; i < ret.Length; ++i) + { + ret[i] = (PointF)ptArray[i]; + } + + return ret; + } + + public static Rectangle[] InflateRectangles(Rectangle[] rects, int amount) + { + Rectangle[] inflated = new Rectangle[rects.Length]; + + for (int i = 0; i < rects.Length; ++i) + { + inflated[i] = Rectangle.Inflate(rects[i], amount, amount); + } + + return inflated; + } + + public static void InflateRectanglesInPlace(Rectangle[] rects, int amount) + { + for (int i = 0; i < rects.Length; ++i) + { + rects[i].Inflate(amount, amount); + } + } + + public static RectangleF[] InflateRectangles(RectangleF[] rectsF, int amount) + { + RectangleF[] inflated = new RectangleF[rectsF.Length]; + + for (int i = 0; i < rectsF.Length; ++i) + { + inflated[i] = RectangleF.Inflate(rectsF[i], amount, amount); + } + + return inflated; + } + + public static void InflateRectanglesInPlace(RectangleF[] rectsF, float amount) + { + for (int i = 0; i < rectsF.Length; ++i) + { + rectsF[i].Inflate(amount, amount); + } + } + + public static Rectangle PointsToConstrainedRectangle(Point a, Point b) + { + Rectangle rect = Utility.PointsToRectangle(a, b); + int minWH = Math.Min(rect.Width, rect.Height); + + rect.Width = minWH; + rect.Height = minWH; + + if (rect.Y != a.Y) + { + rect.Location = new Point(rect.X, a.Y - minWH); + } + + if (rect.X != a.X) + { + rect.Location = new Point(a.X - minWH, rect.Y); + } + + return rect; + } + + public static RectangleF PointsToConstrainedRectangle(PointF a, PointF b) + { + RectangleF rect = Utility.PointsToRectangle(a, b); + float minWH = Math.Min(rect.Width, rect.Height); + + rect.Width = minWH; + rect.Height = minWH; + + if (rect.Y != a.Y) + { + rect.Location = new PointF(rect.X, a.Y - minWH); + } + + if (rect.X != a.X) + { + rect.Location = new PointF(a.X - minWH, rect.Y); + } + + return rect; + } + + /// <summary> + /// Takes two points and creates a bounding rectangle from them. + /// </summary> + /// <param name="a">One corner of the rectangle.</param> + /// <param name="b">The other corner of the rectangle.</param> + /// <returns>A Rectangle instance that bounds the two points.</returns> + public static Rectangle PointsToRectangle(Point a, Point b) + { + int x = Math.Min(a.X, b.X); + int y = Math.Min(a.Y, b.Y); + int width = Math.Abs(a.X - b.X) + 1; + int height = Math.Abs(a.Y - b.Y) + 1; + + return new Rectangle(x, y, width, height); + } + + public static RectangleF PointsToRectangle(PointF a, PointF b) + { + float x = Math.Min(a.X, b.X); + float y = Math.Min(a.Y, b.Y); + float width = Math.Abs(a.X - b.X) + 1; + float height = Math.Abs(a.Y - b.Y) + 1; + + return new RectangleF(x, y, width, height); + } + + public static Rectangle PointsToRectangleExclusive(Point a, Point b) + { + int x = Math.Min(a.X, b.X); + int y = Math.Min(a.Y, b.Y); + int width = Math.Abs(a.X - b.X); + int height = Math.Abs(a.Y - b.Y); + + return new Rectangle(x, y, width, height); + } + + public static RectangleF PointsToRectangleExclusive(PointF a, PointF b) + { + float x = Math.Min(a.X, b.X); + float y = Math.Min(a.Y, b.Y); + float width = Math.Abs(a.X - b.X); + float height = Math.Abs(a.Y - b.Y); + + return new RectangleF(x, y, width, height); + } + + public static RectangleF[] PointsToRectangles(PointF[] pointsF) + { + if (pointsF.Length == 0) + { + return new RectangleF[] { }; + } + + if (pointsF.Length == 1) + { + return new RectangleF[] { new RectangleF(pointsF[0].X, pointsF[0].Y, 1, 1) }; + } + + RectangleF[] rectsF = new RectangleF[pointsF.Length - 1]; + + for (int i = 0; i < pointsF.Length - 1; ++i) + { + rectsF[i] = PointsToRectangle(pointsF[i], pointsF[i + 1]); + } + + return rectsF; + } + + public static Rectangle[] PointsToRectangles(Point[] points) + { + if (points.Length == 0) + { + return new Rectangle[] { }; + } + + if (points.Length == 1) + { + return new Rectangle[] { new Rectangle(points[0].X, points[0].Y, 1, 1) }; + } + + Rectangle[] rects = new Rectangle[points.Length - 1]; + + for (int i = 0; i < points.Length - 1; ++i) + { + rects[i] = PointsToRectangle(points[i], points[i + 1]); + } + + return rects; + } + + /// <summary> + /// Converts a RectangleF to RectangleF by rounding down the Location and rounding + /// up the Size. + /// </summary> + public static Rectangle RoundRectangle(RectangleF rectF) + { + float left = (float)Math.Floor(rectF.Left); + float top = (float)Math.Floor(rectF.Top); + float right = (float)Math.Ceiling(rectF.Right); + float bottom = (float)Math.Ceiling(rectF.Bottom); + + return Rectangle.Truncate(RectangleF.FromLTRB(left, top, right, bottom)); + } + + public static Stack Reverse(Stack reverseMe) + { + Stack reversed = new Stack(); + + foreach (object o in reverseMe) + { + reversed.Push(o); + } + + return reversed; + } + + public static void SerializeObjectToStream(object graph, Stream stream) + { + new BinaryFormatter().Serialize(stream, graph); + } + + public static object DeserializeObjectFromStream(Stream stream) + { + return new BinaryFormatter().Deserialize(stream); + } + + [Obsolete("Use rect.Contains() instead", true)] + public static bool IsPointInRectangle(Point pt, Rectangle rect) + { + return rect.Contains(pt); + } + + [Obsolete("Use rect.Contains() instead", true)] + public static bool IsPointInRectangle(int x, int y, Rectangle rect) + { + return rect.Contains(x, y); + } + + public static Bitmap FullCloneBitmap(Bitmap cloneMe) + { + Bitmap bitmap = new Bitmap(cloneMe.Width, cloneMe.Height, cloneMe.PixelFormat); + + using (Graphics g = Graphics.FromImage(bitmap)) + { + g.DrawImage(cloneMe, 0, 0, cloneMe.Width, cloneMe.Height); + } + + return bitmap; + } + + /// <summary> + /// Allows you to find the bounding box for a "region" that is described as an + /// array of bounding boxes. + /// </summary> + /// <param name="rectsF">The "region" you want to find a bounding box for.</param> + /// <returns>A RectangleF structure that surrounds the Region.</returns> + public static RectangleF GetRegionBounds(RectangleF[] rectsF, int startIndex, int length) + { + if (rectsF.Length == 0) + { + return RectangleF.Empty; + } + + float left = rectsF[startIndex].Left; + float top = rectsF[startIndex].Top; + float right = rectsF[startIndex].Right; + float bottom = rectsF[startIndex].Bottom; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + RectangleF rectF = rectsF[i]; + + if (rectF.Left < left) + { + left = rectF.Left; + } + + if (rectF.Top < top) + { + top = rectF.Top; + } + + if (rectF.Right > right) + { + right = rectF.Right; + } + + if (rectF.Bottom > bottom) + { + bottom = rectF.Bottom; + } + } + + return RectangleF.FromLTRB(left, top, right, bottom); + } + + public static RectangleF GetTraceBounds(PointF[] pointsF, int startIndex, int length) + { + if (pointsF.Length == 0) + { + return RectangleF.Empty; + } + + float left = pointsF[startIndex].X; + float top = pointsF[startIndex].Y; + float right = 1 + pointsF[startIndex].X; + float bottom = 1 + pointsF[startIndex].Y; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + PointF pointF = pointsF[i]; + + if (pointF.X < left) + { + left = pointF.X; + } + + if (pointF.Y < top) + { + top = pointF.Y; + } + + if (pointF.X > right) + { + right = pointF.X; + } + + if (pointF.Y > bottom) + { + bottom = pointF.Y; + } + } + + return RectangleF.FromLTRB(left, top, right, bottom); + } + + public static Rectangle GetTraceBounds(Point[] points, int startIndex, int length) + { + if (points.Length == 0) + { + return Rectangle.Empty; + } + + int left = points[startIndex].X; + int top = points[startIndex].Y; + int right = 1 + points[startIndex].X; + int bottom = 1 + points[startIndex].Y; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + Point point = points[i]; + + if (point.X < left) + { + left = point.X; + } + + if (point.Y < top) + { + top = point.Y; + } + + if (point.X > right) + { + right = point.X; + } + + if (point.Y > bottom) + { + bottom = point.Y; + } + } + + return Rectangle.FromLTRB(left, top, right, bottom); + } + + /// <summary> + /// Allows you to find the bounding box for a "region" that is described as an + /// array of bounding boxes. + /// </summary> + /// <param name="rectsF">The "region" you want to find a bounding box for.</param> + /// <returns>A RectangleF structure that surrounds the Region.</returns> + public static Rectangle GetRegionBounds(Rectangle[] rects, int startIndex, int length) + { + if (rects.Length == 0) + { + return Rectangle.Empty; + } + + int left = rects[startIndex].Left; + int top = rects[startIndex].Top; + int right = rects[startIndex].Right; + int bottom = rects[startIndex].Bottom; + + for (int i = startIndex + 1; i < startIndex + length; ++i) + { + Rectangle rect = rects[i]; + + if (rect.Left < left) + { + left = rect.Left; + } + + if (rect.Top < top) + { + top = rect.Top; + } + + if (rect.Right > right) + { + right = rect.Right; + } + + if (rect.Bottom > bottom) + { + bottom = rect.Bottom; + } + } + + return Rectangle.FromLTRB(left, top, right, bottom); + } + + public static RectangleF GetRegionBounds(RectangleF[] rectsF) + { + return GetRegionBounds(rectsF, 0, rectsF.Length); + } + + public static Rectangle GetRegionBounds(Rectangle[] rects) + { + return GetRegionBounds(rects, 0, rects.Length); + } + + private static float DistanceSquared(RectangleF[] rectsF, int indexA, int indexB) + { + PointF centerA = new PointF(rectsF[indexA].Left + (rectsF[indexA].Width / 2), rectsF[indexA].Top + (rectsF[indexA].Height / 2)); + PointF centerB = new PointF(rectsF[indexB].Left + (rectsF[indexB].Width / 2), rectsF[indexB].Top + (rectsF[indexB].Height / 2)); + + return ((centerA.X - centerB.X) * (centerA.X - centerB.X)) + + ((centerA.Y - centerB.Y) * (centerA.Y - centerB.Y)); + } + + public static Rectangle[] SimplifyRegion(Rectangle[] rects, int complexity) + { + if (complexity == 0 || rects.Length < complexity) + { + return (Rectangle[])rects.Clone(); + } + + Rectangle[] boxes = new Rectangle[complexity]; + + for (int i = 0; i < complexity; ++i) + { + int startIndex = (i * rects.Length) / complexity; + int length = Math.Min(rects.Length, ((i + 1) * rects.Length) / complexity) - startIndex; + boxes[i] = GetRegionBounds(rects, startIndex, length); + } + + return boxes; + } + + + public static RectangleF[] SimplifyTrace(PointF[] pointsF, int complexity) + { + if (complexity == 0 || + (pointsF.Length - 1) < complexity) + { + return PointsToRectangles(pointsF); + } + + RectangleF[] boxes = new RectangleF[complexity]; + int parLength = pointsF.Length - 1; // "(points as Rectangles).Length" + + for (int i = 0; i < complexity; ++i) + { + int startIndex = (i * parLength) / complexity; + int length = Math.Min(parLength, ((i + 1) * parLength) / complexity) - startIndex; + boxes[i] = GetTraceBounds(pointsF, startIndex, length + 1); + } + + return boxes; + } + + public static RectangleF[] SimplifyTrace(PointF[] pointsF) + { + return SimplifyTrace(pointsF, defaultSimplificationFactor); + } + + public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects, int complexity, int inflationAmount) + { + Rectangle[] simplified = SimplifyRegion(rects, complexity); + + for (int i = 0; i < simplified.Length; ++i) + { + simplified[i].Inflate(inflationAmount, inflationAmount); + } + + return simplified; + } + + public static Rectangle[] SimplifyAndInflateRegion(Rectangle[] rects) + { + return SimplifyAndInflateRegion(rects, defaultSimplificationFactor, 1); + } + + public static RectangleF[] TranslateRectangles(RectangleF[] rectsF, PointF offset) + { + RectangleF[] retRectsF = new RectangleF[rectsF.Length]; + int i = 0; + + foreach (RectangleF rectF in rectsF) + { + retRectsF[i] = new RectangleF(rectF.X + offset.X, rectF.Y + offset.Y, rectF.Width, rectF.Height); + ++i; + } + + return retRectsF; + } + + public static Rectangle[] TranslateRectangles(Rectangle[] rects, int dx, int dy) + { + Rectangle[] retRects = new Rectangle[rects.Length]; + + for (int i = 0; i < rects.Length; ++i) + { + retRects[i] = new Rectangle(rects[i].X + dx, rects[i].Y + dy, rects[i].Width, rects[i].Height); + } + + return retRects; + } + + public static void TranslatePointsInPlace(PointF[] ptsF, float dx, float dy) + { + for (int i = 0; i < ptsF.Length; ++i) + { + ptsF[i].X += dx; + ptsF[i].Y += dy; + } + } + + public static void TranslatePointsInPlace(Point[] pts, int dx, int dy) + { + for (int i = 0; i < pts.Length; ++i) + { + pts[i].X += dx; + pts[i].Y += dy; + } + } + + public static Rectangle[] TruncateRectangles(RectangleF[] rectsF) + { + Rectangle[] rects = new Rectangle[rectsF.Length]; + + for (int i = 0; i < rectsF.Length; ++i) + { + rects[i] = Rectangle.Truncate(rectsF[i]); + } + + return rects; + } + + public static Point[] TruncatePoints(PointF[] pointsF) + { + Point[] points = new Point[pointsF.Length]; + + for (int i = 0; i < pointsF.Length; ++i) + { + points[i] = Point.Truncate(pointsF[i]); + } + + return points; + } + + public static Point[] RoundPoints(PointF[] pointsF) + { + Point[] points = new Point[pointsF.Length]; + + for (int i = 0; i < pointsF.Length; ++i) + { + points[i] = Point.Round(pointsF[i]); + } + + return points; + } + + /// <summary> + /// The Sutherland-Hodgman clipping alrogithm. + /// http://ezekiel.vancouver.wsu.edu/~cs442/lectures/clip/clip/index.html + /// + /// # Clipping a convex polygon to a convex region (e.g., rectangle) will always produce a convex polygon (or no polygon if completely outside the clipping region). + /// # Clipping a concave polygon to a rectangle may produce several polygons (see figure above) or, as the following algorithm does, produce a single, possibly degenerate, polygon. + /// # Divide and conquer: Clip entire polygon against a single edge (i.e., half-plane). Repeat for each edge in the clipping region. + /// + /// The input is a sequence of vertices: {v0, v1, ... vn} given as an array of Points + /// the result is a sequence of vertices, given as an array of Points. This result may have + /// less than, equal, more than, or 0 vertices. + /// </summary> + /// <param name="vertices"></param> + /// <returns></returns> + public static List<PointF> SutherlandHodgman(RectangleF bounds, List<PointF> v) + { + List<PointF> p1 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Left, v); + List<PointF> p2 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Right, p1); + List<PointF> p3 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Top, p2); + List<PointF> p4 = SutherlandHodgmanOneAxis(bounds, RectangleEdge.Bottom, p3); + + return p4; + } + + private enum RectangleEdge + { + Left, + Right, + Top, + Bottom + } + + private static List<PointF> SutherlandHodgmanOneAxis(RectangleF bounds, RectangleEdge edge, List<PointF> v) + { + if (v.Count == 0) + { + return new List<PointF>(); + } + + List<PointF> polygon = new List<PointF>(); + + PointF s = v[v.Count - 1]; + + for (int i = 0; i < v.Count; ++i) + { + PointF p = v[i]; + bool pIn = IsInside(bounds, edge, p); + bool sIn = IsInside(bounds, edge, s); + + if (sIn && pIn) + { + // case 1: inside -> inside + polygon.Add(p); + } + else if (sIn && !pIn) + { + // case 2: inside -> outside + polygon.Add(LineIntercept(bounds, edge, s, p)); + } + else if (!sIn && !pIn) + { + // case 3: outside -> outside + // emit nothing + } + else if (!sIn && pIn) + { + // case 4: outside -> inside + polygon.Add(LineIntercept(bounds, edge, s, p)); + polygon.Add(p); + } + + s = p; + } + + return polygon; + } + + private static bool IsInside(RectangleF bounds, RectangleEdge edge, PointF p) + { + switch (edge) + { + case RectangleEdge.Left: + return !(p.X < bounds.Left); + + case RectangleEdge.Right: + return !(p.X >= bounds.Right); + + case RectangleEdge.Top: + return !(p.Y < bounds.Top); + + case RectangleEdge.Bottom: + return !(p.Y >= bounds.Bottom); + + default: + throw new InvalidEnumArgumentException("edge"); + } + } + + private static Point LineIntercept(Rectangle bounds, RectangleEdge edge, Point a, Point b) + { + if (a == b) + { + return a; + } + + switch (edge) + { + case RectangleEdge.Bottom: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom); + + case RectangleEdge.Left: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X))); + + case RectangleEdge.Right: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X))); + + case RectangleEdge.Top: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new Point(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top); + } + + throw new ArgumentException("no intercept found"); + } + + private static PointF LineIntercept(RectangleF bounds, RectangleEdge edge, PointF a, PointF b) + { + if (a == b) + { + return a; + } + + switch (edge) + { + case RectangleEdge.Bottom: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(a.X + (((b.X - a.X) * (bounds.Bottom - a.Y)) / (b.Y - a.Y)), bounds.Bottom); + + case RectangleEdge.Left: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(bounds.Left, a.Y + (((b.Y - a.Y) * (bounds.Left - a.X)) / (b.X - a.X))); + + case RectangleEdge.Right: + if (b.X == a.X) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(bounds.Right, a.Y + (((b.Y - a.Y) * (bounds.Right - a.X)) / (b.X - a.X))); + + case RectangleEdge.Top: + if (b.Y == a.Y) + { + throw new ArgumentException("no intercept found"); + } + + return new PointF(a.X + (((b.X - a.X) * (bounds.Top - a.Y)) / (b.Y - a.Y)), bounds.Top); + } + + throw new ArgumentException("no intercept found"); + } + + public static Point[] GetLinePoints(Point first, Point second) + { + Point[] coords = null; + + int x1 = first.X; + int y1 = first.Y; + int x2 = second.X; + int y2 = second.Y; + int dx = x2 - x1; + int dy = y2 - y1; + int dxabs = Math.Abs(dx); + int dyabs = Math.Abs(dy); + int px = x1; + int py = y1; + int sdx = Math.Sign(dx); + int sdy = Math.Sign(dy); + int x = 0; + int y = 0; + + if (dxabs > dyabs) + { + coords = new Point[dxabs + 1]; + + for (int i = 0; i <= dxabs; i++) + { + y += dyabs; + + if (y >= dxabs) + { + y -= dxabs; + py += sdy; + } + + coords[i] = new Point(px, py); + px += sdx; + } + } + else + // had to add in this cludge for slopes of 1 ... wasn't drawing half the line + if (dxabs == dyabs) + { + coords = new Point[dxabs + 1]; + + for (int i = 0; i <= dxabs; i++) + { + coords[i] = new Point(px, py); + px += sdx; + py += sdy; + } + } + else + { + coords = new Point[dyabs + 1]; + + for (int i = 0; i <= dyabs; i++) + { + x += dxabs; + + if (x >= dyabs) + { + x -= dyabs; + px += sdx; + } + + coords[i] = new Point(px, py); + py += sdy; + } + } + + return coords; + } + + public static long GetTimeMs() + { + return Utility.TicksToMs(DateTime.Now.Ticks); + } + + /// <summary> + /// Returns the Distance between two points + /// </summary> + public static float Distance(PointF a, PointF b) + { + return Magnitude(new PointF(a.X - b.X, a.Y - b.Y)); + } + + /// <summary> + /// Returns the Magnitude (distance to origin) of a point + /// </summary> + // TODO: In v4.0 codebase, turn this into an extension method + public static float Magnitude(PointF p) + { + return (float)Math.Sqrt(p.X * p.X + p.Y * p.Y); + } + + // TODO: In v4.0 codebase, turn this into an extension method + public static double Clamp(double x, double min, double max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + // TODO: In v4.0 codebase, turn this into an extension method + public static float Clamp(float x, float min, float max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + // TODO: In v4.0 codebase, turn this into an extension method + public static int Clamp(int x, int min, int max) + { + if (x < min) + { + return min; + } + else if (x > max) + { + return max; + } + else + { + return x; + } + } + + public static byte ClampToByte(double x) + { + if (x > 255) + { + return 255; + } + else if (x < 0) + { + return 0; + } + else + { + return (byte)x; + } + } + + public static byte ClampToByte(float x) + { + if (x > 255) + { + return 255; + } + else if (x < 0) + { + return 0; + } + else + { + return (byte)x; + } + } + + public static byte ClampToByte(int x) + { + if (x > 255) + { + return 255; + } + else if (x < 0) + { + return 0; + } + else + { + return (byte)x; + } + } + + public static float Lerp(float from, float to, float frac) + { + return (from + frac * (to - from)); + } + + public static double Lerp(double from, double to, double frac) + { + return (from + frac * (to - from)); + } + + public static PointF Lerp(PointF from, PointF to, float frac) + { + return new PointF(Lerp(from.X, to.X, frac), Lerp(from.Y, to.Y, frac)); + } + + public static int ColorDifference(ColorBgra a, ColorBgra b) + { + return (int)Math.Ceiling(Math.Sqrt(ColorDifferenceSquared(a, b))); + } + + public static int ColorDifferenceSquared(ColorBgra a, ColorBgra b) + { + int diffSq = 0, tmp; + + tmp = a.R - b.R; + diffSq += tmp * tmp; + tmp = a.G - b.G; + diffSq += tmp * tmp; + tmp = a.B - b.B; + diffSq += tmp * tmp; + + return diffSq / 3; + } + + /// <summary> + /// Reads a 16-bit unsigned integer from a Stream in little-endian format. + /// </summary> + /// <param name="stream"></param> + /// <returns>-1 on failure, else the 16-bit unsigned integer that was read.</returns> + public static int ReadUInt16(Stream stream) + { + int byte1 = stream.ReadByte(); + + if (byte1 == -1) + { + return -1; + } + + int byte2 = stream.ReadByte(); + + if (byte2 == -1) + { + return -1; + } + + return byte1 + (byte2 << 8); + } + + public static void WriteUInt16(Stream stream, UInt16 word) + { + stream.WriteByte((byte)(word & 0xff)); + stream.WriteByte((byte)(word >> 8)); + } + + public static void WriteUInt24(Stream stream, int uint24) + { + stream.WriteByte((byte)(uint24 & 0xff)); + stream.WriteByte((byte)((uint24 >> 8) & 0xff)); + stream.WriteByte((byte)((uint24 >> 16) & 0xff)); + } + + public static void WriteUInt32(Stream stream, UInt32 uint32) + { + stream.WriteByte((byte)(uint32 & 0xff)); + stream.WriteByte((byte)((uint32 >> 8) & 0xff)); + stream.WriteByte((byte)((uint32 >> 16) & 0xff)); + stream.WriteByte((byte)((uint32 >> 24) & 0xff)); + } + + /// <summary> + /// Reads a 24-bit unsigned integer from a Stream in little-endian format. + /// </summary> + /// <param name="stream"></param> + /// <returns>-1 on failure, else the 24-bit unsigned integer that was read.</returns> + public static int ReadUInt24(Stream stream) + { + int byte1 = stream.ReadByte(); + + if (byte1 == -1) + { + return -1; + } + + int byte2 = stream.ReadByte(); + + if (byte2 == -1) + { + return -1; + } + + int byte3 = stream.ReadByte(); + + if (byte3 == -1) + { + return -1; + } + + return byte1 + (byte2 << 8) + (byte3 << 16); + } + + /// <summary> + /// Reads a 32-bit unsigned integer from a Stream in little-endian format. + /// </summary> + /// <param name="stream"></param> + /// <returns>-1 on failure, else the 32-bit unsigned integer that was read.</returns> + public static long ReadUInt32(Stream stream) + { + int byte1 = stream.ReadByte(); + + if (byte1 == -1) + { + return -1; + } + + int byte2 = stream.ReadByte(); + + if (byte2 == -1) + { + return -1; + } + + int byte3 = stream.ReadByte(); + + if (byte3 == -1) + { + return -1; + } + + int byte4 = stream.ReadByte(); + + if (byte4 == -1) + { + return -1; + } + + return unchecked((long)((uint)(byte1 + (byte2 << 8) + (byte3 << 16) + (byte4 << 24)))); + } + + public static int ReadFromStream(Stream input, byte[] buffer, int offset, int count) + { + int totalBytesRead = 0; + + while (totalBytesRead < count) + { + int bytesRead = input.Read(buffer, offset + totalBytesRead, count - totalBytesRead); + + if (bytesRead == 0) + { + throw new IOException("ran out of data"); + } + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } + + public static long CopyStream(Stream input, Stream output, long maxBytes) + { + long bytesCopied = 0; + byte[] buffer = new byte[4096]; + + while (true) + { + int bytesRead = input.Read(buffer, 0, buffer.Length); + + if (bytesRead == 0) + { + break; + } + else + { + int bytesToCopy; + + if (maxBytes != -1 && (bytesCopied + bytesRead) > maxBytes) + { + bytesToCopy = (int)(maxBytes - bytesCopied); + } + else + { + bytesToCopy = bytesRead; + } + + output.Write(buffer, 0, bytesRead); + bytesCopied += bytesToCopy; + + if (bytesToCopy != bytesRead) + { + break; + } + } + } + + return bytesCopied; + } + + public static long CopyStream(Stream input, Stream output) + { + return CopyStream(input, output, -1); + } + + private struct Edge + { + public int miny; // int + public int maxy; // int + public int x; // fixed point: 24.8 + public int dxdy; // fixed point: 24.8 + + public Edge(int miny, int maxy, int x, int dxdy) + { + this.miny = miny; + this.maxy = maxy; + this.x = x; + this.dxdy = dxdy; + } + } + + public static Scanline[] GetScans(Point[] vertices) + { + return GetScans(vertices, 0, vertices.Length); + } + + public static Scanline[] GetScans(Point[] vertices, int startIndex, int length) + { + if (length > vertices.Length - startIndex) + { + throw new ArgumentException("out of bounds: length > vertices.Length - startIndex"); + } + + int ymax = 0; + + // Build edge table + Edge[] edgeTable = new Edge[length]; + int edgeCount = 0; + + for (int i = startIndex; i < startIndex + length; ++i) + { + Point top = vertices[i]; + Point bottom = vertices[(((i + 1) - startIndex) % length) + startIndex]; + int dy; + + if (top.Y > bottom.Y) + { + Point temp = top; + top = bottom; + bottom = temp; + } + + dy = bottom.Y - top.Y; + + if (dy != 0) + { + edgeTable[edgeCount] = new Edge(top.Y, bottom.Y, top.X << 8, (((bottom.X - top.X) << 8) / dy)); + ymax = Math.Max(ymax, bottom.Y); + ++edgeCount; + } + } + + // Sort edge table by miny + for (int i = 0; i < edgeCount - 1; ++i) + { + int min = i; + + for (int j = i + 1; j < edgeCount; ++j) + { + if (edgeTable[j].miny < edgeTable[min].miny) + { + min = j; + } + } + + if (min != i) + { + Edge temp = edgeTable[min]; + edgeTable[min] = edgeTable[i]; + edgeTable[i] = temp; + } + } + + // Compute how many scanlines we will be emitting + int scanCount = 0; + int activeLow = 0; + int activeHigh = 0; + int yscan1 = edgeTable[0].miny; + + // we assume that edgeTable[0].miny == yscan + while (activeHigh < edgeCount - 1 && + edgeTable[activeHigh + 1].miny == yscan1) + { + ++activeHigh; + } + + while (yscan1 <= ymax) + { + // Find new edges where yscan == miny + while (activeHigh < edgeCount - 1 && + edgeTable[activeHigh + 1].miny == yscan1) + { + ++activeHigh; + } + + int count = 0; + for (int i = activeLow; i <= activeHigh; ++i) + { + if (edgeTable[i].maxy > yscan1) + { + ++count; + } + } + + scanCount += count / 2; + ++yscan1; + + // Remove edges where yscan == maxy + while (activeLow < edgeCount - 1 && + edgeTable[activeLow].maxy <= yscan1) + { + ++activeLow; + } + + if (activeLow > activeHigh) + { + activeHigh = activeLow; + } + } + + // Allocate scanlines that we'll return + Scanline[] scans = new Scanline[scanCount]; + + // Active Edge Table (AET): it is indices into the Edge Table (ET) + int[] active = new int[edgeCount]; + int activeCount = 0; + int yscan2 = edgeTable[0].miny; + int scansIndex = 0; + + // Repeat until both the ET and AET are empty + while (yscan2 <= ymax) + { + // Move any edges from the ET to the AET where yscan == miny + for (int i = 0; i < edgeCount; ++i) + { + if (edgeTable[i].miny == yscan2) + { + active[activeCount] = i; + ++activeCount; + } + } + + // Sort the AET on x + for (int i = 0; i < activeCount - 1; ++i) + { + int min = i; + + for (int j = i + 1; j < activeCount; ++j) + { + if (edgeTable[active[j]].x < edgeTable[active[min]].x) + { + min = j; + } + } + + if (min != i) + { + int temp = active[min]; + active[min] = active[i]; + active[i] = temp; + } + } + + // For each pair of entries in the AET, fill in pixels between their info + for (int i = 0; i < activeCount; i += 2) + { + Edge el = edgeTable[active[i]]; + Edge er = edgeTable[active[i + 1]]; + int startx = (el.x + 0xff) >> 8; // ceil(x) + int endx = er.x >> 8; // floor(x) + + scans[scansIndex] = new Scanline(startx, yscan2, endx - startx); + ++scansIndex; + } + + ++yscan2; + + // Remove from the AET any edge where yscan == maxy + int k = 0; + while (k < activeCount && activeCount > 0) + { + if (edgeTable[active[k]].maxy == yscan2) + { + // remove by shifting everything down one + for (int j = k + 1; j < activeCount; ++j) + { + active[j - 1] = active[j]; + } + + --activeCount; + } + else + { + ++k; + } + } + + // Update x for each entry in AET + for (int i = 0; i < activeCount; ++i) + { + edgeTable[active[i]].x += edgeTable[active[i]].dxdy; + } + } + + return scans; + } + + public static PointF TransformOnePoint(Matrix matrix, PointF ptF) + { + PointF[] ptFs = new PointF[1] { ptF }; + matrix.TransformPoints(ptFs); + return ptFs[0]; + } + + public static PointF TransformOneVector(Matrix matrix, PointF ptF) + { + PointF[] ptFs = new PointF[1] { ptF }; + matrix.TransformVectors(ptFs); + return ptFs[0]; + } + + public static PointF NormalizeVector(PointF vecF) + { + float magnitude = Magnitude(vecF); + vecF.X /= magnitude; + vecF.Y /= magnitude; + return vecF; + } + + public static PointF NormalizeVector2(PointF vecF) + { + float magnitude = Magnitude(vecF); + + if (magnitude == 0) + { + vecF.X = 0; + vecF.Y = 0; + } + else + { + vecF.X /= magnitude; + vecF.Y /= magnitude; + } + + return vecF; + } + + public static void NormalizeVectors(PointF[] vecsF) + { + for (int i = 0; i < vecsF.Length; ++i) + { + vecsF[i] = NormalizeVector(vecsF[i]); + } + } + + public static PointF RotateVector(PointF vecF, float angleDelta) + { + angleDelta *= (float)( Math.PI / 180.0); + float vecFLen = Magnitude(vecF); + float vecFAngle = angleDelta + (float)Math.Atan2(vecF.Y, vecF.X); + vecF.X = (float)Math.Cos(vecFAngle); + vecF.Y = (float)Math.Sin(vecFAngle); + return vecF; + } + + public static void RotateVectors(PointF[] vecFs, float angleDelta) + { + for (int i = 0; i < vecFs.Length; ++i) + { + vecFs[i] = RotateVector(vecFs[i], angleDelta); + } + } + + public static PointF MultiplyVector(PointF vecF, float scalar) + { + return new PointF(vecF.X * scalar, vecF.Y * scalar); + } + + public static PointF AddVectors(PointF a, PointF b) + { + return new PointF(a.X + b.X, a.Y + b.Y); + } + + public static PointF SubtractVectors(PointF lhs, PointF rhs) + { + return new PointF(lhs.X - rhs.X, lhs.Y - rhs.Y); + } + + public static PointF NegateVector(PointF v) + { + return new PointF(-v.X, -v.Y); + } + + public static float GetAngleOfTransform(Matrix matrix) + { + PointF[] pts = new PointF[] { new PointF(1.0f, 0.0f) }; + matrix.TransformVectors(pts); + double atan2 = Math.Atan2(pts[0].Y, pts[0].X); + double angle = atan2 * (180.0f / Math.PI); + + return (float)angle; + } + + public static bool IsTransformFlipped(Matrix matrix) + { + PointF ptX = new PointF(1.0f, 0.0f); + PointF ptXT = Utility.TransformOneVector(matrix, ptX); + double atan2X = Math.Atan2(ptXT.Y, ptXT.X); + double angleX = atan2X * (180.0 / Math.PI); + + PointF ptY = new PointF(0.0f, 1.0f); + PointF ptYT = Utility.TransformOneVector(matrix, ptY); + double atan2Y = Math.Atan2(ptYT.Y, ptYT.X); + double angleY = (atan2Y * (180.0 / Math.PI)) - 90.0; + + while (angleX < 0) + { + angleX += 360; + } + + while (angleY < 0) + { + angleY += 360; + } + + double angleDelta = Math.Abs(angleX - angleY); + + return angleDelta > 1.0 && angleDelta < 359.0; + } + + /// <summary> + /// Calculates the dot product of two vectors. + /// </summary> + public static float DotProduct(PointF lhs, PointF rhs) + { + return lhs.X * rhs.X + lhs.Y * rhs.Y; + } + + /// <summary> + /// Calculates the orthogonal projection of y on to u. + /// yhat = u * ((y dot u) / (u dot u)) + /// z = y - yhat + /// Section 6.2 (pg. 381) of Linear Algebra and its Applications, Second Edition, by David C. Lay + /// </summary> + /// <param name="y">The vector to decompose</param> + /// <param name="u">The non-zero vector to project y on to</param> + /// <param name="yhat">The orthogonal projection of y onto u</param> + /// <param name="yhatLen">The length of yhat such that yhat = yhatLen * u</param> + /// <param name="z">The component of y orthogonal to u</param> + /// <remarks> + /// As a special case, if u=(0,0) the results are all zero. + /// </remarks> + public static void GetProjection(PointF y, PointF u, out PointF yhat, out float yhatLen, out PointF z) + { + if (u.X == 0 && u.Y == 0) + { + yhat = new PointF(0, 0); + yhatLen = 0; + z = new PointF(0, 0); + } + else + { + float yDotU = DotProduct(y, u); + float uDotU = DotProduct(u, u); + yhatLen = yDotU / uDotU; + yhat = MultiplyVector(u, yhatLen); + z = SubtractVectors(y, yhat); + } + } + + public static int GreatestCommonDivisor(int a, int b) + { + int r; + + if (a < b) + { + r = a; + a = b; + b = r; + } + + do + { + r = a % b; + a = b; + b = r; + } while (r != 0); + + return a; + } + + public static void Swap(ref int a, ref int b) + { + int t; + + t = a; + a = b; + b = t; + } + + public static void Swap<T>(ref T a, ref T b) + { + T t; + + t = a; + a = b; + b = t; + } + + private static byte[] DownloadSmallFile(Uri uri, WebProxy proxy) + { + WebRequest request = WebRequest.Create(uri); + + if (proxy != null) + { + request.Proxy = proxy; + } + + request.Timeout = 5000; + WebResponse response = request.GetResponse(); + Stream stream = response.GetResponseStream(); + + try + { + byte[] buffer = new byte[8192]; + int offset = 0; + + while (offset < buffer.Length) + { + int bytesRead = stream.Read(buffer, offset, buffer.Length - offset); + + if (bytesRead == 0) + { + byte[] smallerBuffer = new byte[offset + bytesRead]; + + for (int i = 0; i < offset + bytesRead; ++i) + { + smallerBuffer[i] = buffer[i]; + } + + buffer = smallerBuffer; + } + + offset += bytesRead; + } + + return buffer; + } + + finally + { + if (stream != null) + { + stream.Close(); + stream = null; + } + + if (response != null) + { + response.Close(); + response = null; + } + } + } + + public static T[] RepeatArray<T>(T[] array, int repeatCount) + { + T[] returnArray = new T[repeatCount * array.Length]; + + for (int i = 0; i < repeatCount; ++i) + { + for (int j = 0; j < array.Length; ++j) + { + int index = (i * array.Length) + j; + returnArray[index] = array[j]; + } + } + + return returnArray; + } + + public static byte FastScaleByteByByte(byte a, byte b) + { + int r1 = a * b + 0x80; + int r2 = ((r1 >> 8) + r1) >> 8; + return (byte)r2; + } + + public static int FastDivideShortByByte(ushort n, byte d) + { + int i = d * 3; + uint m = masTable[i]; + uint a = masTable[i + 1]; + uint s = masTable[i + 2]; + + uint nTimesMPlusA = unchecked((n * m) + a); + uint shifted = nTimesMPlusA >> (int)s; + int r = (int)shifted; + + return r; + } + + // i = z * 3; + // (x / z) = ((x * masTable[i]) + masTable[i + 1]) >> masTable[i + 2) + private static readonly uint[] masTable = + { + 0x00000000, 0x00000000, 0, // 0 + 0x00000001, 0x00000000, 0, // 1 + 0x00000001, 0x00000000, 1, // 2 + 0xAAAAAAAB, 0x00000000, 33, // 3 + 0x00000001, 0x00000000, 2, // 4 + 0xCCCCCCCD, 0x00000000, 34, // 5 + 0xAAAAAAAB, 0x00000000, 34, // 6 + 0x49249249, 0x49249249, 33, // 7 + 0x00000001, 0x00000000, 3, // 8 + 0x38E38E39, 0x00000000, 33, // 9 + 0xCCCCCCCD, 0x00000000, 35, // 10 + 0xBA2E8BA3, 0x00000000, 35, // 11 + 0xAAAAAAAB, 0x00000000, 35, // 12 + 0x4EC4EC4F, 0x00000000, 34, // 13 + 0x49249249, 0x49249249, 34, // 14 + 0x88888889, 0x00000000, 35, // 15 + 0x00000001, 0x00000000, 4, // 16 + 0xF0F0F0F1, 0x00000000, 36, // 17 + 0x38E38E39, 0x00000000, 34, // 18 + 0xD79435E5, 0xD79435E5, 36, // 19 + 0xCCCCCCCD, 0x00000000, 36, // 20 + 0xC30C30C3, 0xC30C30C3, 36, // 21 + 0xBA2E8BA3, 0x00000000, 36, // 22 + 0xB21642C9, 0x00000000, 36, // 23 + 0xAAAAAAAB, 0x00000000, 36, // 24 + 0x51EB851F, 0x00000000, 35, // 25 + 0x4EC4EC4F, 0x00000000, 35, // 26 + 0x97B425ED, 0x97B425ED, 36, // 27 + 0x49249249, 0x49249249, 35, // 28 + 0x8D3DCB09, 0x00000000, 36, // 29 + 0x88888889, 0x00000000, 36, // 30 + 0x42108421, 0x42108421, 35, // 31 + 0x00000001, 0x00000000, 5, // 32 + 0x3E0F83E1, 0x00000000, 35, // 33 + 0xF0F0F0F1, 0x00000000, 37, // 34 + 0x75075075, 0x75075075, 36, // 35 + 0x38E38E39, 0x00000000, 35, // 36 + 0x6EB3E453, 0x6EB3E453, 36, // 37 + 0xD79435E5, 0xD79435E5, 37, // 38 + 0x69069069, 0x69069069, 36, // 39 + 0xCCCCCCCD, 0x00000000, 37, // 40 + 0xC7CE0C7D, 0x00000000, 37, // 41 + 0xC30C30C3, 0xC30C30C3, 37, // 42 + 0x2FA0BE83, 0x00000000, 35, // 43 + 0xBA2E8BA3, 0x00000000, 37, // 44 + 0x5B05B05B, 0x5B05B05B, 36, // 45 + 0xB21642C9, 0x00000000, 37, // 46 + 0xAE4C415D, 0x00000000, 37, // 47 + 0xAAAAAAAB, 0x00000000, 37, // 48 + 0x5397829D, 0x00000000, 36, // 49 + 0x51EB851F, 0x00000000, 36, // 50 + 0xA0A0A0A1, 0x00000000, 37, // 51 + 0x4EC4EC4F, 0x00000000, 36, // 52 + 0x9A90E7D9, 0x9A90E7D9, 37, // 53 + 0x97B425ED, 0x97B425ED, 37, // 54 + 0x94F2094F, 0x94F2094F, 37, // 55 + 0x49249249, 0x49249249, 36, // 56 + 0x47DC11F7, 0x47DC11F7, 36, // 57 + 0x8D3DCB09, 0x00000000, 37, // 58 + 0x22B63CBF, 0x00000000, 35, // 59 + 0x88888889, 0x00000000, 37, // 60 + 0x4325C53F, 0x00000000, 36, // 61 + 0x42108421, 0x42108421, 36, // 62 + 0x41041041, 0x41041041, 36, // 63 + 0x00000001, 0x00000000, 6, // 64 + 0xFC0FC0FD, 0x00000000, 38, // 65 + 0x3E0F83E1, 0x00000000, 36, // 66 + 0x07A44C6B, 0x00000000, 33, // 67 + 0xF0F0F0F1, 0x00000000, 38, // 68 + 0x76B981DB, 0x00000000, 37, // 69 + 0x75075075, 0x75075075, 37, // 70 + 0xE6C2B449, 0x00000000, 38, // 71 + 0x38E38E39, 0x00000000, 36, // 72 + 0x381C0E07, 0x381C0E07, 36, // 73 + 0x6EB3E453, 0x6EB3E453, 37, // 74 + 0x1B4E81B5, 0x00000000, 35, // 75 + 0xD79435E5, 0xD79435E5, 38, // 76 + 0x3531DEC1, 0x00000000, 36, // 77 + 0x69069069, 0x69069069, 37, // 78 + 0xCF6474A9, 0x00000000, 38, // 79 + 0xCCCCCCCD, 0x00000000, 38, // 80 + 0xCA4587E7, 0x00000000, 38, // 81 + 0xC7CE0C7D, 0x00000000, 38, // 82 + 0x3159721F, 0x00000000, 36, // 83 + 0xC30C30C3, 0xC30C30C3, 38, // 84 + 0xC0C0C0C1, 0x00000000, 38, // 85 + 0x2FA0BE83, 0x00000000, 36, // 86 + 0x2F149903, 0x00000000, 36, // 87 + 0xBA2E8BA3, 0x00000000, 38, // 88 + 0xB81702E1, 0x00000000, 38, // 89 + 0x5B05B05B, 0x5B05B05B, 37, // 90 + 0x2D02D02D, 0x2D02D02D, 36, // 91 + 0xB21642C9, 0x00000000, 38, // 92 + 0xB02C0B03, 0x00000000, 38, // 93 + 0xAE4C415D, 0x00000000, 38, // 94 + 0x2B1DA461, 0x2B1DA461, 36, // 95 + 0xAAAAAAAB, 0x00000000, 38, // 96 + 0xA8E83F57, 0xA8E83F57, 38, // 97 + 0x5397829D, 0x00000000, 37, // 98 + 0xA57EB503, 0x00000000, 38, // 99 + 0x51EB851F, 0x00000000, 37, // 100 + 0xA237C32B, 0xA237C32B, 38, // 101 + 0xA0A0A0A1, 0x00000000, 38, // 102 + 0x9F1165E7, 0x9F1165E7, 38, // 103 + 0x4EC4EC4F, 0x00000000, 37, // 104 + 0x27027027, 0x27027027, 36, // 105 + 0x9A90E7D9, 0x9A90E7D9, 38, // 106 + 0x991F1A51, 0x991F1A51, 38, // 107 + 0x97B425ED, 0x97B425ED, 38, // 108 + 0x2593F69B, 0x2593F69B, 36, // 109 + 0x94F2094F, 0x94F2094F, 38, // 110 + 0x24E6A171, 0x24E6A171, 36, // 111 + 0x49249249, 0x49249249, 37, // 112 + 0x90FDBC09, 0x90FDBC09, 38, // 113 + 0x47DC11F7, 0x47DC11F7, 37, // 114 + 0x8E78356D, 0x8E78356D, 38, // 115 + 0x8D3DCB09, 0x00000000, 38, // 116 + 0x23023023, 0x23023023, 36, // 117 + 0x22B63CBF, 0x00000000, 36, // 118 + 0x44D72045, 0x00000000, 37, // 119 + 0x88888889, 0x00000000, 38, // 120 + 0x8767AB5F, 0x8767AB5F, 38, // 121 + 0x4325C53F, 0x00000000, 37, // 122 + 0x85340853, 0x85340853, 38, // 123 + 0x42108421, 0x42108421, 37, // 124 + 0x10624DD3, 0x00000000, 35, // 125 + 0x41041041, 0x41041041, 37, // 126 + 0x10204081, 0x10204081, 35, // 127 + 0x00000001, 0x00000000, 7, // 128 + 0x0FE03F81, 0x00000000, 35, // 129 + 0xFC0FC0FD, 0x00000000, 39, // 130 + 0xFA232CF3, 0x00000000, 39, // 131 + 0x3E0F83E1, 0x00000000, 37, // 132 + 0xF6603D99, 0x00000000, 39, // 133 + 0x07A44C6B, 0x00000000, 34, // 134 + 0xF2B9D649, 0x00000000, 39, // 135 + 0xF0F0F0F1, 0x00000000, 39, // 136 + 0x077975B9, 0x00000000, 34, // 137 + 0x76B981DB, 0x00000000, 38, // 138 + 0x75DED953, 0x00000000, 38, // 139 + 0x75075075, 0x75075075, 38, // 140 + 0x3A196B1F, 0x00000000, 37, // 141 + 0xE6C2B449, 0x00000000, 39, // 142 + 0xE525982B, 0x00000000, 39, // 143 + 0x38E38E39, 0x00000000, 37, // 144 + 0xE1FC780F, 0x00000000, 39, // 145 + 0x381C0E07, 0x381C0E07, 37, // 146 + 0xDEE95C4D, 0x00000000, 39, // 147 + 0x6EB3E453, 0x6EB3E453, 38, // 148 + 0xDBEB61EF, 0x00000000, 39, // 149 + 0x1B4E81B5, 0x00000000, 36, // 150 + 0x36406C81, 0x00000000, 37, // 151 + 0xD79435E5, 0xD79435E5, 39, // 152 + 0xD62B80D7, 0x00000000, 39, // 153 + 0x3531DEC1, 0x00000000, 37, // 154 + 0xD3680D37, 0x00000000, 39, // 155 + 0x69069069, 0x69069069, 38, // 156 + 0x342DA7F3, 0x00000000, 37, // 157 + 0xCF6474A9, 0x00000000, 39, // 158 + 0xCE168A77, 0xCE168A77, 39, // 159 + 0xCCCCCCCD, 0x00000000, 39, // 160 + 0xCB8727C1, 0x00000000, 39, // 161 + 0xCA4587E7, 0x00000000, 39, // 162 + 0xC907DA4F, 0x00000000, 39, // 163 + 0xC7CE0C7D, 0x00000000, 39, // 164 + 0x634C0635, 0x00000000, 38, // 165 + 0x3159721F, 0x00000000, 37, // 166 + 0x621B97C3, 0x00000000, 38, // 167 + 0xC30C30C3, 0xC30C30C3, 39, // 168 + 0x60F25DEB, 0x00000000, 38, // 169 + 0xC0C0C0C1, 0x00000000, 39, // 170 + 0x17F405FD, 0x17F405FD, 36, // 171 + 0x2FA0BE83, 0x00000000, 37, // 172 + 0xBD691047, 0xBD691047, 39, // 173 + 0x2F149903, 0x00000000, 37, // 174 + 0x5D9F7391, 0x00000000, 38, // 175 + 0xBA2E8BA3, 0x00000000, 39, // 176 + 0x5C90A1FD, 0x5C90A1FD, 38, // 177 + 0xB81702E1, 0x00000000, 39, // 178 + 0x5B87DDAD, 0x5B87DDAD, 38, // 179 + 0x5B05B05B, 0x5B05B05B, 38, // 180 + 0xB509E68B, 0x00000000, 39, // 181 + 0x2D02D02D, 0x2D02D02D, 37, // 182 + 0xB30F6353, 0x00000000, 39, // 183 + 0xB21642C9, 0x00000000, 39, // 184 + 0x1623FA77, 0x1623FA77, 36, // 185 + 0xB02C0B03, 0x00000000, 39, // 186 + 0xAF3ADDC7, 0x00000000, 39, // 187 + 0xAE4C415D, 0x00000000, 39, // 188 + 0x15AC056B, 0x15AC056B, 36, // 189 + 0x2B1DA461, 0x2B1DA461, 37, // 190 + 0xAB8F69E3, 0x00000000, 39, // 191 + 0xAAAAAAAB, 0x00000000, 39, // 192 + 0x15390949, 0x00000000, 36, // 193 + 0xA8E83F57, 0xA8E83F57, 39, // 194 + 0x15015015, 0x15015015, 36, // 195 + 0x5397829D, 0x00000000, 38, // 196 + 0xA655C439, 0xA655C439, 39, // 197 + 0xA57EB503, 0x00000000, 39, // 198 + 0x5254E78F, 0x00000000, 38, // 199 + 0x51EB851F, 0x00000000, 38, // 200 + 0x028C1979, 0x00000000, 33, // 201 + 0xA237C32B, 0xA237C32B, 39, // 202 + 0xA16B312F, 0x00000000, 39, // 203 + 0xA0A0A0A1, 0x00000000, 39, // 204 + 0x4FEC04FF, 0x00000000, 38, // 205 + 0x9F1165E7, 0x9F1165E7, 39, // 206 + 0x27932B49, 0x00000000, 37, // 207 + 0x4EC4EC4F, 0x00000000, 38, // 208 + 0x9CC8E161, 0x00000000, 39, // 209 + 0x27027027, 0x27027027, 37, // 210 + 0x9B4C6F9F, 0x00000000, 39, // 211 + 0x9A90E7D9, 0x9A90E7D9, 39, // 212 + 0x99D722DB, 0x00000000, 39, // 213 + 0x991F1A51, 0x991F1A51, 39, // 214 + 0x4C346405, 0x00000000, 38, // 215 + 0x97B425ED, 0x97B425ED, 39, // 216 + 0x4B809701, 0x4B809701, 38, // 217 + 0x2593F69B, 0x2593F69B, 37, // 218 + 0x12B404AD, 0x12B404AD, 36, // 219 + 0x94F2094F, 0x94F2094F, 39, // 220 + 0x25116025, 0x25116025, 37, // 221 + 0x24E6A171, 0x24E6A171, 37, // 222 + 0x24BC44E1, 0x24BC44E1, 37, // 223 + 0x49249249, 0x49249249, 38, // 224 + 0x91A2B3C5, 0x00000000, 39, // 225 + 0x90FDBC09, 0x90FDBC09, 39, // 226 + 0x905A3863, 0x905A3863, 39, // 227 + 0x47DC11F7, 0x47DC11F7, 38, // 228 + 0x478BBCED, 0x00000000, 38, // 229 + 0x8E78356D, 0x8E78356D, 39, // 230 + 0x46ED2901, 0x46ED2901, 38, // 231 + 0x8D3DCB09, 0x00000000, 39, // 232 + 0x2328A701, 0x2328A701, 37, // 233 + 0x23023023, 0x23023023, 37, // 234 + 0x45B81A25, 0x45B81A25, 38, // 235 + 0x22B63CBF, 0x00000000, 37, // 236 + 0x08A42F87, 0x08A42F87, 35, // 237 + 0x44D72045, 0x00000000, 38, // 238 + 0x891AC73B, 0x00000000, 39, // 239 + 0x88888889, 0x00000000, 39, // 240 + 0x10FEF011, 0x00000000, 36, // 241 + 0x8767AB5F, 0x8767AB5F, 39, // 242 + 0x86D90545, 0x00000000, 39, // 243 + 0x4325C53F, 0x00000000, 38, // 244 + 0x85BF3761, 0x85BF3761, 39, // 245 + 0x85340853, 0x85340853, 39, // 246 + 0x10953F39, 0x10953F39, 36, // 247 + 0x42108421, 0x42108421, 38, // 248 + 0x41CC9829, 0x41CC9829, 38, // 249 + 0x10624DD3, 0x00000000, 36, // 250 + 0x828CBFBF, 0x00000000, 39, // 251 + 0x41041041, 0x41041041, 38, // 252 + 0x81848DA9, 0x00000000, 39, // 253 + 0x10204081, 0x10204081, 36, // 254 + 0x80808081, 0x00000000, 39 // 255 + }; + } +} diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs index 2515815cb..915147d2a 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Text; @@ -9,25 +10,66 @@ using Tango.RemoteDesktop.Utils; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents a generic screen capture engine. + /// The <see cref="TFrame"/> type will be the type of difference object delivered by the engine. + /// </summary> + /// <typeparam name="TFrame">The type of the frame.</typeparam> + /// <seealso cref="Tango.RemoteDesktop.IScreenCaptureEngine{TFrame}" /> public class ScreenCaptureEngine<TFrame> : IScreenCaptureEngine<TFrame> where TFrame : class, IFrame { private Thread _captureThread; private Bitmap _previousBitmap; private bool _isDisposed; + /// <summary> + /// Occurs when a new screen frame is available. + /// </summary> public event EventHandler<ScreenCaptureFrameReceivedEventArgs<TFrame>> FrameReceived; + /// <summary> + /// Gets or sets the screen capture method. + /// </summary> public ICaptureMethod CaptureMethod { get; set; } + + /// <summary> + /// Gets or sets the screen capture region. + /// </summary> public CaptureRegion CaptureRegion { get; set; } + + /// <summary> + /// Gets a value indicating whether this instance is currently capturing. + /// </summary> public bool IsStarted { get; private set; } - public TimeSpan Interval { get; set; } + + /// <summary> + /// Gets or sets the frame rate per second. + /// Default is 10. + /// </summary> + public int FrameRate { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to include the cursor when capturing. + /// </summary> public bool CaptureCursor { get; set; } + + /// <summary> + /// Gets or sets the bitmap comparer. + /// </summary> public IBitmapComparer<TFrame> Comparer { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to enable image comparison. + /// </summary> public bool EnableComparer { get; set; } + /// <summary> + /// Initializes a new instance of the <see cref="ScreenCaptureEngine{TFrame}"/> class using the specified <see cref="IBitmapComparer{TFrame}"/>. + /// </summary> + /// <param name="comparer">The comparer.</param> public ScreenCaptureEngine(IBitmapComparer<TFrame> comparer) { - Interval = TimeSpan.FromMilliseconds(100); + FrameRate = 10; CaptureMethod = new CaptureMethods.DirectXScreenCapture(); CaptureRegion = new CaptureRegion(System.Windows.Forms.Screen.PrimaryScreen.Bounds); Comparer = comparer; @@ -39,6 +81,10 @@ namespace Tango.RemoteDesktop } } + /// <summary> + /// Start capturing. + /// </summary> + /// <exception cref="ObjectDisposedException">Screen capture engine cannot be started after disposed.</exception> public void Start() { if (_isDisposed) @@ -57,10 +103,39 @@ namespace Tango.RemoteDesktop } } + /// <summary> + /// Stop capturing. + /// </summary> + public void Stop() + { + if (IsStarted) + { + IsStarted = false; + _previousBitmap?.Dispose(); + } + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + public void Dispose() + { + if (!_isDisposed) + { + _isDisposed = true; + Stop(); + CaptureMethod?.Dispose(); + } + } + private void CaptureThreadMethod() { + Stopwatch watch = new Stopwatch(); + while (IsStarted) { + watch.Restart(); + var bitmap = CaptureMethod.GetDesktopBitmap(CaptureRegion); if (CaptureCursor) @@ -76,48 +151,34 @@ namespace Tango.RemoteDesktop if (_previousBitmap == null) { _previousBitmap = bitmap.Clone() as Bitmap; - OnFrameReceived(bitmap, null); + OnFrameReceived(bitmap, null, false); } else { - var diffFrame = Comparer.CreateDifference(_previousBitmap, bitmap); + var result = Comparer.CreateDifference(_previousBitmap, bitmap); _previousBitmap.Dispose(); _previousBitmap = bitmap.Clone() as Bitmap; - OnFrameReceived(bitmap, diffFrame); + OnFrameReceived(bitmap, result.Frame, result.ContainsDifference); } } else { - OnFrameReceived(bitmap, null); + OnFrameReceived(bitmap, null, false); } - Thread.Sleep(Interval); - } - } - - public void Stop() - { - if (IsStarted) - { - IsStarted = false; + int delay = Math.Max(5, (FrameRate * 10) - (int)watch.ElapsedMilliseconds); + Thread.Sleep(delay); } } - public void Dispose() - { - if (!_isDisposed) - { - _isDisposed = true; - Stop(); - CaptureMethod?.Dispose(); - } - } - - private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame) + private void OnFrameReceived(Bitmap currentBitmap, TFrame diffFrame, bool hasDifference) { FrameReceived?.Invoke(this, new ScreenCaptureFrameReceivedEventArgs<TFrame>() { Frame = new ScreenCaptureFrame<TFrame>(currentBitmap, diffFrame) + { + HasDifference = hasDifference + } }); } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs index b45a9c9bc..75e7a961f 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrame.cs @@ -10,29 +10,74 @@ using Tango.RemoteDesktop.Frames; namespace Tango.RemoteDesktop { - public class ScreenCaptureFrame<T> : RasterFrame, IDisposable where T : IFrame + /// <summary> + /// Represents a screen capture frame for holding the original bitmap and difference frame. + /// </summary> + /// <typeparam name="TFrame">The type of the frame.</typeparam> + /// <seealso cref="Tango.RemoteDesktop.Frames.RasterFrame" /> + /// <seealso cref="System.IDisposable" /> + public class ScreenCaptureFrame<TFrame> : RasterFrame, IDisposable where TFrame : IFrame { - private T _diffFrame; + private TFrame _diffFrame; + /// <summary> + /// Initializes a new instance of the <see cref="ScreenCaptureFrame{TFrame}"/> class. + /// </summary> + /// <param name="bitmap">The bitmap.</param> public ScreenCaptureFrame(Bitmap bitmap) : base(bitmap) { + } - public ScreenCaptureFrame(Bitmap bitmap, T diffFrame) : this(bitmap) + /// <summary> + /// Initializes a new instance of the <see cref="ScreenCaptureFrame{TFrame}"/> class. + /// </summary> + /// <param name="bitmap">The bitmap.</param> + /// <param name="diffFrame">The difference frame.</param> + public ScreenCaptureFrame(Bitmap bitmap, TFrame diffFrame) : this(bitmap) { _diffFrame = diffFrame; } + /// <summary> + /// Gets a value indicating whether a difference frame is available. + /// </summary> public bool DifferenceAvailable { get { return _diffFrame != null; } } - public T ToDifference() + /// <summary> + /// Gets or sets a value indicating whether the difference frame is available and contains any differences. + /// </summary> + private bool _hasDifference; + public bool HasDifference { + get + { + return DifferenceAvailable && _hasDifference; + } + set { _hasDifference = value; } + } + + /// <summary> + /// Returns the difference frame. + /// </summary> + /// <returns></returns> + /// <exception cref="InvalidOperationException">No difference is available at this point. Please use the 'DifferenceAvailable' property before attempting to get the difference.</exception> + public TFrame ToDifference() + { + if (!DifferenceAvailable) + { + throw new InvalidOperationException("No difference is available at this point. Please use the 'DifferenceAvailable' property before attempting to get the difference."); + } + return _diffFrame; } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public override void Dispose() { base.Dispose(); diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs index 5732744a4..df53d6db9 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/ScreenCaptureFrameReceivedEventArgs.cs @@ -6,8 +6,16 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop { + /// <summary> + /// Represents the <see cref="IScreenCaptureEngine{TFrame}.FrameReceived"/> event arguments. + /// </summary> + /// <typeparam name="T"></typeparam> + /// <seealso cref="System.EventArgs" /> public class ScreenCaptureFrameReceivedEventArgs<T> : EventArgs where T: IFrame { + /// <summary> + /// Gets or sets the screen capture frame containing the original bitmap and difference frame. + /// </summary> public ScreenCaptureFrame<T> Frame { get; set; } } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj index 35f797626..f760b43dc 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Tango.RemoteDesktop.csproj @@ -61,14 +61,26 @@ <Reference Include="WindowsBase" /> </ItemGroup> <ItemGroup> + <Compile Include="BitmapComparerResult.cs" /> <Compile Include="CaptureRegion.cs" /> <Compile Include="CaptureMethods\BitBltScreenCapture.cs" /> <Compile Include="CaptureMethods\DirectXScreenCapture.cs" /> <Compile Include="CaptureMethods\GdiScreenCapture.cs" /> + <Compile Include="Clipping\ClipResult.cs" /> + <Compile Include="Clipping\BitmapCliper.cs" /> <Compile Include="Comparers\VectorBitmapComparer.cs" /> + <Compile Include="Encoders\GifEncoder.cs" /> + <Compile Include="Encoders\Png8BitEncoder.cs" /> <Compile Include="Engines\RasterScreenCaptureEngine.cs" /> <Compile Include="Engines\VectorScreenCaptureEngine.cs" /> + <Compile Include="Frames\VectorFrameColor.cs" /> <Compile Include="IScreenCaptureEngine.cs" /> + <Compile Include="Quantization\ColorBgra.cs" /> + <Compile Include="Quantization\OctreeQuantizer.cs" /> + <Compile Include="Quantization\PaletteTable.cs" /> + <Compile Include="Quantization\Quantizer.cs" /> + <Compile Include="Quantization\Scanline.cs" /> + <Compile Include="Quantization\Utility.cs" /> <Compile Include="ScreenCaptureEngine.cs" /> <Compile Include="ScreenCaptureFrameReceivedEventArgs.cs" /> <Compile Include="Utils\CursorUtils.cs" /> @@ -90,6 +102,5 @@ <Compile Include="Frames\VectorFrame.cs" /> <Compile Include="Frames\VectorFramePixel.cs" /> </ItemGroup> - <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project>
\ No newline at end of file diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs index 11bd4f6ff..9dad26984 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/CursorUtils.cs @@ -8,6 +8,9 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.Utils { + /// <summary> + /// Represents a static cursor helper. + /// </summary> public static class CursorUtils { #region INTERNALS @@ -204,9 +207,16 @@ namespace Tango.RemoteDesktop.Utils #endregion + /// <summary> + /// Applies the current user cursor icon at the specified position on the specified bitmap. + /// </summary> + /// <param name="graphics">The drawing graphics object.</param> + /// <param name="bitmap">The bitmap.</param> + /// <param name="left">The left cursor position.</param> + /// <param name="top">The top cursor position.</param> [DebuggerHidden] [DebuggerStepThrough] - public static void ApplyCursor(System.Drawing.Graphics g, System.Drawing.Bitmap bitmap, int left, int top) + public static void ApplyCursor(System.Drawing.Graphics graphics, System.Drawing.Bitmap bitmap, int left, int top) { try { @@ -221,14 +231,14 @@ namespace Tango.RemoteDesktop.Utils //User32.ICONINFO iconInfo; //User32.GetIconInfo(iconPointer, out iconInfo); - var hdc = g.GetHdc(); + var hdc = graphics.GetHdc(); User32.DrawIconEx(hdc, pci.ptScreenPos.x - left, pci.ptScreenPos.y - top, pci.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); //User32.DestroyIcon(iconPointer); GDIStuff.DeleteObject(pci.hCursor); } } - g.ReleaseHdc(); + graphics.ReleaseHdc(); } catch { } } diff --git a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs index fbcc5710c..83b36fcfa 100644 --- a/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs +++ b/Software/Experiments/Tango.RemoteDesktop/Tango.RemoteDesktop/Utils/DirectBitmap.cs @@ -9,6 +9,10 @@ using System.Threading.Tasks; namespace Tango.RemoteDesktop.Utils { + /// <summary> + /// Represents a bitmap object with fast Get/Set Pixel methods. + /// </summary> + /// <seealso cref="System.IDisposable" /> public class DirectBitmap : IDisposable { public Bitmap Bitmap { get; private set; } @@ -19,6 +23,11 @@ namespace Tango.RemoteDesktop.Utils protected GCHandle BitsHandle { get; private set; } + /// <summary> + /// Initializes a new instance of the <see cref="DirectBitmap"/> class. + /// </summary> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> public DirectBitmap(int width, int height) { Width = width; @@ -28,14 +37,26 @@ namespace Tango.RemoteDesktop.Utils Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); } - public void SetPixel(int x, int y, Color colour) + /// <summary> + /// Sets the pixel color. + /// </summary> + /// <param name="x">The x position.</param> + /// <param name="y">The y position.</param> + /// <param name="color">The color.</param> + public void SetPixel(int x, int y, Color color) { int index = x + (y * Width); - int col = colour.ToArgb(); + int col = color.ToArgb(); Bits[index] = col; } + /// <summary> + /// Gets the pixel color. + /// </summary> + /// <param name="x">The x position.</param> + /// <param name="y">The y position.</param> + /// <returns></returns> public Color GetPixel(int x, int y) { int index = x + (y * Width); @@ -45,6 +66,9 @@ namespace Tango.RemoteDesktop.Utils return result; } + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> public void Dispose() { if (Disposed) return; diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml index 2a1c3a498..ab9249042 100644 --- a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml @@ -7,6 +7,6 @@ mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> - <Image Stretch="Fill" x:Name="img" /> + <Image Stretch="Fill" x:Name="img" RenderOptions.BitmapScalingMode="Fant" /> </Grid> </Window> diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs index 85e5ca0bd..fd0df286c 100644 --- a/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp1/MainWindow.xaml.cs @@ -34,6 +34,7 @@ namespace WpfApp1 public MainWindow() { + InitializeComponent(); ContentRendered += MainWindow_ContentRendered; @@ -41,7 +42,7 @@ namespace WpfApp1 { CaptureRegion = new CaptureRegion(0, 0, 1280, 800) }; - _engine.Interval = TimeSpan.FromMilliseconds(100); + _engine.FrameRate = 5; //Per second _engine.CaptureCursor = true; _engine.FrameReceived += _engine_FrameReceived; } @@ -54,28 +55,49 @@ namespace WpfApp1 if (e.Frame.DifferenceAvailable) { - var diffFrame = e.Frame.ToDifference(); + if (e.Frame.HasDifference) + { + var diffFrame = e.Frame.ToDifference(); + + var bitmapStream = diffFrame.OptimizeBounds().ToEncoder<PngEncoder>().ToStream(); + + size = (int)bitmapStream.Length; + + Bitmap newBitmap = new Bitmap(bitmapStream); + + diffFrame = new RasterFrame(newBitmap, diffFrame.Left, diffFrame.Top); - size = diffFrame.Encode<PngEncoder>().ToArray().Length; + diffFrame.Apply(_currentBitmap); - //diffFrame.Apply(_currentBitmap); + diffFrame.Dispose(); + bitmapStream.Dispose(); - //var updatedFrame = new RasterFrame(_currentBitmap); - //preview = updatedFrame.ToBitmapSource(); + var updatedFrame = new RasterFrame(_currentBitmap); + preview = updatedFrame.ToBitmapSource(); + + Debug.WriteLine($"Actual frame size on network: {size / 1000} kb"); + + Dispatcher.BeginInvoke(new Action(() => + { + img.Source = preview; + })); + } + else + { + //No need to send new frames (no differences)... + } } else { _currentBitmap = e.Frame.ToBitmap().Clone() as Bitmap; preview = e.Frame.ToBitmapSource(); - size = e.Frame.Encode<PngEncoder>().ToArray().Length; - } + size = e.Frame.ToEncoder<Png8BitEncoder>().ToArray().Length; - //Debug.WriteLine($"Actual frame size on network: {size / 1000} kb"); - - //Dispatcher.BeginInvoke(new Action(() => - //{ - // img.Source = preview; - //})); + Dispatcher.BeginInvoke(new Action(() => + { + img.Source = preview; + })); + } e.Frame.Dispose(); } diff --git a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs index 0a0f7ff57..f7673f0ab 100644 --- a/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs +++ b/Software/Experiments/Tango.RemoteDesktop/WpfApp2/MainWindow.xaml.cs @@ -30,6 +30,7 @@ namespace WpfApp1 public MainWindow() { + InitializeComponent(); ContentRendered += MainWindow_ContentRendered; |
