/* FastBitmapLib The MIT License (MIT) Copyright (C) 2013 Luiz Fernando Silva This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. The full license may be found on the License.txt file attached to the base directory of this project. */ using System; using System.Drawing; using System.Drawing.Imaging; using System.Runtime.InteropServices; namespace POSV.Printer { /// /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images /// public unsafe class FastBitmap : IDisposable { /// /// Specifies the number of bytes available per pixel of the bitmap object being manipulated /// private const int BytesPerPixel = 4; /// /// The Bitmap object encapsulated on this FastBitmap /// private readonly Bitmap _bitmap; /// /// The BitmapData resulted from the lock operation /// private BitmapData _bitmapData; /// /// The first pixel of the bitmap /// private int *_scan0; /// /// Gets the width of this FastBitmap object /// public int Width { get; } /// /// Gets the height of this FastBitmap object /// public int Height { get; } /// /// Gets the pointer to the first pixel of the bitmap /// public IntPtr Scan0 => _bitmapData.Scan0; /// /// Gets the stride width of the bitmap /// public int Stride { get; private set; } /// /// Gets a boolean value that states whether this FastBitmap is currently locked in memory /// public bool Locked { get; private set; } /// /// Gets an array of 32-bit color pixel values that represent this FastBitmap /// /// The locking operation required to extract the values off from the underlying bitmap failed /// The bitmap is already locked outside this fast bitmap 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; } } /// /// Creates a new instance of the FastBitmap class with a specified Bitmap. /// The bitmap provided must have a 32bpp depth /// /// The Bitmap object to encapsulate on this FastBitmap object /// The bitmap provided does not have a 32bpp pixel format 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; } /// /// Disposes of this fast bitmap object and releases any pending resources. /// The underlying bitmap is not disposes, and is unlocked, if currently locked /// public void Dispose() { if (Locked) { Unlock(); } } /// /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, /// an exception is thrown /// /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal /// The bitmap is already locked /// The locking operation in the underlying bitmap failed /// The bitmap is already locked outside this fast bitmap public FastBitmapLocker Lock() { if (Locked) { throw new InvalidOperationException("Unlock must be called before a Lock operation"); } return Lock(ImageLockMode.ReadWrite); } /// /// Locks the bitmap to start the bitmap operations /// /// The lock mode to use on the bitmap /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal /// The locking operation in the underlying bitmap failed /// The bitmap is already locked outside this fast bitmap private FastBitmapLocker Lock(ImageLockMode lockMode) { var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height); return Lock(lockMode, rect); } /// /// Locks the bitmap to start the bitmap operations /// /// The lock mode to use on the bitmap /// The rectangle to lock /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal /// The provided region is invalid /// The locking operation in the underlying bitmap failed /// The bitmap region is already locked private FastBitmapLocker Lock(ImageLockMode lockMode, Rectangle rect) { // Lock the bitmap's bits _bitmapData = _bitmap.LockBits(rect, lockMode, _bitmap.PixelFormat); _scan0 = (int*)_bitmapData.Scan0; Stride = _bitmapData.Stride / BytesPerPixel; Locked = true; return new FastBitmapLocker(this); } /// /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked /// beforehand, an exception is thrown /// /// The bitmap is already unlocked /// The unlocking operation in the underlying bitmap failed public void Unlock() { if (!Locked) { throw new InvalidOperationException("Lock must be called before an Unlock operation"); } _bitmap.UnlockBits(_bitmapData); Locked = false; } /// /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, /// an exception is thrown /// /// The X coordinate of the pixel to set /// The Y coordinate of the pixel to set /// The new color of the pixel to set /// The fast bitmap is not locked /// The provided coordinates are out of bounds of the bitmap public void SetPixel(int x, int y, Color color) { SetPixel(x, y, color.ToArgb()); } /// /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, /// an exception is thrown /// /// The X coordinate of the pixel to set /// The Y coordinate of the pixel to set /// The new color of the pixel to set /// The fast bitmap is not locked /// The provided coordinates are out of bounds of the bitmap public void SetPixel(int x, int y, int color) { SetPixel(x, y, unchecked((uint)color)); } /// /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehands, /// an exception is thrown /// /// The X coordinate of the pixel to set /// The Y coordinate of the pixel to set /// The new color of the pixel to set /// The fast bitmap is not locked /// The provided coordinates are out of bounds of the bitmap 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; } /// /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehands, /// an exception is thrown /// /// The X coordinate of the pixel to get /// The Y coordinate of the pixel to get /// The fast bitmap is not locked /// The provided coordinates are out of bounds of the bitmap public Color GetPixel(int x, int y) { return Color.FromArgb(GetPixelInt(x, y)); } /// /// Gets the pixel color at the given coordinates as an integer value. If the bitmap /// was not locked beforehands, an exception is thrown /// /// The X coordinate of the pixel to get /// The Y coordinate of the pixel to get /// The fast bitmap is not locked /// The provided coordinates are out of bounds of the bitmap 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); } /// /// Gets the pixel color at the given coordinates as an unsigned integer value. /// If the bitmap was not locked beforehands, an exception is thrown /// /// The X coordinate of the pixel to get /// The Y coordinate of the pixel to get /// The fast bitmap is not locked /// The provided coordinates are out of bounds of the bitmap 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); } /// /// 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 /// /// The array of colors to copy /// Whether to ignore zeroes when copying the data 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++); } } } } /// /// Clears the bitmap with the given color /// /// The color to clear the bitmap with public void Clear(Color color) { Clear(color.ToArgb()); } /// /// Clears the bitmap with the given color /// /// The color to clear the bitmap with public void Clear(int color) { bool unlockAfter = false; if(!Locked) { Lock(); unlockAfter = true; } // Clear all the pixels int count = Width * Height; int* curScan = _scan0; // 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(); } } /// /// Copies a region of the source bitmap into this fast bitmap /// /// The source image to copy /// The region on the source bitmap that will be copied over /// The region on this fast bitmap that will be changed /// The provided source bitmap is the same bitmap locked in this FastBitmap 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); } } } /// /// 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 /// /// The bitmap to copy the pixels from /// The bitmap to copy the pixels to /// Whether the copy proceedure was successful /// The provided source and target bitmaps are the same 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; } /// /// Clears the given bitmap with the given color /// /// The bitmap to clear /// The color to clear the bitmap with public static void ClearBitmap(Bitmap bitmap, Color color) { ClearBitmap(bitmap, color.ToArgb()); } /// /// Clears the given bitmap with the given color /// /// The bitmap to clear /// The color to clear the bitmap with public static void ClearBitmap(Bitmap bitmap, int color) { using (var fb = bitmap.FastLock()) { fb.Clear(color); } } /// /// Copies a region of the source bitmap to a target bitmap /// /// The source image to copy /// The target image to be altered /// The region on the source bitmap that will be copied over /// The region on the target bitmap that will be changed /// The provided source and target bitmaps are the same bitmap 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); } } /// /// 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 /// /// The source bitmap to slice /// The region of the source bitmap to slice /// A Bitmap that represents the rectangle region slice of the source bitmap /// The provided bimap is not 32bpp /// The provided region is invalid 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; } // .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count); // .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] public static extern IntPtr memcpy(void* dest, void* src, ulong count); /// /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls /// public struct FastBitmapLocker : IDisposable { /// /// The fast bitmap instance attached to this locker /// private readonly FastBitmap _fastBitmap; /// /// Gets the fast bitmap instance attached to this locker /// public FastBitmap FastBitmap => _fastBitmap; /// /// 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 /// /// A fast bitmap to attach to this locker which will be released after a call to Dispose public FastBitmapLocker(FastBitmap fastBitmap) { _fastBitmap = fastBitmap; } /// /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap /// public void Dispose() { if(_fastBitmap.Locked) _fastBitmap.Unlock(); } } } /// /// Static class that contains fast bitmap extension methdos for the Bitmap class /// public static class FastBitmapExtensions { /// /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels /// /// The bitmap to lock /// A locked FastBitmap public static FastBitmap FastLock(this Bitmap bitmap) { var fast = new FastBitmap(bitmap); fast.Lock(); return fast; } /// /// 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 /// /// The bitmap to clone /// A deep clone of this Bitmap object, with all the data copied over public static Bitmap DeepClone(this Bitmap bitmap) { return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); } } }