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/Visual_Studio/Tango.RemoteDesktop/Utils | |
| 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/Visual_Studio/Tango.RemoteDesktop/Utils')
3 files changed, 1189 insertions, 0 deletions
diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs new file mode 100644 index 000000000..9dad26984 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/CursorUtils.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Utils +{ + /// <summary> + /// Represents a static cursor helper. + /// </summary> + public static class CursorUtils + { + #region INTERNALS + + internal static class User32 + { + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; + public Int32 xHotspot; + public Int32 yHotspot; + public IntPtr hbmMask; + public IntPtr hbmColor; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINT ptScreenPos; + } + + [DllImport("user32.dll")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool DrawIconEx(IntPtr hdc, int xLeft, int yTop, IntPtr hIcon, int cxWidth, int cyHeight, int istepIfAniCur, IntPtr hbrFlickerFreeDraw, int diFlags); + + [DllImport("user32.dll")] + public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon); + + [DllImport("user32.dll")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + } + + #endregion + + #region Internal Classes + + [StructLayout(LayoutKind.Sequential)] + struct CURSORINFO + { + public Int32 cbSize; + public Int32 flags; + public IntPtr hCursor; + public POINTAPI ptScreenPos; + } + + [StructLayout(LayoutKind.Sequential)] + struct POINTAPI + { + public int x; + public int y; + } + + [DllImport("user32.dll")] + static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll")] + static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon); + + const Int32 CURSOR_SHOWING = 0x00000001; + + internal struct SIZE + { + public int cx; + public int cy; + } + + internal class GDIStuff + { + #region Class Variables + public const int SRCCOPY = 13369376; + #endregion + + + #region Class Functions + [DllImport("gdi32.dll", EntryPoint = "CreateDC")] + public static extern IntPtr CreateDC(IntPtr lpszDriver, string lpszDevice, IntPtr lpszOutput, IntPtr lpInitData); + + [DllImport("gdi32.dll", EntryPoint = "DeleteDC")] + public static extern IntPtr DeleteDC(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "DeleteObject")] + public static extern IntPtr DeleteObject(IntPtr hDc); + + [DllImport("gdi32.dll", EntryPoint = "BitBlt")] + public static extern bool BitBlt(IntPtr hdcDest, int xDest, + int yDest, int wDest, + int hDest, IntPtr hdcSource, + int xSrc, int ySrc, int RasterOp); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleBitmap")] + public static extern IntPtr CreateCompatibleBitmap + (IntPtr hdc, int nWidth, int nHeight); + + [DllImport("gdi32.dll", EntryPoint = "CreateCompatibleDC")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [DllImport("gdi32.dll", EntryPoint = "SelectObject")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr bmp); + #endregion + } + + internal class Win32Stuff + { + + #region Class Variables + + public const int SM_CXSCREEN = 0; + public const int SM_CYSCREEN = 1; + + public const Int32 CURSOR_SHOWING = 0x00000001; + + [StructLayout(LayoutKind.Sequential)] + public struct ICONINFO + { + public bool fIcon; // Specifies whether this structure defines an icon or a cursor. A value of TRUE specifies + public Int32 xHotspot; // Specifies the x-coordinate of a cursor's hot spot. If this structure defines an icon, the hot + public Int32 yHotspot; // Specifies the y-coordinate of the cursor's hot spot. If this structure defines an icon, the hot + public IntPtr hbmMask; // (HBITMAP) Specifies the icon bitmask bitmap. If this structure defines a black and white icon, + public IntPtr hbmColor; // (HBITMAP) Handle to the icon color bitmap. This member can be optional if this + } + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public Int32 x; + public Int32 y; + } + + [StructLayout(LayoutKind.Sequential)] + public struct CURSORINFO + { + public Int32 cbSize; // Specifies the size, in bytes, of the structure. + public Int32 flags; // Specifies the cursor state. This parameter can be one of the following values: + public IntPtr hCursor; // Handle to the cursor. + public POINT ptScreenPos; // A POINT structure that receives the screen coordinates of the cursor. + } + + #endregion + + + #region Class Functions + + [DllImport("user32.dll", EntryPoint = "GetDesktopWindow")] + public static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll", EntryPoint = "GetDC")] + public static extern IntPtr GetDC(IntPtr ptr); + + [DllImport("user32.dll", EntryPoint = "GetSystemMetrics")] + public static extern int GetSystemMetrics(int abc); + + [DllImport("user32.dll", EntryPoint = "GetWindowDC")] + public static extern IntPtr GetWindowDC(Int32 ptr); + + [DllImport("user32.dll", EntryPoint = "ReleaseDC")] + public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc); + + + [DllImport("user32.dll", EntryPoint = "GetCursorInfo")] + public static extern bool GetCursorInfo(out CURSORINFO pci); + + [DllImport("user32.dll", EntryPoint = "CopyIcon")] + public static extern IntPtr CopyIcon(IntPtr hIcon); + + [DllImport("user32.dll", SetLastError = true)] + static extern bool DestroyIcon(IntPtr hIcon); + + [DllImport("user32.dll", EntryPoint = "GetIconInfo")] + public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo); + + + #endregion + } + + #endregion + + /// <summary> + /// Applies the current user cursor icon at the specified position on the specified bitmap. + /// </summary> + /// <param name="graphics">The drawing graphics object.</param> + /// <param name="bitmap">The bitmap.</param> + /// <param name="left">The left cursor position.</param> + /// <param name="top">The top cursor position.</param> + [DebuggerHidden] + [DebuggerStepThrough] + public static void ApplyCursor(System.Drawing.Graphics graphics, System.Drawing.Bitmap bitmap, int left, int top) + { + try + { + CURSORINFO pci; + pci.cbSize = Marshal.SizeOf(typeof(CURSORINFO)); + + if (GetCursorInfo(out pci)) + { + if (pci.flags == CURSOR_SHOWING) + { + //var iconPointer = User32.CopyIcon(pci.hCursor); + //User32.ICONINFO iconInfo; + //User32.GetIconInfo(iconPointer, out iconInfo); + + var hdc = graphics.GetHdc(); + User32.DrawIconEx(hdc, pci.ptScreenPos.x - left, pci.ptScreenPos.y - top, pci.hCursor, 0, 0, 0, IntPtr.Zero, 0x0003); + + //User32.DestroyIcon(iconPointer); + GDIStuff.DeleteObject(pci.hCursor); + } + } + graphics.ReleaseHdc(); + } + catch { } + } + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs new file mode 100644 index 000000000..83b36fcfa --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/DirectBitmap.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Utils +{ + /// <summary> + /// Represents a bitmap object with fast Get/Set Pixel methods. + /// </summary> + /// <seealso cref="System.IDisposable" /> + public class DirectBitmap : IDisposable + { + public Bitmap Bitmap { get; private set; } + public Int32[] Bits { get; private set; } + public bool Disposed { get; private set; } + public int Height { get; private set; } + public int Width { get; private set; } + + protected GCHandle BitsHandle { get; private set; } + + /// <summary> + /// Initializes a new instance of the <see cref="DirectBitmap"/> class. + /// </summary> + /// <param name="width">The width.</param> + /// <param name="height">The height.</param> + public DirectBitmap(int width, int height) + { + Width = width; + Height = height; + Bits = new Int32[width * height]; + BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned); + Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject()); + } + + /// <summary> + /// Sets the pixel color. + /// </summary> + /// <param name="x">The x position.</param> + /// <param name="y">The y position.</param> + /// <param name="color">The color.</param> + public void SetPixel(int x, int y, Color color) + { + int index = x + (y * Width); + int col = color.ToArgb(); + + Bits[index] = col; + } + + /// <summary> + /// Gets the pixel color. + /// </summary> + /// <param name="x">The x position.</param> + /// <param name="y">The y position.</param> + /// <returns></returns> + public Color GetPixel(int x, int y) + { + int index = x + (y * Width); + int col = Bits[index]; + Color result = Color.FromArgb(col); + + return result; + } + + /// <summary> + /// Releases unmanaged and - optionally - managed resources. + /// </summary> + public void Dispose() + { + if (Disposed) return; + Disposed = true; + BitsHandle.Free(); + } + } +} diff --git a/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs new file mode 100644 index 000000000..f32bd4b39 --- /dev/null +++ b/Software/Visual_Studio/Tango.RemoteDesktop/Utils/FastBitmap.cs @@ -0,0 +1,864 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Drawing.Imaging; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace Tango.RemoteDesktop.Utils +{ + /// <summary> + /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images + /// </summary> + public unsafe class FastBitmap : IDisposable + { + /// <summary> + /// Specifies the number of bytes available per pixel of the bitmap object being manipulated + /// </summary> + public const int BytesPerPixel = 4; + + /// <summary> + /// The Bitmap object encapsulated on this FastBitmap + /// </summary> + private readonly Bitmap _bitmap; + + /// <summary> + /// The BitmapData resulted from the lock operation + /// </summary> + private BitmapData _bitmapData; + + /// <summary> + /// The first pixel of the bitmap + /// </summary> + private int* _scan0; + + /// <summary> + /// Gets the width of this FastBitmap object + /// </summary> + public int Width { get; } + + /// <summary> + /// Gets the height of this FastBitmap object + /// </summary> + public int Height { get; } + + /// <summary> + /// Gets the pointer to the first pixel of the bitmap + /// </summary> + public IntPtr Scan0 => _bitmapData.Scan0; + + /// <summary> + /// Gets the stride width (in int32-sized values) of the bitmap + /// </summary> + public int Stride { get; private set; } + + /// <summary> + /// Gets the stride width (in bytes) of the bitmap + /// </summary> + public int StrideInBytes { get; private set; } + + /// <summary> + /// Gets a boolean value that states whether this FastBitmap is currently locked in memory + /// </summary> + public bool Locked { get; private set; } + + /// <summary> + /// Gets an array of 32-bit color pixel values that represent this FastBitmap + /// </summary> + /// <exception cref="Exception">The locking operation required to extract the values off from the underlying bitmap failed</exception> + /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception> + public int[] DataArray + { + get + { + bool unlockAfter = false; + if (!Locked) + { + Lock(); + unlockAfter = true; + } + + // Declare an array to hold the bytes of the bitmap + int bytes = Math.Abs(_bitmapData.Stride) * _bitmap.Height; + int[] argbValues = new int[bytes / BytesPerPixel]; + + // Copy the RGB values into the array + Marshal.Copy(_bitmapData.Scan0, argbValues, 0, bytes / BytesPerPixel); + + if (unlockAfter) + { + Unlock(); + } + + return argbValues; + } + } + + /// <summary> + /// Creates a new instance of the FastBitmap class with a specified Bitmap. + /// The bitmap provided must have a 32bpp depth + /// </summary> + /// <param name="bitmap">The Bitmap object to encapsulate on this FastBitmap object</param> + /// <exception cref="ArgumentException">The bitmap provided does not have a 32bpp pixel format</exception> + public FastBitmap(Bitmap bitmap) + { + if (Image.GetPixelFormatSize(bitmap.PixelFormat) != 32) + { + throw new ArgumentException(@"The provided bitmap must have a 32bpp depth", nameof(bitmap)); + } + + _bitmap = bitmap; + + Width = bitmap.Width; + Height = bitmap.Height; + } + + /// <summary> + /// Disposes of this fast bitmap object and releases any pending resources. + /// The underlying bitmap is not disposes, and is unlocked, if currently locked + /// </summary> + public void Dispose() + { + if (Locked) + { + Unlock(); + } + } + + /// <summary> + /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, + /// an exception is thrown + /// </summary> + /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns> + /// <exception cref="InvalidOperationException">The bitmap is already locked</exception> + /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception> + /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception> + public FastBitmapLocker Lock() + { + return Lock((FastBitmapLockFormat)_bitmap.PixelFormat); + } + + /// <summary> + /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, + /// an exception is thrown. + /// + /// The provided pixel format should be a 32bpp format. + /// </summary> + /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param> + /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns> + /// <exception cref="InvalidOperationException">The bitmap is already locked</exception> + /// <exception cref="Exception">The locking operation in the underlying bitmap failed</exception> + /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception> + public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat) + { + if (Locked) + { + throw new InvalidOperationException("Unlock must be called before a Lock operation"); + } + + return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat); + } + + /// <summary> + /// Locks the bitmap to start the bitmap operations + /// </summary> + /// <param name="lockMode">The lock mode to use on the bitmap</param> + /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param> + /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns> + /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception> + /// <exception cref="InvalidOperationException">The bitmap is already locked outside this fast bitmap</exception> + /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception> + private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat) + { + var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height); + + return Lock(lockMode, rect, pixelFormat); + } + + /// <summary> + /// Locks the bitmap to start the bitmap operations + /// </summary> + /// <param name="lockMode">The lock mode to use on the bitmap</param> + /// <param name="rect">The rectangle to lock</param> + /// <param name="pixelFormat">A pixel format to use when locking the underlying bitmap</param> + /// <returns>A fast bitmap locked struct that will unlock the underlying bitmap after disposal</returns> + /// <exception cref="System.ArgumentException">The provided region is invalid</exception> + /// <exception cref="System.Exception">The locking operation in the underlying bitmap failed</exception> + /// <exception cref="InvalidOperationException">The bitmap region is already locked</exception> + /// <exception cref="ArgumentException"><see cref="!:pixelFormat"/> is not a 32bpp format</exception> + private FastBitmapLocker Lock(ImageLockMode lockMode, Rectangle rect, PixelFormat pixelFormat) + { + // Lock the bitmap's bits + _bitmapData = _bitmap.LockBits(rect, lockMode, pixelFormat); + + _scan0 = (int*)_bitmapData.Scan0; + Stride = _bitmapData.Stride / BytesPerPixel; + StrideInBytes = _bitmapData.Stride; + + Locked = true; + + return new FastBitmapLocker(this); + } + + /// <summary> + /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked + /// beforehand, an exception is thrown + /// </summary> + /// <exception cref="InvalidOperationException">The bitmap is already unlocked</exception> + /// <exception cref="System.Exception">The unlocking operation in the underlying bitmap failed</exception> + public void Unlock() + { + if (!Locked) + { + throw new InvalidOperationException("Lock must be called before an Unlock operation"); + } + + _bitmap.UnlockBits(_bitmapData); + + Locked = false; + } + + /// <summary> + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// </summary> + /// <param name="x">The X coordinate of the pixel to set</param> + /// <param name="y">The Y coordinate of the pixel to set</param> + /// <param name="color">The new color of the pixel to set</param> + /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception> + /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception> + public void SetPixel(int x, int y, Color color) + { + SetPixel(x, y, color.ToArgb()); + } + + /// <summary> + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// </summary> + /// <param name="x">The X coordinate of the pixel to set</param> + /// <param name="y">The Y coordinate of the pixel to set</param> + /// <param name="color">The new color of the pixel to set</param> + /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception> + /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception> + public void SetPixel(int x, int y, int color) + { + SetPixel(x, y, unchecked((uint)color)); + } + + /// <summary> + /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// </summary> + /// <param name="x">The X coordinate of the pixel to set</param> + /// <param name="y">The Y coordinate of the pixel to set</param> + /// <param name="color">The new color of the pixel to set</param> + /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception> + /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception> + public void SetPixel(int x, int y, uint color) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + *(uint*)(_scan0 + x + y * Stride) = color; + } + + /// <summary> + /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands, + /// an exception is thrown + /// </summary> + /// <param name="x">The X coordinate of the pixel to get</param> + /// <param name="y">The Y coordinate of the pixel to get</param> + /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception> + /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception> + public Color GetPixel(int x, int y) + { + return Color.FromArgb(GetPixelInt(x, y)); + } + + /// <summary> + /// Gets the pixel color at the given coordinates as an integer value. If the bitmap + /// was not locked beforehands, an exception is thrown + /// </summary> + /// <param name="x">The X coordinate of the pixel to get</param> + /// <param name="y">The Y coordinate of the pixel to get</param> + /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception> + /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception> + public int GetPixelInt(int x, int y) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + return *(_scan0 + x + y * Stride); + } + + /// <summary> + /// Gets the pixel color at the given coordinates as an unsigned integer value. + /// If the bitmap was not locked beforehands, an exception is thrown + /// </summary> + /// <param name="x">The X coordinate of the pixel to get</param> + /// <param name="y">The Y coordinate of the pixel to get</param> + /// <exception cref="InvalidOperationException">The fast bitmap is not locked</exception> + /// <exception cref="ArgumentOutOfRangeException">The provided coordinates are out of bounds of the bitmap</exception> + public uint GetPixelUInt(int x, int y) + { + if (!Locked) + { + throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); + } + + if (x < 0 || x >= Width) + { + throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); + } + if (y < 0 || y >= Height) + { + throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); + } + + return *((uint*)_scan0 + x + y * Stride); + } + + /// <summary> + /// Copies the contents of the given array of colors into this FastBitmap. + /// Throws an ArgumentException if the count of colors on the array mismatches the pixel count from this FastBitmap + /// </summary> + /// <param name="colors">The array of colors to copy</param> + /// <param name="ignoreZeroes">Whether to ignore zeroes when copying the data</param> + public void CopyFromArray(int[] colors, bool ignoreZeroes = false) + { + if (colors.Length != Width * Height) + { + throw new ArgumentException(@"The number of colors of the given array mismatch the pixel count of the bitmap", nameof(colors)); + } + + // Simply copy the argb values array + // ReSharper disable once InconsistentNaming + int* s0t = _scan0; + + fixed (int* source = colors) + { + // ReSharper disable once InconsistentNaming + int* s0s = source; + + int count = Width * Height; + + if (!ignoreZeroes) + { + // Unfold the loop + const int sizeBlock = 8; + int rem = count % sizeBlock; + + count /= sizeBlock; + + while (count-- > 0) + { + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + *(s0t++) = *(s0s++); + } + + while (rem-- > 0) + { + *(s0t++) = *(s0s++); + } + } + else + { + while (count-- > 0) + { + if (*(s0s) == 0) { s0t++; s0s++; continue; } + *(s0t++) = *(s0s++); + } + } + } + } + + /// <summary> + /// Clears the bitmap with the given color + /// </summary> + /// <param name="color">The color to clear the bitmap with</param> + public void Clear(Color color) + { + Clear(color.ToArgb()); + } + + /// <summary> + /// Clears the bitmap with the given color + /// </summary> + /// <param name="color">The color to clear the bitmap with</param> + public void Clear(int color) + { + bool unlockAfter = false; + if (!Locked) + { + Lock(); + unlockAfter = true; + } + + // Clear all the pixels + int count = Width * Height; + int* curScan = _scan0; + + // Uniform color pixel values can be mem-set straight away + int component = (color & 0xFF); + if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && component == ((color >> 24) & 0xFF)) + { + memset(_scan0, component, (ulong)(Height * Stride * BytesPerPixel)); + } + else + { + // Defines the ammount of assignments that the main while() loop is performing per loop. + // The value specified here must match the number of assignment statements inside that loop + const int assignsPerLoop = 8; + + int rem = count % assignsPerLoop; + count /= assignsPerLoop; + + while (count-- > 0) + { + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + *(curScan++) = color; + } + while (rem-- > 0) + { + *(curScan++) = color; + } + + if (unlockAfter) + { + Unlock(); + } + } + } + + /// <summary> + /// Clears a square region of this image w/ a given color + /// </summary> + /// <param name="region"></param> + /// <param name="color"></param> + public void ClearRegion(Rectangle region, Color color) + { + ClearRegion(region, color.ToArgb()); + } + + /// <summary> + /// Clears a square region of this image w/ a given color + /// </summary> + /// <param name="region"></param> + /// <param name="color"></param> + public void ClearRegion(Rectangle region, int color) + { + var thisReg = new Rectangle(0, 0, Width, Height); + if (!region.IntersectsWith(thisReg)) + return; + + // If the region covers the entire image, use faster Clear(). + if (region == thisReg) + { + Clear(color); + return; + } + + int minX = region.X; + int maxX = region.X + region.Width; + + int minY = region.Y; + int maxY = region.Y + region.Height; + + // Bail out of optimization if there's too few rows to make this worth it + if (maxY - minY < 16) + { + for (int y = minY; y < maxY; y++) + { + for (int x = minX; x < maxX; x++) + { + *(_scan0 + x + y * Stride) = color; + } + } + return; + } + + ulong strideWidth = (ulong)region.Width * BytesPerPixel; + + // Uniform color pixel values can be mem-set straight away + int component = (color & 0xFF); + if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && + component == ((color >> 24) & 0xFF)) + { + for (int y = minY; y < maxY; y++) + { + memset(_scan0 + minX + y * Stride, component, strideWidth); + } + } + else + { + // Prepare a horizontal slice of pixels that will be copied over each horizontal row down. + var row = new int[region.Width]; + + fixed (int* pRow = row) + { + int count = region.Width; + int rem = count % 8; + count /= 8; + int* pSrc = pRow; + while (count-- > 0) + { + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + *pSrc++ = color; + } + while (rem-- > 0) + { + *pSrc++ = color; + } + + var sx = _scan0 + minX; + for (int y = minY; y < maxY; y++) + { + memcpy(sx + y * Stride, pRow, strideWidth); + } + } + } + } + + /// <summary> + /// Copies a region of the source bitmap into this fast bitmap + /// </summary> + /// <param name="source">The source image to copy</param> + /// <param name="srcRect">The region on the source bitmap that will be copied over</param> + /// <param name="destRect">The region on this fast bitmap that will be changed</param> + /// <exception cref="ArgumentException">The provided source bitmap is the same bitmap locked in this FastBitmap</exception> + public void CopyRegion(Bitmap source, Rectangle srcRect, Rectangle destRect) + { + // Throw exception when trying to copy same bitmap over + if (source == _bitmap) + { + throw new ArgumentException(@"Copying regions across the same bitmap is not supported", nameof(source)); + } + + var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); + var destBitmapRect = new Rectangle(0, 0, Width, Height); + + // Check if the rectangle configuration doesn't generate invalid states or does not affect the target image + if (srcRect.Width <= 0 || srcRect.Height <= 0 || destRect.Width <= 0 || destRect.Height <= 0 || + !srcBitmapRect.IntersectsWith(srcRect) || !destRect.IntersectsWith(destBitmapRect)) + return; + + // Find the areas of the first and second bitmaps that are going to be affected + srcBitmapRect = Rectangle.Intersect(srcRect, srcBitmapRect); + + // Clip the source rectangle on top of the destination rectangle in a way that clips out the regions of the original bitmap + // that will not be drawn on the destination bitmap for being out of bounds + srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(srcRect.X, srcRect.Y, destRect.Width, destRect.Height)); + + destBitmapRect = Rectangle.Intersect(destRect, destBitmapRect); + + // Clip the source bitmap region yet again here + srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(-destRect.X + srcRect.X, -destRect.Y + srcRect.Y, Width, Height)); + + // Calculate the rectangle containing the maximum possible area that is supposed to be affected by the copy region operation + int copyWidth = Math.Min(srcBitmapRect.Width, destBitmapRect.Width); + int copyHeight = Math.Min(srcBitmapRect.Height, destBitmapRect.Height); + + if (copyWidth == 0 || copyHeight == 0) + return; + + int srcStartX = srcBitmapRect.Left; + int srcStartY = srcBitmapRect.Top; + + int destStartX = destBitmapRect.Left; + int destStartY = destBitmapRect.Top; + + using (var fastSource = source.FastLock()) + { + ulong strideWidth = (ulong)copyWidth * BytesPerPixel; + + // Perform copies of whole pixel rows + for (int y = 0; y < copyHeight; y++) + { + int destX = destStartX; + int destY = destStartY + y; + + int srcX = srcStartX; + int srcY = srcStartY + y; + + long offsetSrc = (srcX + srcY * fastSource.Stride); + long offsetDest = (destX + destY * Stride); + + memcpy(_scan0 + offsetDest, fastSource._scan0 + offsetSrc, strideWidth); + } + } + } + + /// <summary> + /// Performs a copy operation of the pixels from the Source bitmap to the Target bitmap. + /// If the dimensions or pixel depths of both images don't match, the copy is not performed + /// </summary> + /// <param name="source">The bitmap to copy the pixels from</param> + /// <param name="target">The bitmap to copy the pixels to</param> + /// <returns>Whether the copy proceedure was successful</returns> + /// <exception cref="ArgumentException">The provided source and target bitmaps are the same</exception> + public static bool CopyPixels(Bitmap source, Bitmap target) + { + if (source == target) + { + throw new ArgumentException(@"Copying pixels across the same bitmap is not supported", nameof(source)); + } + + if (source.Width != target.Width || source.Height != target.Height || source.PixelFormat != target.PixelFormat) + return false; + + using (FastBitmap fastSource = source.FastLock(), + fastTarget = target.FastLock()) + { + memcpy(fastTarget.Scan0, fastSource.Scan0, (ulong)(fastSource.Height * fastSource.Stride * BytesPerPixel)); + } + + return true; + } + + /// <summary> + /// Clears the given bitmap with the given color + /// </summary> + /// <param name="bitmap">The bitmap to clear</param> + /// <param name="color">The color to clear the bitmap with</param> + public static void ClearBitmap(Bitmap bitmap, Color color) + { + ClearBitmap(bitmap, color.ToArgb()); + } + + /// <summary> + /// Clears the given bitmap with the given color + /// </summary> + /// <param name="bitmap">The bitmap to clear</param> + /// <param name="color">The color to clear the bitmap with</param> + public static void ClearBitmap(Bitmap bitmap, int color) + { + using (var fb = bitmap.FastLock()) + { + fb.Clear(color); + } + } + + /// <summary> + /// Copies a region of the source bitmap to a target bitmap + /// </summary> + /// <param name="source">The source image to copy</param> + /// <param name="target">The target image to be altered</param> + /// <param name="srcRect">The region on the source bitmap that will be copied over</param> + /// <param name="destRect">The region on the target bitmap that will be changed</param> + /// <exception cref="ArgumentException">The provided source and target bitmaps are the same bitmap</exception> + public static void CopyRegion(Bitmap source, Bitmap target, Rectangle srcRect, Rectangle destRect) + { + var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); + var destBitmapRect = new Rectangle(0, 0, target.Width, target.Height); + + // If the copy operation results in an entire copy, use CopyPixels instead + if (srcBitmapRect == srcRect && destBitmapRect == destRect && srcBitmapRect == destBitmapRect) + { + CopyPixels(source, target); + return; + } + + using (var fastTarget = target.FastLock()) + { + fastTarget.CopyRegion(source, srcRect, destRect); + } + } + + /// <summary> + /// Returns a bitmap that is a slice of the original provided 32bpp Bitmap. + /// The region must have a width and a height > 0, and must lie inside the source bitmap's area + /// </summary> + /// <param name="source">The source bitmap to slice</param> + /// <param name="region">The region of the source bitmap to slice</param> + /// <returns>A Bitmap that represents the rectangle region slice of the source bitmap</returns> + /// <exception cref="ArgumentException">The provided bimap is not 32bpp</exception> + /// <exception cref="ArgumentException">The provided region is invalid</exception> + public static Bitmap SliceBitmap(Bitmap source, Rectangle region) + { + if (region.Width <= 0 || region.Height <= 0) + { + throw new ArgumentException(@"The provided region must have a width and a height > 0", nameof(region)); + } + + var sliceRectangle = Rectangle.Intersect(new Rectangle(Point.Empty, source.Size), region); + + if (sliceRectangle.IsEmpty) + { + throw new ArgumentException(@"The provided region must not lie outside of the bitmap's region completely", nameof(region)); + } + + var slicedBitmap = new Bitmap(sliceRectangle.Width, sliceRectangle.Height); + CopyRegion(source, slicedBitmap, sliceRectangle, new Rectangle(0, 0, sliceRectangle.Width, sliceRectangle.Height)); + + return slicedBitmap; + } + +#if NETSTANDARD + public static void memcpy(IntPtr dest, IntPtr src, ulong count) + { + Buffer.MemoryCopy(src.ToPointer(), dest.ToPointer(), count, count); + } + + public static void memcpy(void* dest, void* src, ulong count) + { + Buffer.MemoryCopy(src, dest, count, count); + } + + public static void memset(void* dest, int value, ulong count) + { + Unsafe.InitBlock(dest, (byte)value, (uint)count); + } +#else + /// <summary> + /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed + /// </summary> + [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count); + + /// <summary> + /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed + /// </summary> + [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memcpy(void* dest, void* src, ulong count); + + /// <summary> + /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed + /// </summary> + [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] + public static extern IntPtr memset(void* dest, int value, ulong count); +#endif + + /// <summary> + /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls + /// </summary> + public struct FastBitmapLocker : IDisposable + { + /// <summary> + /// Gets the fast bitmap instance attached to this locker + /// </summary> + public FastBitmap FastBitmap { get; } + + /// <summary> + /// Initializes a new instance of the FastBitmapLocker struct with an initial fast bitmap object. + /// The fast bitmap object passed will be unlocked after calling Dispose() on this struct + /// </summary> + /// <param name="fastBitmap">A fast bitmap to attach to this locker which will be released after a call to Dispose</param> + public FastBitmapLocker(FastBitmap fastBitmap) + { + FastBitmap = fastBitmap; + } + + /// <summary> + /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap + /// </summary> + public void Dispose() + { + if (FastBitmap.Locked) + FastBitmap.Unlock(); + } + } + } + + /// <summary> + /// Describes a pixel format to use when locking a bitmap using <see cref="FastBitmap"/>. + /// </summary> + public enum FastBitmapLockFormat + { + /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the red, green, and blue components. The remaining 8 bits are not used.</summary> + Format32bppRgb = 139273, + /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. The red, green, and blue components are premultiplied, according to the alpha component.</summary> + Format32bppPArgb = 925707, + /// <summary>Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components.</summary> + Format32bppArgb = 2498570, + } + + /// <summary> + /// Static class that contains fast bitmap extension methdos for the Bitmap class + /// </summary> + public static class FastBitmapExtensions + { + /// <summary> + /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels + /// </summary> + /// <param name="bitmap">The bitmap to lock</param> + /// <returns>A locked FastBitmap</returns> + public static FastBitmap FastLock(this Bitmap bitmap) + { + var fast = new FastBitmap(bitmap); + fast.Lock(); + + return fast; + } + + /// <summary> + /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels + /// </summary> + /// <param name="bitmap">The bitmap to lock</param> + /// <param name="lockFormat">The underlying pixel format to use when locking the bitmap</param> + /// <returns>A locked FastBitmap</returns> + public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat) + { + var fast = new FastBitmap(bitmap); + fast.Lock(lockFormat); + + return fast; + } + + /// <summary> + /// Returns a deep clone of this Bitmap object, with all the data copied over. + /// After a deep clone, the new bitmap is completely independent from the original + /// </summary> + /// <param name="bitmap">The bitmap to clone</param> + /// <returns>A deep clone of this Bitmap object, with all the data copied over</returns> + public static Bitmap DeepClone(this Bitmap bitmap) + { + return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); + } + } +} |
