├── .gitignore ├── FastBitmap.sln ├── FastBitmap ├── FastBitmap.cs ├── FastBitmapLib.csproj └── Properties │ └── AssemblyInfo.cs ├── FastBitmapTests ├── BitmapSnapshot.cs ├── BitmapSnapshotTesting.cs ├── FastBitmapTests.cs ├── FastBitmapTests.csproj ├── Properties │ └── AssemblyInfo.cs ├── Snapshot │ └── Files │ │ └── FastBitmapTests.FastBitmapTests │ │ ├── TestClippingCopyRegion.png │ │ ├── TestComplexCopyRegion.png │ │ ├── TestInvalidCopyRegion.png │ │ ├── TestOutOfBoundsCopyRegion.png │ │ ├── TestSimpleCopyRegion.png │ │ └── TestSlicedDestinationCopyRegion.png └── packages.config ├── LICENSE ├── README.md └── appveyor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | 185 | .vs 186 | 187 | .scripts 188 | -------------------------------------------------------------------------------- /FastBitmap.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29209.152 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastBitmapLib", "FastBitmap\FastBitmapLib.csproj", "{4BCC21C7-7AA5-4C80-BD42-AAB57D5284C1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastBitmapTests", "FastBitmapTests\FastBitmapTests.csproj", "{F9FECF83-7112-4F21-A9F8-0E62E98E38AF}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4BCC21C7-7AA5-4C80-BD42-AAB57D5284C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {4BCC21C7-7AA5-4C80-BD42-AAB57D5284C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {4BCC21C7-7AA5-4C80-BD42-AAB57D5284C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {4BCC21C7-7AA5-4C80-BD42-AAB57D5284C1}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {F9FECF83-7112-4F21-A9F8-0E62E98E38AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {F9FECF83-7112-4F21-A9F8-0E62E98E38AF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {F9FECF83-7112-4F21-A9F8-0E62E98E38AF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {F9FECF83-7112-4F21-A9F8-0E62E98E38AF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D58F0FFF-06BD-4DAE-8C85-C24AF773B632} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /FastBitmap/FastBitmap.cs: -------------------------------------------------------------------------------- 1 | /* 2 | FastBitmapLib 3 | 4 | The MIT License (MIT) 5 | 6 | Copyright (c) 2014 Luiz Fernando 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | using System; 28 | using System.Drawing; 29 | using System.Drawing.Imaging; 30 | using System.Runtime.CompilerServices; 31 | using System.Runtime.InteropServices; 32 | 33 | namespace FastBitmapLib 34 | { 35 | /// 36 | /// Encapsulates a Bitmap for fast bitmap pixel operations using 32bpp images 37 | /// 38 | public unsafe class FastBitmap : IDisposable 39 | { 40 | /// 41 | /// Specifies the number of bytes available per pixel of the bitmap object being manipulated 42 | /// 43 | public const int BytesPerPixel = 4; 44 | 45 | /// 46 | /// The Bitmap object encapsulated on this FastBitmap 47 | /// 48 | private readonly Bitmap _bitmap; 49 | 50 | /// 51 | /// The BitmapData resulted from the lock operation 52 | /// 53 | private BitmapData _bitmapData; 54 | 55 | /// 56 | /// The first pixel of the bitmap 57 | /// 58 | private int* _scan0; 59 | 60 | /// 61 | /// Gets the width of this FastBitmap object 62 | /// 63 | public int Width { get; } 64 | 65 | /// 66 | /// Gets the height of this FastBitmap object 67 | /// 68 | public int Height { get; } 69 | 70 | /// 71 | /// Gets the pointer to the first pixel of the bitmap 72 | /// 73 | public IntPtr Scan0 => _bitmapData.Scan0; 74 | 75 | /// 76 | /// Gets the stride width (in int32-sized values) of the bitmap 77 | /// 78 | public int Stride { get; private set; } 79 | 80 | /// 81 | /// Gets the stride width (in bytes) of the bitmap 82 | /// 83 | public int StrideInBytes { get; private set; } 84 | 85 | /// 86 | /// Gets a boolean value that states whether this FastBitmap is currently locked in memory 87 | /// 88 | public bool Locked { get; private set; } 89 | 90 | /// 91 | /// Gets an array of 32-bit color pixel values that represent this FastBitmap. 92 | /// 93 | /// The locking operation required to extract the values off from the underlying bitmap failed 94 | /// The bitmap is already locked outside this fast bitmap 95 | [Obsolete("DataArray property is deprecated. Please use GetDataAsArray() method instead.")] 96 | public int[] DataArray 97 | { 98 | get 99 | { 100 | bool unlockAfter = false; 101 | if (!Locked) 102 | { 103 | Lock(); 104 | unlockAfter = true; 105 | } 106 | 107 | // Declare an array to hold the bytes of the bitmap 108 | int bytes = Math.Abs(_bitmapData.Stride) * _bitmap.Height; 109 | int[] argbValues = new int[bytes / BytesPerPixel]; 110 | 111 | // Copy the RGB values into the array 112 | Marshal.Copy(_bitmapData.Scan0, argbValues, 0, bytes / BytesPerPixel); 113 | 114 | if (unlockAfter) 115 | { 116 | Unlock(); 117 | } 118 | 119 | return argbValues; 120 | } 121 | } 122 | 123 | /// 124 | /// Creates a new instance of the FastBitmap class with a specified Bitmap. 125 | /// The bitmap provided must have a 32bpp depth 126 | /// 127 | /// The Bitmap object to encapsulate on this FastBitmap object 128 | /// The bitmap provided does not have a 32bpp pixel format 129 | public FastBitmap(Bitmap bitmap) 130 | { 131 | if (Image.GetPixelFormatSize(bitmap.PixelFormat) != 32) 132 | { 133 | throw new ArgumentException(@"The provided bitmap must have a 32bpp depth", nameof(bitmap)); 134 | } 135 | 136 | _bitmap = bitmap; 137 | 138 | Width = bitmap.Width; 139 | Height = bitmap.Height; 140 | } 141 | 142 | /// 143 | /// Disposes of this fast bitmap object and releases any pending resources. 144 | /// The underlying bitmap is not disposes, and is unlocked, if currently locked 145 | /// 146 | public void Dispose() 147 | { 148 | if (Locked) 149 | { 150 | Unlock(); 151 | } 152 | } 153 | 154 | /// 155 | /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, 156 | /// an exception is thrown 157 | /// 158 | /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal 159 | /// The bitmap is already locked 160 | /// The locking operation in the underlying bitmap failed 161 | /// The bitmap is already locked outside this fast bitmap 162 | public FastBitmapLocker Lock() 163 | { 164 | return Lock((FastBitmapLockFormat)_bitmap.PixelFormat); 165 | } 166 | 167 | /// 168 | /// Locks the bitmap to start the bitmap operations. If the bitmap is already locked, 169 | /// an exception is thrown. 170 | /// 171 | /// The provided pixel format should be a 32bpp format. 172 | /// 173 | /// A pixel format to use when locking the underlying bitmap 174 | /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal 175 | /// The bitmap is already locked 176 | /// The locking operation in the underlying bitmap failed 177 | /// The bitmap is already locked outside this fast bitmap 178 | public FastBitmapLocker Lock(FastBitmapLockFormat pixelFormat) 179 | { 180 | if (Locked) 181 | { 182 | throw new InvalidOperationException("Unlock must be called before a Lock operation"); 183 | } 184 | 185 | return Lock(ImageLockMode.ReadWrite, (PixelFormat)pixelFormat); 186 | } 187 | 188 | /// 189 | /// Locks the bitmap to start the bitmap operations 190 | /// 191 | /// The lock mode to use on the bitmap 192 | /// A pixel format to use when locking the underlying bitmap 193 | /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal 194 | /// The locking operation in the underlying bitmap failed 195 | /// The bitmap is already locked outside this fast bitmap 196 | /// is not a 32bpp format 197 | private FastBitmapLocker Lock(ImageLockMode lockMode, PixelFormat pixelFormat) 198 | { 199 | var rect = new Rectangle(0, 0, _bitmap.Width, _bitmap.Height); 200 | 201 | return Lock(lockMode, rect, pixelFormat); 202 | } 203 | 204 | /// 205 | /// Locks the bitmap to start the bitmap operations 206 | /// 207 | /// The lock mode to use on the bitmap 208 | /// The rectangle to lock 209 | /// A pixel format to use when locking the underlying bitmap 210 | /// A fast bitmap locked struct that will unlock the underlying bitmap after disposal 211 | /// The provided region is invalid 212 | /// The locking operation in the underlying bitmap failed 213 | /// The bitmap region is already locked 214 | /// is not a 32bpp format 215 | private FastBitmapLocker Lock(ImageLockMode lockMode, Rectangle rect, PixelFormat pixelFormat) 216 | { 217 | // Lock the bitmap's bits 218 | _bitmapData = _bitmap.LockBits(rect, lockMode, pixelFormat); 219 | 220 | _scan0 = (int*)_bitmapData.Scan0; 221 | Stride = _bitmapData.Stride / BytesPerPixel; 222 | StrideInBytes = _bitmapData.Stride; 223 | 224 | Locked = true; 225 | 226 | return new FastBitmapLocker(this); 227 | } 228 | 229 | /// 230 | /// Unlocks the bitmap and applies the changes made to it. If the bitmap was not locked 231 | /// beforehand, an exception is thrown 232 | /// 233 | /// The bitmap is already unlocked 234 | /// The unlocking operation in the underlying bitmap failed 235 | public void Unlock() 236 | { 237 | if (!Locked) 238 | { 239 | throw new InvalidOperationException("Lock must be called before an Unlock operation"); 240 | } 241 | 242 | _bitmap.UnlockBits(_bitmapData); 243 | 244 | Locked = false; 245 | } 246 | 247 | /// 248 | /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehand, 249 | /// an exception is thrown 250 | /// 251 | /// The X coordinate of the pixel to set 252 | /// The Y coordinate of the pixel to set 253 | /// The new color of the pixel to set 254 | /// The fast bitmap is not locked 255 | /// The provided coordinates are out of bounds of the bitmap 256 | public void SetPixel(int x, int y, Color color) 257 | { 258 | SetPixel(x, y, color.ToArgb()); 259 | } 260 | 261 | /// 262 | /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehand, 263 | /// an exception is thrown 264 | /// 265 | /// The X coordinate of the pixel to set 266 | /// The Y coordinate of the pixel to set 267 | /// The new color of the pixel to set 268 | /// The fast bitmap is not locked 269 | /// The provided coordinates are out of bounds of the bitmap 270 | public void SetPixel(int x, int y, int color) 271 | { 272 | SetPixel(x, y, unchecked((uint)color)); 273 | } 274 | 275 | /// 276 | /// Sets the pixel color at the given coordinates. If the bitmap was not locked beforehand, 277 | /// an exception is thrown 278 | /// 279 | /// The X coordinate of the pixel to set 280 | /// The Y coordinate of the pixel to set 281 | /// The new color of the pixel to set 282 | /// The fast bitmap is not locked 283 | /// The provided coordinates are out of bounds of the bitmap 284 | public void SetPixel(int x, int y, uint color) 285 | { 286 | if (!Locked) 287 | { 288 | throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); 289 | } 290 | 291 | if (x < 0 || x >= Width) 292 | { 293 | throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); 294 | } 295 | if (y < 0 || y >= Height) 296 | { 297 | throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); 298 | } 299 | 300 | *(uint*)(_scan0 + x + y * Stride) = color; 301 | } 302 | 303 | /// 304 | /// Sets the pixel color at the given index. If the bitmap was not locked beforehand, 305 | /// an exception is thrown 306 | /// 307 | /// An index into the underlying bitmap data, between 0 <= index < Height * Stride 308 | /// The new color of the pixel to set 309 | /// The fast bitmap is not locked 310 | /// The provided index is out of bounds of the bitmap 311 | public void SetPixel(int index, uint color) 312 | { 313 | if (!Locked) 314 | { 315 | throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); 316 | } 317 | 318 | if (index < 0 || index >= Height * Stride) 319 | { 320 | throw new ArgumentOutOfRangeException(nameof(index), $@"The index must be >= 0 and < {nameof(Height)} * {nameof(Stride)}"); 321 | } 322 | 323 | *(uint*)(_scan0 + index) = color; 324 | } 325 | 326 | /// 327 | /// Gets the pixel color at the given coordinates. If the bitmap was not locked beforehand, 328 | /// an exception is thrown 329 | /// 330 | /// The X coordinate of the pixel to get 331 | /// The Y coordinate of the pixel to get 332 | /// The fast bitmap is not locked 333 | /// The provided coordinates are out of bounds of the bitmap 334 | public Color GetPixel(int x, int y) 335 | { 336 | return Color.FromArgb(GetPixelInt(x, y)); 337 | } 338 | 339 | /// 340 | /// Gets the pixel color at the given coordinates as an integer value. If the bitmap 341 | /// was not locked beforehand, an exception is thrown 342 | /// 343 | /// The X coordinate of the pixel to get 344 | /// The Y coordinate of the pixel to get 345 | /// The fast bitmap is not locked 346 | /// The provided coordinates are out of bounds of the bitmap 347 | public int GetPixelInt(int x, int y) 348 | { 349 | if (!Locked) 350 | { 351 | throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); 352 | } 353 | 354 | if (x < 0 || x >= Width) 355 | { 356 | throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); 357 | } 358 | if (y < 0 || y >= Height) 359 | { 360 | throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); 361 | } 362 | 363 | return *(_scan0 + x + y * Stride); 364 | } 365 | 366 | /// 367 | /// Gets the pixel color at the given coordinates as an unsigned integer value. 368 | /// If the bitmap was not locked beforehand, an exception is thrown 369 | /// 370 | /// The X coordinate of the pixel to get 371 | /// The Y coordinate of the pixel to get 372 | /// The fast bitmap is not locked 373 | /// The provided coordinates are out of bounds of the bitmap 374 | public uint GetPixelUInt(int x, int y) 375 | { 376 | if (!Locked) 377 | { 378 | throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); 379 | } 380 | 381 | if (x < 0 || x >= Width) 382 | { 383 | throw new ArgumentOutOfRangeException(nameof(x), @"The X component must be >= 0 and < width"); 384 | } 385 | if (y < 0 || y >= Height) 386 | { 387 | throw new ArgumentOutOfRangeException(nameof(y), @"The Y component must be >= 0 and < height"); 388 | } 389 | 390 | return *((uint*)_scan0 + x + y * Stride); 391 | } 392 | 393 | /// 394 | /// Gets the pixel color at a given absolute index on the underlying bitmap data as 395 | /// an unsigned integer value. 396 | /// 397 | /// If the bitmap was not locked beforehand, an exception is thrown 398 | /// 399 | /// An index into the underlying bitmap data, between 0 <= index < Height * Stride 400 | /// The fast bitmap is not locked 401 | /// The provided index is out of bounds of the bitmap data 402 | public uint GetPixelUInt(int index) 403 | { 404 | if (!Locked) 405 | { 406 | throw new InvalidOperationException("The FastBitmap must be locked before any pixel operations are made"); 407 | } 408 | 409 | if (index < 0 || index >= Height * Stride) 410 | { 411 | throw new ArgumentOutOfRangeException(nameof(index), $@"The index must be >= 0 and < {nameof(Height)} * {nameof(Stride)}"); 412 | } 413 | 414 | return *((uint*)_scan0 + index); 415 | } 416 | 417 | /// 418 | /// Copies the contents of the given array of colors into this FastBitmap. 419 | /// Throws an ArgumentException if the count of colors on the array mismatches the pixel count from this FastBitmap 420 | /// 421 | /// The array of colors to copy 422 | /// Whether to ignore zeroes when copying the data 423 | public void CopyFromArray(int[] colors, bool ignoreZeroes = false) 424 | { 425 | if (colors.Length != Width * Height) 426 | { 427 | throw new ArgumentException(@"The number of colors of the given array mismatch the pixel count of the bitmap", nameof(colors)); 428 | } 429 | 430 | // Simply copy the argb values array 431 | // ReSharper disable once InconsistentNaming 432 | int* s0t = _scan0; 433 | 434 | fixed (int* source = colors) 435 | { 436 | // ReSharper disable once InconsistentNaming 437 | int* s0s = source; 438 | 439 | int count = Width * Height; 440 | 441 | if (!ignoreZeroes) 442 | { 443 | // Unfold the loop 444 | const int sizeBlock = 8; 445 | int rem = count % sizeBlock; 446 | 447 | count /= sizeBlock; 448 | 449 | while (count-- > 0) 450 | { 451 | *(s0t++) = *(s0s++); 452 | *(s0t++) = *(s0s++); 453 | *(s0t++) = *(s0s++); 454 | *(s0t++) = *(s0s++); 455 | 456 | *(s0t++) = *(s0s++); 457 | *(s0t++) = *(s0s++); 458 | *(s0t++) = *(s0s++); 459 | *(s0t++) = *(s0s++); 460 | } 461 | 462 | while (rem-- > 0) 463 | { 464 | *(s0t++) = *(s0s++); 465 | } 466 | } 467 | else 468 | { 469 | while (count-- > 0) 470 | { 471 | if (*(s0s) == 0) { s0t++; s0s++; continue; } 472 | *(s0t++) = *(s0s++); 473 | } 474 | } 475 | } 476 | } 477 | 478 | /// 479 | /// Gets an array of 32-bit color pixel values that represent this FastBitmap. 480 | /// 481 | /// The locking operation required to extract the values off from the underlying bitmap failed 482 | /// The bitmap is already locked outside this fast bitmap 483 | public int[] GetDataAsArray() 484 | { 485 | bool unlockAfter = false; 486 | if (!Locked) 487 | { 488 | Lock(); 489 | unlockAfter = true; 490 | } 491 | 492 | // Declare an array to hold the bytes of the bitmap 493 | int bytes = Math.Abs(_bitmapData.Stride) * _bitmap.Height; 494 | int[] argbValues = new int[bytes / BytesPerPixel]; 495 | 496 | // Copy the RGB values into the array 497 | Marshal.Copy(_bitmapData.Scan0, argbValues, 0, bytes / BytesPerPixel); 498 | 499 | if (unlockAfter) 500 | { 501 | Unlock(); 502 | } 503 | 504 | return argbValues; 505 | } 506 | 507 | /// 508 | /// Clears the bitmap with the given color 509 | /// 510 | /// The color to clear the bitmap with 511 | public void Clear(Color color) 512 | { 513 | Clear(color.ToArgb()); 514 | } 515 | 516 | /// 517 | /// Clears the bitmap with the given color 518 | /// 519 | /// The color to clear the bitmap with 520 | public void Clear(int color) 521 | { 522 | bool unlockAfter = false; 523 | if (!Locked) 524 | { 525 | Lock(); 526 | unlockAfter = true; 527 | } 528 | 529 | // Clear all the pixels 530 | int count = Width * Height; 531 | int* curScan = _scan0; 532 | 533 | // Uniform color pixel values can be mem-set straight away 534 | int component = (color & 0xFF); 535 | if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && component == ((color >> 24) & 0xFF)) 536 | { 537 | memset(_scan0, component, (ulong)(Height * Stride * BytesPerPixel)); 538 | } 539 | else 540 | { 541 | // Defines the ammount of assignments that the main while() loop is performing per loop. 542 | // The value specified here must match the number of assignment statements inside that loop 543 | const int assignsPerLoop = 8; 544 | 545 | int rem = count % assignsPerLoop; 546 | count /= assignsPerLoop; 547 | 548 | while (count-- > 0) 549 | { 550 | *(curScan++) = color; 551 | *(curScan++) = color; 552 | *(curScan++) = color; 553 | *(curScan++) = color; 554 | 555 | *(curScan++) = color; 556 | *(curScan++) = color; 557 | *(curScan++) = color; 558 | *(curScan++) = color; 559 | } 560 | while (rem-- > 0) 561 | { 562 | *(curScan++) = color; 563 | } 564 | 565 | if (unlockAfter) 566 | { 567 | Unlock(); 568 | } 569 | } 570 | } 571 | 572 | /// 573 | /// Clears a square region of this image w/ a given color 574 | /// 575 | /// 576 | /// 577 | public void ClearRegion(Rectangle region, Color color) 578 | { 579 | ClearRegion(region, color.ToArgb()); 580 | } 581 | 582 | /// 583 | /// Clears a square region of this image w/ a given color 584 | /// 585 | /// 586 | /// 587 | public void ClearRegion(Rectangle region, int color) 588 | { 589 | var thisReg = new Rectangle(0, 0, Width, Height); 590 | if (!region.IntersectsWith(thisReg)) 591 | return; 592 | 593 | // If the region covers the entire image, use faster Clear(). 594 | if (region == thisReg) 595 | { 596 | Clear(color); 597 | return; 598 | } 599 | 600 | int minX = region.X; 601 | int maxX = region.X + region.Width; 602 | 603 | int minY = region.Y; 604 | int maxY = region.Y + region.Height; 605 | 606 | // Bail out of optimization if there's too few rows to make this worth it 607 | if (maxY - minY < 16) 608 | { 609 | for (int y = minY; y < maxY; y++) 610 | { 611 | for (int x = minX; x < maxX; x++) 612 | { 613 | *(_scan0 + x + y * Stride) = color; 614 | } 615 | } 616 | return; 617 | } 618 | 619 | ulong strideWidth = (ulong)region.Width * BytesPerPixel; 620 | 621 | // Uniform color pixel values can be mem-set straight away 622 | int component = (color & 0xFF); 623 | if (component == ((color >> 8) & 0xFF) && component == ((color >> 16) & 0xFF) && 624 | component == ((color >> 24) & 0xFF)) 625 | { 626 | for (int y = minY; y < maxY; y++) 627 | { 628 | memset(_scan0 + minX + y * Stride, component, strideWidth); 629 | } 630 | } 631 | else 632 | { 633 | // Prepare a horizontal slice of pixels that will be copied over each horizontal row down. 634 | var row = new int[region.Width]; 635 | 636 | fixed (int* pRow = row) 637 | { 638 | int count = region.Width; 639 | int rem = count % 8; 640 | count /= 8; 641 | int* pSrc = pRow; 642 | while (count-- > 0) 643 | { 644 | *pSrc++ = color; 645 | *pSrc++ = color; 646 | *pSrc++ = color; 647 | *pSrc++ = color; 648 | 649 | *pSrc++ = color; 650 | *pSrc++ = color; 651 | *pSrc++ = color; 652 | *pSrc++ = color; 653 | } 654 | while (rem-- > 0) 655 | { 656 | *pSrc++ = color; 657 | } 658 | 659 | var sx = _scan0 + minX; 660 | for (int y = minY; y < maxY; y++) 661 | { 662 | memcpy(sx + y * Stride, pRow, strideWidth); 663 | } 664 | } 665 | } 666 | } 667 | 668 | /// 669 | /// Copies a region of the source bitmap into this fast bitmap 670 | /// 671 | /// The source image to copy 672 | /// The region on the source bitmap that will be copied over 673 | /// The region on this fast bitmap that will be changed 674 | /// The provided source bitmap is the same bitmap locked in this FastBitmap 675 | public void CopyRegion(Bitmap source, Rectangle srcRect, Rectangle destRect) 676 | { 677 | // Throw exception when trying to copy same bitmap over 678 | if (source == _bitmap) 679 | { 680 | throw new ArgumentException(@"Copying regions across the same bitmap is not supported", nameof(source)); 681 | } 682 | 683 | var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); 684 | var destBitmapRect = new Rectangle(0, 0, Width, Height); 685 | 686 | // Check if the rectangle configuration doesn't generate invalid states or does not affect the target image 687 | if (srcRect.Width <= 0 || srcRect.Height <= 0 || destRect.Width <= 0 || destRect.Height <= 0 || 688 | !srcBitmapRect.IntersectsWith(srcRect) || !destRect.IntersectsWith(destBitmapRect)) 689 | return; 690 | 691 | // Find the areas of the first and second bitmaps that are going to be affected 692 | srcBitmapRect = Rectangle.Intersect(srcRect, srcBitmapRect); 693 | 694 | // Clip the source rectangle on top of the destination rectangle in a way that clips out the regions of the original bitmap 695 | // that will not be drawn on the destination bitmap for being out of bounds 696 | srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(srcRect.X, srcRect.Y, destRect.Width, destRect.Height)); 697 | 698 | destBitmapRect = Rectangle.Intersect(destRect, destBitmapRect); 699 | 700 | // Clip the source bitmap region yet again here 701 | srcBitmapRect = Rectangle.Intersect(srcBitmapRect, new Rectangle(-destRect.X + srcRect.X, -destRect.Y + srcRect.Y, Width, Height)); 702 | 703 | // Calculate the rectangle containing the maximum possible area that is supposed to be affected by the copy region operation 704 | int copyWidth = Math.Min(srcBitmapRect.Width, destBitmapRect.Width); 705 | int copyHeight = Math.Min(srcBitmapRect.Height, destBitmapRect.Height); 706 | 707 | if (copyWidth == 0 || copyHeight == 0) 708 | return; 709 | 710 | int srcStartX = srcBitmapRect.Left; 711 | int srcStartY = srcBitmapRect.Top; 712 | 713 | int destStartX = destBitmapRect.Left; 714 | int destStartY = destBitmapRect.Top; 715 | 716 | using (var fastSource = source.FastLock()) 717 | { 718 | ulong strideWidth = (ulong)copyWidth * BytesPerPixel; 719 | 720 | // Perform copies of whole pixel rows 721 | for (int y = 0; y < copyHeight; y++) 722 | { 723 | int destX = destStartX; 724 | int destY = destStartY + y; 725 | 726 | int srcX = srcStartX; 727 | int srcY = srcStartY + y; 728 | 729 | long offsetSrc = (srcX + srcY * fastSource.Stride); 730 | long offsetDest = (destX + destY * Stride); 731 | 732 | memcpy(_scan0 + offsetDest, fastSource._scan0 + offsetSrc, strideWidth); 733 | } 734 | } 735 | } 736 | 737 | /// 738 | /// Performs a copy operation of the pixels from the Source bitmap to the Target bitmap. 739 | /// If the dimensions or pixel depths of both images don't match, the copy is not performed 740 | /// 741 | /// The bitmap to copy the pixels from 742 | /// The bitmap to copy the pixels to 743 | /// Whether the copy proceedure was successful 744 | /// The provided source and target bitmaps are the same 745 | public static bool CopyPixels(Bitmap source, Bitmap target) 746 | { 747 | if (source == target) 748 | { 749 | throw new ArgumentException(@"Copying pixels across the same bitmap is not supported", nameof(source)); 750 | } 751 | 752 | if (source.Width != target.Width || source.Height != target.Height || source.PixelFormat != target.PixelFormat) 753 | return false; 754 | 755 | using (FastBitmap fastSource = source.FastLock(), 756 | fastTarget = target.FastLock()) 757 | { 758 | memcpy(fastTarget.Scan0, fastSource.Scan0, (ulong)(fastSource.Height * fastSource.Stride * BytesPerPixel)); 759 | } 760 | 761 | return true; 762 | } 763 | 764 | /// 765 | /// Clears the given bitmap with the given color 766 | /// 767 | /// The bitmap to clear 768 | /// The color to clear the bitmap with 769 | public static void ClearBitmap(Bitmap bitmap, Color color) 770 | { 771 | ClearBitmap(bitmap, color.ToArgb()); 772 | } 773 | 774 | /// 775 | /// Clears the given bitmap with the given color 776 | /// 777 | /// The bitmap to clear 778 | /// The color to clear the bitmap with 779 | public static void ClearBitmap(Bitmap bitmap, int color) 780 | { 781 | using (var fb = bitmap.FastLock()) 782 | { 783 | fb.Clear(color); 784 | } 785 | } 786 | 787 | /// 788 | /// Copies a region of the source bitmap to a target bitmap 789 | /// 790 | /// The source image to copy 791 | /// The target image to be altered 792 | /// The region on the source bitmap that will be copied over 793 | /// The region on the target bitmap that will be changed 794 | /// The provided source and target bitmaps are the same bitmap 795 | public static void CopyRegion(Bitmap source, Bitmap target, Rectangle srcRect, Rectangle destRect) 796 | { 797 | var srcBitmapRect = new Rectangle(0, 0, source.Width, source.Height); 798 | var destBitmapRect = new Rectangle(0, 0, target.Width, target.Height); 799 | 800 | // If the copy operation results in an entire copy, use CopyPixels instead 801 | if (srcBitmapRect == srcRect && destBitmapRect == destRect && srcBitmapRect == destBitmapRect) 802 | { 803 | CopyPixels(source, target); 804 | return; 805 | } 806 | 807 | using (var fastTarget = target.FastLock()) 808 | { 809 | fastTarget.CopyRegion(source, srcRect, destRect); 810 | } 811 | } 812 | 813 | /// 814 | /// Returns a bitmap that is a slice of the original provided 32bpp Bitmap. 815 | /// The region must have a width and a height > 0, and must lie inside the source bitmap's area 816 | /// 817 | /// The source bitmap to slice 818 | /// The region of the source bitmap to slice 819 | /// A Bitmap that represents the rectangle region slice of the source bitmap 820 | /// The provided bimap is not 32bpp 821 | /// The provided region is invalid 822 | public static Bitmap SliceBitmap(Bitmap source, Rectangle region) 823 | { 824 | if (region.Width <= 0 || region.Height <= 0) 825 | { 826 | throw new ArgumentException(@"The provided region must have a width and a height > 0", nameof(region)); 827 | } 828 | 829 | var sliceRectangle = Rectangle.Intersect(new Rectangle(Point.Empty, source.Size), region); 830 | 831 | if (sliceRectangle.IsEmpty) 832 | { 833 | throw new ArgumentException(@"The provided region must not lie outside of the bitmap's region completely", nameof(region)); 834 | } 835 | 836 | var slicedBitmap = new Bitmap(sliceRectangle.Width, sliceRectangle.Height); 837 | CopyRegion(source, slicedBitmap, sliceRectangle, new Rectangle(0, 0, sliceRectangle.Width, sliceRectangle.Height)); 838 | 839 | return slicedBitmap; 840 | } 841 | 842 | #if NETSTANDARD 843 | public static void memcpy(IntPtr dest, IntPtr src, ulong count) 844 | { 845 | Buffer.MemoryCopy(src.ToPointer(), dest.ToPointer(), count, count); 846 | } 847 | 848 | public static void memcpy(void* dest, void* src, ulong count) 849 | { 850 | Buffer.MemoryCopy(src, dest, count, count); 851 | } 852 | 853 | public static void memset(void* dest, int value, ulong count) 854 | { 855 | Unsafe.InitBlock(dest, (byte)value, (uint)count); 856 | } 857 | #else 858 | /// 859 | /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed 860 | /// 861 | [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] 862 | public static extern IntPtr memcpy(IntPtr dest, IntPtr src, ulong count); 863 | 864 | /// 865 | /// .NET wrapper to native call of 'memcpy'. Requires Microsoft Visual C++ Runtime installed 866 | /// 867 | [DllImport("msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] 868 | public static extern IntPtr memcpy(void* dest, void* src, ulong count); 869 | 870 | /// 871 | /// .NET wrapper to native call of 'memset'. Requires Microsoft Visual C++ Runtime installed 872 | /// 873 | [DllImport("msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false)] 874 | public static extern IntPtr memset(void* dest, int value, ulong count); 875 | #endif 876 | 877 | /// 878 | /// Represents a disposable structure that is returned during Lock() calls, and unlocks the bitmap on Dispose calls 879 | /// 880 | public readonly struct FastBitmapLocker : IDisposable 881 | { 882 | /// 883 | /// Gets the fast bitmap instance attached to this locker 884 | /// 885 | public FastBitmap FastBitmap { get; } 886 | 887 | /// 888 | /// Initializes a new instance of the FastBitmapLocker struct with an initial fast bitmap object. 889 | /// The fast bitmap object passed will be unlocked after calling Dispose() on this struct 890 | /// 891 | /// A fast bitmap to attach to this locker which will be released after a call to Dispose 892 | public FastBitmapLocker(FastBitmap fastBitmap) 893 | { 894 | FastBitmap = fastBitmap; 895 | } 896 | 897 | /// 898 | /// Disposes of this FastBitmapLocker, essentially unlocking the underlying fast bitmap 899 | /// 900 | public void Dispose() 901 | { 902 | if (FastBitmap.Locked) 903 | FastBitmap.Unlock(); 904 | } 905 | } 906 | } 907 | 908 | /// 909 | /// Describes a pixel format to use when locking a bitmap using . 910 | /// 911 | public enum FastBitmapLockFormat 912 | { 913 | /// 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. 914 | Format32bppRgb = 139273, 915 | /// 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. 916 | Format32bppPArgb = 925707, 917 | /// Specifies that the format is 32 bits per pixel; 8 bits each are used for the alpha, red, green, and blue components. 918 | Format32bppArgb = 2498570, 919 | } 920 | 921 | /// 922 | /// Static class that contains fast bitmap extension methdos for the Bitmap class 923 | /// 924 | public static class FastBitmapExtensions 925 | { 926 | /// 927 | /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels 928 | /// 929 | /// The bitmap to lock 930 | /// A locked FastBitmap 931 | public static FastBitmap FastLock(this Bitmap bitmap) 932 | { 933 | var fast = new FastBitmap(bitmap); 934 | fast.Lock(); 935 | 936 | return fast; 937 | } 938 | 939 | /// 940 | /// Locks this bitmap into memory and returns a FastBitmap that can be used to manipulate its pixels 941 | /// 942 | /// The bitmap to lock 943 | /// The underlying pixel format to use when locking the bitmap 944 | /// A locked FastBitmap 945 | public static FastBitmap FastLock(this Bitmap bitmap, FastBitmapLockFormat lockFormat) 946 | { 947 | var fast = new FastBitmap(bitmap); 948 | fast.Lock(lockFormat); 949 | 950 | return fast; 951 | } 952 | 953 | /// 954 | /// Returns a deep clone of this Bitmap object, with all the data copied over. 955 | /// After a deep clone, the new bitmap is completely independent from the original 956 | /// 957 | /// The bitmap to clone 958 | /// A deep clone of this Bitmap object, with all the data copied over 959 | public static Bitmap DeepClone(this Bitmap bitmap) 960 | { 961 | return bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat); 962 | } 963 | } 964 | } -------------------------------------------------------------------------------- /FastBitmap/FastBitmapLib.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0;net452 5 | true 6 | Luiz Fernando 7 | FastBitmap 8 | Copyright © Luiz Fernando 2017 9 | A fast C# Bitmap wrapping layer. 10 | 2.1.0 11 | true 12 | MIT 13 | imaging bitmap 14 | https://github.com/LuizZak/FastBitmap/ 15 | https://github.com/LuizZak/FastBitmap/ 16 | .NET Standard 2.0 support via @UlyssesWu 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FastBitmap/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTrademark("")] 8 | [assembly: AssemblyCulture("")] 9 | 10 | // Setting ComVisible to false makes the types in this assembly not visible 11 | // to COM components. If you need to access a type in this assembly from 12 | // COM, set the ComVisible attribute to true on that type. 13 | [assembly: ComVisible(false)] 14 | 15 | // The following GUID is for the ID of the typelib if this project is exposed to COM 16 | [assembly: Guid("4bcc21c7-7aa5-4c80-bd42-aab57d5284c1")] 17 | -------------------------------------------------------------------------------- /FastBitmapTests/BitmapSnapshot.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Pixelaria 3 | Copyright (C) 2013 Luiz Fernando Silva 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | The full license may be found on the License.txt file attached to the 20 | base directory of this project. 21 | */ 22 | 23 | using System.Drawing; 24 | using JetBrains.Annotations; 25 | using Microsoft.VisualStudio.TestTools.UnitTesting; 26 | 27 | namespace FastBitmapTests 28 | { 29 | /// 30 | /// 31 | /// A pre-implemented snapshot provider for Bitmap-comparison tests. 32 | /// 33 | public class BitmapSnapshot : ISnapshotProvider 34 | { 35 | /// 36 | /// Whether tests are currently under record mode- under record mode, results are recorded on disk to be later 37 | /// compared when not in record mode. 38 | /// 39 | /// Calls to always fail with an assertion during record mode. 40 | /// 41 | /// Defaults to false. 42 | /// 43 | public static bool RecordMode = false; 44 | 45 | public static void Snapshot([NotNull] Bitmap bitmap, [NotNull] TestContext context) 46 | { 47 | BitmapSnapshotTesting.Snapshot(bitmap, context, RecordMode); 48 | } 49 | 50 | public Bitmap GenerateBitmap(Bitmap context) 51 | { 52 | return context; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /FastBitmapTests/BitmapSnapshotTesting.cs: -------------------------------------------------------------------------------- 1 | /* 2 | Pixelaria 3 | Copyright (C) 2013 Luiz Fernando Silva 4 | 5 | This program is free software; you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation; either version 2 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | You should have received a copy of the GNU General Public License along 16 | with this program; if not, write to the Free Software Foundation, Inc., 17 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 18 | 19 | The full license may be found on the License.txt file attached to the 20 | base directory of this project. 21 | */ 22 | 23 | using System; 24 | using System.Drawing; 25 | using System.Drawing.Imaging; 26 | using System.IO; 27 | using System.Linq; 28 | using System.Text.RegularExpressions; 29 | using FastBitmapLib; 30 | using JetBrains.Annotations; 31 | using Microsoft.VisualStudio.TestTools.UnitTesting; 32 | 33 | namespace FastBitmapTests 34 | { 35 | /// 36 | /// Helper static class to perform bitmap-based rendering comparisons as assertions. 37 | /// 38 | public static class BitmapSnapshotTesting 39 | { 40 | /// 41 | /// If true, when generating output folders for test results, paths are created for each segment of the namespace 42 | /// of the target test class, e.g. 'PixUI.Controls.LabelControlViewTests' becomes '...\PixUI\Controls\LabelControlViewtests\', 43 | /// otherwise a single folder with the fully-qualified class name is used instead. 44 | /// 45 | /// If this property is changed across test recordings, the tests must be re-recorded to account for the new directory paths 46 | /// expected by the snapshot class. 47 | /// 48 | /// Defaults to false. 49 | /// 50 | public static bool SeparateDirectoriesPerNamespace = false; 51 | 52 | /// 53 | /// Performs a snapshot text with a given test context/object pair, using an instantiable snapshot provider. 54 | /// 55 | public static void Snapshot([NotNull] TObject source, [NotNull] TestContext context, bool recordMode) where TProvider : ISnapshotProvider, new() 56 | { 57 | var provider = new TProvider(); 58 | 59 | Snapshot(provider, source, context, recordMode); 60 | } 61 | 62 | /// 63 | /// Performs a snapshot text with a given test context/object pair, using a given instantiated snapshot provider. 64 | /// 65 | public static void Snapshot([NotNull] ISnapshotProvider provider, [NotNull] T target, [NotNull] TestContext context, bool recordMode) 66 | { 67 | string targetPath = CombinedTestResultPath(TestResultsPath(), context); 68 | 69 | // Verify path exists 70 | if (!Directory.Exists(targetPath)) 71 | Directory.CreateDirectory(targetPath); 72 | 73 | string testFileName = context.TestName + ".png"; 74 | string testFilePath = Path.Combine(targetPath, testFileName); 75 | 76 | // Verify comparison file's existence (if not in record mode) 77 | if (!recordMode) 78 | { 79 | if(!File.Exists(testFilePath)) 80 | Assert.Fail($"Could not find reference image file {testFilePath} to compare. Please re-run the test with {nameof(recordMode)} set to true to record a test result to compare later."); 81 | } 82 | 83 | var image = provider.GenerateBitmap(target); 84 | 85 | if (recordMode) 86 | { 87 | image.Save(testFilePath, ImageFormat.Png); 88 | 89 | Assert.Fail( 90 | $"Saved image to path {testFilePath}. Re-run test mode with {nameof(recordMode)} set to false to start comparing with record test result."); 91 | } 92 | else 93 | { 94 | // Load recorded image and compare 95 | using (var expected = (Bitmap)Image.FromFile(testFilePath)) 96 | using (var expLock = expected.FastLock()) 97 | using (var actLock = image.FastLock()) 98 | { 99 | bool areEqual = expLock.Width == actLock.Width && expLock.DataArray.SequenceEqual(actLock.DataArray); 100 | 101 | if (areEqual) 102 | return; // Success! 103 | 104 | // Save to test results directory for further inspection 105 | string directoryName = CombinedTestResultPath(context.TestDir, context); 106 | string baseFileName = Path.ChangeExtension(testFileName, null); 107 | 108 | string savePathExpected = Path.Combine(directoryName, Path.ChangeExtension(baseFileName + "-expected", ".png")); 109 | string savePathActual = Path.Combine(directoryName, Path.ChangeExtension(baseFileName + "-actual", ".png")); 110 | 111 | // Ensure path exists 112 | if (!Directory.Exists(directoryName)) 113 | { 114 | Assert.IsNotNull(directoryName, "directoryName != null"); 115 | Directory.CreateDirectory(directoryName); 116 | } 117 | 118 | image.Save(savePathActual, ImageFormat.Png); 119 | expected.Save(savePathExpected, ImageFormat.Png); 120 | 121 | context.AddResultFile(savePathActual); 122 | 123 | Assert.Fail($"Resulted image did not match expected image. Inspect results under directory {directoryName} for info about results"); 124 | } 125 | } 126 | } 127 | 128 | private static string CombinedTestResultPath([NotNull] string basePath, [NotNull] TestContext context) 129 | { 130 | if(!SeparateDirectoriesPerNamespace) 131 | return Path.Combine(basePath, context.FullyQualifiedTestClassName); 132 | 133 | var segments = context.FullyQualifiedTestClassName.Split('.'); 134 | 135 | return Path.Combine(new[] {basePath}.Concat(segments).ToArray()); 136 | } 137 | 138 | private static string TestResultsPath() 139 | { 140 | string path = Path.GetFullPath(AppDomain.CurrentDomain.BaseDirectory); 141 | 142 | if (path.EndsWith(@"bin\Debug") || path.EndsWith(@"bin\Release")) 143 | { 144 | return Path.GetFullPath(Path.Combine(path, @"..\..\Snapshot\Files")); 145 | } 146 | 147 | if (Regex.IsMatch(path, @"bin\\Debug\\net\d+") || Regex.IsMatch(path, @"bin\\Release\\net\d+")) 148 | { 149 | return Path.GetFullPath(Path.Combine(path, @"..\..\..\Snapshot\Files")); 150 | } 151 | if (path.EndsWith(@"bin\Debug") || path.EndsWith(@"bin\Release")) 152 | { 153 | return Path.GetFullPath(Path.Combine(path, @"..\..\Snapshot\Files")); 154 | } 155 | 156 | Assert.Fail($@"Invalid/unrecognized test assembly path {path}: Path must end in either bin\[Debug|Release] or bin\[Debug|Release]\[netxyz|netcore|netstandard]"); 157 | 158 | return path; 159 | } 160 | } 161 | 162 | /// 163 | /// Base interface for objects instantiated to provide bitmaps for snapshot tests 164 | /// 165 | /// The type of object this snapshot provider receives in order to produce snapshots. 166 | public interface ISnapshotProvider 167 | { 168 | /// 169 | /// Asks this snapshot provider to create a from a given object context. 170 | /// 171 | [NotNull] 172 | Bitmap GenerateBitmap([NotNull] T context); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /FastBitmapTests/FastBitmapTests.cs: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Luiz Fernando Silva 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | 25 | using System; 26 | using System.Drawing; 27 | using System.Drawing.Imaging; 28 | using Microsoft.VisualStudio.TestTools.UnitTesting; 29 | 30 | using FastBitmapLib; 31 | using JetBrains.Annotations; 32 | 33 | namespace FastBitmapTests 34 | { 35 | /// 36 | /// Contains tests for the FastBitmap class and related components 37 | /// 38 | [TestClass] 39 | public class FastBitmapTests 40 | { 41 | [TestInitialize] 42 | public void TestInitialize() 43 | { 44 | BitmapSnapshot.RecordMode = false; 45 | } 46 | 47 | [TestMethod] 48 | public void TestStride() 49 | { 50 | var bitmap = new Bitmap(64, 64); 51 | var fastBitmap = new FastBitmap(bitmap); 52 | fastBitmap.Lock(); 53 | 54 | Assert.AreEqual(fastBitmap.Stride, 64); 55 | Assert.AreEqual(fastBitmap.StrideInBytes, 64 * 4); 56 | 57 | fastBitmap.Unlock(); 58 | } 59 | 60 | [TestMethod] 61 | [ExpectedException(typeof(ArgumentException), 62 | "Providing a bitmap with a bit-depth different than 32bpp to a FastBitmap must return an ArgumentException")] 63 | public void TestFastBitmapCreation() 64 | { 65 | var bitmap = new Bitmap(64, 64); 66 | var fastBitmap = new FastBitmap(bitmap); 67 | fastBitmap.Lock(); 68 | fastBitmap.Unlock(); 69 | 70 | // Try creating a FastBitmap with different 32bpp depths 71 | try 72 | { 73 | // ReSharper disable once ObjectCreationAsStatement 74 | new FastBitmap(new Bitmap(1, 1, PixelFormat.Format32bppArgb)); 75 | // ReSharper disable once ObjectCreationAsStatement 76 | new FastBitmap(new Bitmap(1, 1, PixelFormat.Format32bppPArgb)); 77 | // ReSharper disable once ObjectCreationAsStatement 78 | new FastBitmap(new Bitmap(1, 1, PixelFormat.Format32bppRgb)); 79 | } 80 | catch (ArgumentException) 81 | { 82 | Assert.Fail("The FastBitmap should accept any type of 32bpp pixel format bitmap"); 83 | } 84 | 85 | // Try creating a FastBitmap with a bitmap of a bit depth different from 32bpp 86 | var invalidBitmap = new Bitmap(64, 64, PixelFormat.Format4bppIndexed); 87 | 88 | // ReSharper disable once ObjectCreationAsStatement 89 | new FastBitmap(invalidBitmap); 90 | } 91 | 92 | /// 93 | /// Tests sequential instances of FastBitmaps on the same Bitmap. 94 | /// As long as all the operations pending on a fast bitmap are finished, the original bitmap can be used in as many future fast bitmaps as needed. 95 | /// 96 | [TestMethod] 97 | public void TestSequentialFastBitmapLocking() 98 | { 99 | var bitmap = new Bitmap(64, 64); 100 | var fastBitmap = new FastBitmap(bitmap); 101 | 102 | Assert.IsFalse(fastBitmap.Locked, "Immediately after creation, the FastBitmap.Locked property must be false"); 103 | 104 | fastBitmap.Lock(); 105 | 106 | Assert.IsTrue(fastBitmap.Locked, "After a successful call to .Lock(), the .Locked property must be true"); 107 | 108 | fastBitmap.Unlock(); 109 | 110 | Assert.IsFalse(fastBitmap.Locked, "After a successful call to .Lock(), the .Locked property must be false"); 111 | 112 | fastBitmap = new FastBitmap(bitmap); 113 | fastBitmap.Lock(); 114 | fastBitmap.Unlock(); 115 | } 116 | 117 | /// 118 | /// Tests a failing scenario for fast bitmap creations where a sequential fast bitmap is created and locked while another fast bitmap is operating on the same bitmap 119 | /// 120 | [TestMethod] 121 | [ExpectedException(typeof(InvalidOperationException), "Trying to Lock() a bitmap while it is Locked() in another FastBitmap must raise an exception")] 122 | public void TestFailedSequentialFastBitmapLocking() 123 | { 124 | var bitmap = new Bitmap(64, 64); 125 | var fastBitmap = new FastBitmap(bitmap); 126 | fastBitmap.Lock(); 127 | 128 | fastBitmap = new FastBitmap(bitmap); 129 | fastBitmap.Lock(); 130 | } 131 | 132 | /// 133 | /// Tests the behavior of the .Clear() instance and class methods by clearing a bitmap and checking the result pixel-by-pixel 134 | /// 135 | [TestMethod] 136 | public void TestClearBitmap() 137 | { 138 | var bitmap = GenerateRainbowBitmap(63, 63); // Non-divisible by 8 bitmap, used to test loop unrolling 139 | FastBitmap.ClearBitmap(bitmap, Color.Red); 140 | 141 | // Loop through the image checking the pixels now 142 | for (int y = 0; y < bitmap.Height; y++) 143 | { 144 | for (int x = 0; x < bitmap.Width; x++) 145 | { 146 | if (bitmap.GetPixel(x, y).ToArgb() != Color.Red.ToArgb()) 147 | { 148 | Assert.Fail( 149 | "Immediately after a call to FastBitmap.Clear(), all of the bitmap's pixels must be of the provided color"); 150 | } 151 | } 152 | } 153 | 154 | // Test an arbitrary color now 155 | FastBitmap.ClearBitmap(bitmap, Color.FromArgb(25, 12, 0, 42)); 156 | 157 | // Loop through the image checking the pixels now 158 | for (int y = 0; y < bitmap.Height; y++) 159 | { 160 | for (int x = 0; x < bitmap.Width; x++) 161 | { 162 | if (bitmap.GetPixel(x, y).ToArgb() != Color.FromArgb(25, 12, 0, 42).ToArgb()) 163 | { 164 | Assert.Fail( 165 | "Immediately after a call to FastBitmap.Clear(), all of the bitmap's pixels must be of the provided color"); 166 | } 167 | } 168 | } 169 | 170 | // Test instance call 171 | var fastBitmap = new FastBitmap(bitmap); 172 | fastBitmap.Clear(Color.FromArgb(25, 12, 0, 42)); 173 | 174 | Assert.IsFalse(fastBitmap.Locked, "After a successfull call to .Clear() on a fast bitmap previously unlocked, the .Locked property must be false"); 175 | 176 | // Loop through the image checking the pixels now 177 | for (int y = 0; y < bitmap.Height; y++) 178 | { 179 | for (int x = 0; x < bitmap.Width; x++) 180 | { 181 | if (bitmap.GetPixel(x, y).ToArgb() != Color.FromArgb(25, 12, 0, 42).ToArgb()) 182 | { 183 | Assert.Fail( 184 | "Immediately after a call to FastBitmap.Clear(), all of the bitmap's pixels must be of the provided color"); 185 | } 186 | } 187 | } 188 | } 189 | 190 | [TestMethod] 191 | public void TestClearBitmapMemSetOptimization() 192 | { 193 | // If a provided color has the same byte values for each component 194 | // (e.g. 0xFFFFFFFF, 0xABABABAB, 0x66666666, etc.) the code takes a 195 | // fast path that simply mem-sets each row of the target image 196 | 197 | { 198 | var bitmap = new Bitmap(63, 63); // Non-divisible by 8 bitmap, used to test loop unrolling 199 | 200 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 63, 63), Color.Red); 201 | 202 | using (var fastBitmap = bitmap.FastLock()) 203 | { 204 | fastBitmap.Clear(Color.White); 205 | } 206 | 207 | // Verify expected pixels 208 | for (int y = 0; y < bitmap.Height; y++) 209 | { 210 | for (int x = 0; x < bitmap.Width; x++) 211 | { 212 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.White.ToArgb(), $"{{{x},{y}}}"); 213 | } 214 | } 215 | } 216 | 217 | { 218 | // Now try with a black transparent colors 219 | 220 | var bitmap = new Bitmap(63, 63); 221 | 222 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 64, 64), Color.Red); 223 | 224 | using (var fastBitmap = bitmap.FastLock()) 225 | { 226 | fastBitmap.Clear(Color.FromArgb(0)); 227 | } 228 | 229 | // Verify expected pixels 230 | for (int y = 0; y < bitmap.Height; y++) 231 | { 232 | for (int x = 0; x < bitmap.Width; x++) 233 | { 234 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), 0, $"{{{x},{y}}}"); 235 | } 236 | } 237 | } 238 | } 239 | 240 | /// 241 | /// Tests the behavior of the GetPixel(x, y) method by comparing the results from it to the results of the native Bitmap.GetPixel() 242 | /// 243 | [TestMethod] 244 | public void TestGetPixel() 245 | { 246 | var original = GenerateRainbowBitmap(12, 12); 247 | var copy = original.Clone(new Rectangle(0, 0, 12, 12), original.PixelFormat); 248 | 249 | var fastOriginal = new FastBitmap(original); 250 | fastOriginal.Lock(); 251 | 252 | for (int y = 0; y < original.Height; y++) 253 | { 254 | for (int x = 0; x < original.Width; x++) 255 | { 256 | Assert.AreEqual(fastOriginal.GetPixel(x, y).ToArgb(), copy.GetPixel(x, y).ToArgb(), 257 | "Calls to FastBitmap.GetPixel() must return the same value as returned by Bitmap.GetPixel()"); 258 | } 259 | } 260 | 261 | fastOriginal.Unlock(); 262 | } 263 | 264 | /// 265 | /// Tests the behavior of the GetPixelInt(x, y) method by comparing the results from it to the results of the native Bitmap.GetPixel() 266 | /// 267 | [TestMethod] 268 | public void TestGetPixelInt() 269 | { 270 | var original = GenerateRainbowBitmap(12, 12); 271 | var copy = original.Clone(new Rectangle(0, 0, 12, 12), original.PixelFormat); 272 | 273 | var fastOriginal = new FastBitmap(original); 274 | fastOriginal.Lock(); 275 | 276 | for (int y = 0; y < original.Height; y++) 277 | { 278 | for (int x = 0; x < original.Width; x++) 279 | { 280 | Assert.AreEqual(fastOriginal.GetPixelInt(x, y), copy.GetPixel(x, y).ToArgb(), 281 | "Calls to FastBitmap.GetPixelInt() must return the same value as returned by Bitmap.GetPixel()"); 282 | } 283 | } 284 | 285 | fastOriginal.Unlock(); 286 | } 287 | 288 | /// 289 | /// Tests the behavior of the GetPixelUInt(x, y) method by comparing the results from it to the results of the native Bitmap.GetPixel() 290 | /// 291 | [TestMethod] 292 | public void TestGetPixelUInt() 293 | { 294 | var original = GenerateRainbowBitmap(12, 12); 295 | var copy = original.Clone(new Rectangle(0, 0, 12, 12), original.PixelFormat); 296 | 297 | var fastOriginal = new FastBitmap(original); 298 | fastOriginal.Lock(); 299 | 300 | for (int y = 0; y < original.Height; y++) 301 | { 302 | for (int x = 0; x < original.Width; x++) 303 | { 304 | Assert.AreEqual(fastOriginal.GetPixelUInt(x, y), (uint)copy.GetPixel(x, y).ToArgb(), 305 | "Calls to FastBitmap.GetPixelUInt() must return the same value as returned by Bitmap.GetPixel()"); 306 | } 307 | } 308 | 309 | fastOriginal.Unlock(); 310 | } 311 | 312 | /// 313 | /// Tests the behavior of the GetPixelUInt(index) method by comparing the results from it to the results of the native Bitmap.GetPixel() 314 | /// 315 | [TestMethod] 316 | public void TestGetPixelUIntIndex() 317 | { 318 | var original = GenerateRainbowBitmap(12, 12); 319 | var copy = original.Clone(new Rectangle(0, 0, 12, 12), original.PixelFormat); 320 | 321 | var fastOriginal = new FastBitmap(original); 322 | fastOriginal.Lock(); 323 | 324 | for (int y = 0; y < original.Height; y++) 325 | { 326 | for (int x = 0; x < original.Width; x++) 327 | { 328 | Assert.AreEqual(fastOriginal.GetPixelUInt(x + y * fastOriginal.Height), (uint)copy.GetPixel(x, y).ToArgb(), 329 | "Calls to FastBitmap.GetPixelUInt() must return the same value as returned by Bitmap.GetPixel()"); 330 | } 331 | } 332 | 333 | fastOriginal.Unlock(); 334 | } 335 | 336 | /// 337 | /// Tests the behavior of the SetPixel() method by randomly filling two bitmaps via native SetPixel and the implemented SetPixel, then comparing the output similarity 338 | /// 339 | [TestMethod] 340 | public void TestSetPixel() 341 | { 342 | var bitmap1 = new Bitmap(12, 12); 343 | var bitmap2 = new Bitmap(12, 12); 344 | 345 | var fastBitmap1 = new FastBitmap(bitmap1); 346 | fastBitmap1.Lock(); 347 | 348 | var r = new Random(); 349 | 350 | for (int y = 0; y < bitmap1.Height; y++) 351 | { 352 | for (int x = 0; x < bitmap1.Width; x++) 353 | { 354 | int intColor = r.Next(0xFFFFFF); 355 | var color = Color.FromArgb(intColor); 356 | 357 | fastBitmap1.SetPixel(x, y, color); 358 | bitmap2.SetPixel(x, y, color); 359 | } 360 | } 361 | 362 | fastBitmap1.Unlock(); 363 | 364 | AssertBitmapEquals(bitmap1, bitmap2, 365 | "Calls to FastBitmap.SetPixel() must be equivalent to calls to Bitmap.SetPixel()"); 366 | } 367 | 368 | /// 369 | /// Tests the behavior of the SetPixel() integer overload method by randomly filling two bitmaps via native SetPixel and the implemented SetPixel, then comparing the output similarity 370 | /// 371 | [TestMethod] 372 | public void TestSetPixelInt() 373 | { 374 | var bitmap1 = new Bitmap(12, 12); 375 | var bitmap2 = new Bitmap(12, 12); 376 | 377 | var fastBitmap1 = new FastBitmap(bitmap1); 378 | fastBitmap1.Lock(); 379 | 380 | var r = new Random(); 381 | 382 | for (int y = 0; y < bitmap1.Height; y++) 383 | { 384 | for (int x = 0; x < bitmap1.Width; x++) 385 | { 386 | int intColor = r.Next(0xFFFFFF); 387 | var color = Color.FromArgb(intColor); 388 | 389 | fastBitmap1.SetPixel(x, y, intColor); 390 | bitmap2.SetPixel(x, y, color); 391 | } 392 | } 393 | 394 | fastBitmap1.Unlock(); 395 | 396 | AssertBitmapEquals(bitmap1, bitmap2, 397 | "Calls to FastBitmap.SetPixel() with an integer overload must be equivalent to calls to Bitmap.SetPixel() with a Color with the same ARGB value as the interger"); 398 | } 399 | 400 | /// 401 | /// Tests the behavior of the SetPixel() unsigned integer overload method by randomly filling two bitmaps via native SetPixel and the implemented SetPixel, then comparing the output similarity 402 | /// 403 | [TestMethod] 404 | public void TestSetPixelUInt() 405 | { 406 | var bitmap1 = new Bitmap(12, 12); 407 | var bitmap2 = new Bitmap(12, 12); 408 | 409 | var fastBitmap1 = new FastBitmap(bitmap1); 410 | fastBitmap1.Lock(); 411 | 412 | var r = new Random(); 413 | 414 | for (int y = 0; y < bitmap1.Height; y++) 415 | { 416 | for (int x = 0; x < bitmap1.Width; x++) 417 | { 418 | uint uintColor = (uint)r.Next(0xFFFFFF); 419 | var color = Color.FromArgb((int)uintColor); 420 | 421 | fastBitmap1.SetPixel(x, y, uintColor); 422 | bitmap2.SetPixel(x, y, color); 423 | } 424 | } 425 | 426 | fastBitmap1.Unlock(); 427 | 428 | AssertBitmapEquals(bitmap1, bitmap2, 429 | "Calls to FastBitmap.SetPixel() with an integer overload must be equivalent to calls to Bitmap.SetPixel() with a Color with the same ARGB value as the interger"); 430 | } 431 | 432 | /// 433 | /// Tests the behavior of the SetPixel() indexed unsigned integer overload method by randomly filling two bitmaps via native SetPixel and the implemented SetPixel, then comparing the output similarity 434 | /// 435 | [TestMethod] 436 | public void TestSetPixelUIntIndex() 437 | { 438 | var bitmap1 = new Bitmap(12, 12); 439 | var bitmap2 = new Bitmap(12, 12); 440 | 441 | var fastBitmap1 = new FastBitmap(bitmap1); 442 | fastBitmap1.Lock(); 443 | 444 | var r = new Random(); 445 | 446 | for (int y = 0; y < bitmap1.Height; y++) 447 | { 448 | for (int x = 0; x < bitmap1.Width; x++) 449 | { 450 | uint uintColor = (uint)r.Next(0xFFFFFF); 451 | var color = Color.FromArgb((int)uintColor); 452 | 453 | fastBitmap1.SetPixel(x + y * fastBitmap1.Height, uintColor); 454 | bitmap2.SetPixel(x, y, color); 455 | } 456 | } 457 | 458 | fastBitmap1.Unlock(); 459 | 460 | AssertBitmapEquals(bitmap1, bitmap2, 461 | "Calls to FastBitmap.SetPixel() with an integer overload must be equivalent to calls to Bitmap.SetPixel() with a Color with the same ARGB value as the interger"); 462 | } 463 | 464 | /// 465 | /// Tests a call to FastBitmap.CopyPixels() with valid provided bitmaps 466 | /// 467 | [TestMethod] 468 | public void TestValidCopyPixels() 469 | { 470 | var bitmap1 = GenerateRainbowBitmap(64, 64); 471 | var bitmap2 = new Bitmap(64, 64); 472 | 473 | FastBitmap.CopyPixels(bitmap1, bitmap2); 474 | 475 | AssertBitmapEquals(bitmap1, bitmap2, 476 | "After a successful call to CopyPixels(), both bitmaps must be equal down to the pixel level"); 477 | } 478 | 479 | /// 480 | /// Tests a call to FastBitmap.CopyPixels() with bitmaps of different sizes and different bitdepths 481 | /// 482 | [TestMethod] 483 | public void TestInvalidCopyPixels() 484 | { 485 | var bitmap1 = new Bitmap(64, 64, PixelFormat.Format24bppRgb); 486 | var bitmap2 = new Bitmap(64, 64, PixelFormat.Format1bppIndexed); 487 | 488 | if (FastBitmap.CopyPixels(bitmap1, bitmap2)) 489 | { 490 | Assert.Fail("Trying to copy two bitmaps of different bitdepths should not be allowed"); 491 | } 492 | 493 | bitmap1 = new Bitmap(64, 64, PixelFormat.Format32bppArgb); 494 | bitmap2 = new Bitmap(66, 64, PixelFormat.Format32bppArgb); 495 | 496 | if (FastBitmap.CopyPixels(bitmap1, bitmap2)) 497 | { 498 | Assert.Fail("Trying to copy two bitmaps of different sizes should not be allowed"); 499 | } 500 | } 501 | 502 | #region CopyRegion Tests 503 | 504 | /// 505 | /// Tests the CopyRegion() static and instance methods by creating two bitmaps, copying regions over from one to another, and comparing the expected pixel equalities 506 | /// 507 | [TestMethod] 508 | public void TestSimpleCopyRegion() 509 | { 510 | var canvasBitmap = new Bitmap(64, 64); 511 | var copyBitmap = GenerateRainbowBitmap(32, 32); 512 | 513 | var sourceRectangle = new Rectangle(0, 0, 32, 32); 514 | var targetRectangle = new Rectangle(0, 0, 64, 64); 515 | 516 | FastBitmap.CopyRegion(copyBitmap, canvasBitmap, sourceRectangle, targetRectangle); 517 | 518 | BitmapSnapshot.Snapshot(canvasBitmap, TestContext); 519 | } 520 | 521 | /// 522 | /// Tests the CopyRegion() static and instance methods by creating two bitmaps, copying regions over from one to another, and comparing the expected pixel equalities. 523 | /// The source and target rectangles are moved around, and the source rectangle clips outside the bounds of the copy bitmap 524 | /// 525 | [TestMethod] 526 | public void TestComplexCopyRegion() 527 | { 528 | var canvasBitmap = new Bitmap(64, 64); 529 | var copyBitmap = GenerateRainbowBitmap(32, 32); 530 | 531 | var sourceRectangle = new Rectangle(5, 5, 32, 32); 532 | var targetRectangle = new Rectangle(9, 9, 23, 48); 533 | 534 | FastBitmap.CopyRegion(copyBitmap, canvasBitmap, sourceRectangle, targetRectangle); 535 | 536 | BitmapSnapshot.Snapshot(canvasBitmap, TestContext); 537 | } 538 | 539 | /// 540 | /// Tests the CopyRegion() static and instance methods by creating two bitmaps, copying regions over from one to another, and comparing the expected pixel equalities. 541 | /// The copy region clips outside the target and source bitmap areas 542 | /// 543 | [TestMethod] 544 | public void TestClippingCopyRegion() 545 | { 546 | var canvasBitmap = new Bitmap(64, 64); 547 | var copyBitmap = GenerateRainbowBitmap(32, 32); 548 | 549 | var sourceRectangle = new Rectangle(-5, 5, 32, 32); 550 | var targetRectangle = new Rectangle(40, 9, 23, 48); 551 | 552 | FastBitmap.CopyRegion(copyBitmap, canvasBitmap, sourceRectangle, targetRectangle); 553 | 554 | BitmapSnapshot.Snapshot(canvasBitmap, TestContext); 555 | } 556 | 557 | /// 558 | /// Tests the CopyRegion() static and instance methods by creating two bitmaps, copying regions over from one to another, and comparing the expected pixel equalities. 559 | /// The source region provided is out of the bounds of the copy image 560 | /// 561 | [TestMethod] 562 | public void TestOutOfBoundsCopyRegion() 563 | { 564 | var canvasBitmap = new Bitmap(64, 64); 565 | var copyBitmap = GenerateRainbowBitmap(32, 32); 566 | 567 | var sourceRectangle = new Rectangle(32, 0, 32, 32); 568 | var targetRectangle = new Rectangle(0, 0, 23, 48); 569 | 570 | FastBitmap.CopyRegion(copyBitmap, canvasBitmap, sourceRectangle, targetRectangle); 571 | 572 | BitmapSnapshot.Snapshot(canvasBitmap, TestContext); 573 | } 574 | 575 | /// 576 | /// Tests the CopyRegion() static and instance methods by creating two bitmaps, copying regions over from one to another, and comparing the expected pixel equalities. 577 | /// The source region provided is invalid, and no modifications are to be made 578 | /// 579 | [TestMethod] 580 | public void TestInvalidCopyRegion() 581 | { 582 | var canvasBitmap = new Bitmap(64, 64); 583 | var copyBitmap = GenerateRainbowBitmap(32, 32); 584 | 585 | var sourceRectangle = new Rectangle(0, 0, -1, 32); 586 | var targetRectangle = new Rectangle(0, 0, 23, 48); 587 | 588 | FastBitmap.CopyRegion(copyBitmap, canvasBitmap, sourceRectangle, targetRectangle); 589 | 590 | BitmapSnapshot.Snapshot(canvasBitmap, TestContext); 591 | } 592 | 593 | /// 594 | /// Tests sequential region copying across multiple bitmaps by copying regions between 4 bitmaps 595 | /// 596 | [TestMethod] 597 | public void TestSequentialCopyRegion() 598 | { 599 | var bitmap1 = new Bitmap(64, 64); 600 | var bitmap2 = new Bitmap(64, 64); 601 | var bitmap3 = new Bitmap(64, 64); 602 | var bitmap4 = new Bitmap(64, 64); 603 | 604 | var region = new Rectangle(0, 0, 64, 64); 605 | 606 | FastBitmap.CopyRegion(bitmap1, bitmap2, region, region); 607 | FastBitmap.CopyRegion(bitmap3, bitmap4, region, region); 608 | FastBitmap.CopyRegion(bitmap1, bitmap3, region, region); 609 | FastBitmap.CopyRegion(bitmap4, bitmap2, region, region); 610 | } 611 | 612 | /// 613 | /// Tests a copy region operation that is slices through the destination 614 | /// 615 | [TestMethod] 616 | public void TestSlicedDestinationCopyRegion() 617 | { 618 | // Have a copy operation that goes: 619 | // 620 | // -src--- 621 | // -dest-|-----|------ 622 | // | |xxxxx| | 623 | // | |xxxxx| | 624 | // ------|-----|------ 625 | // ------- 626 | // 627 | 628 | var canvasBitmap = new Bitmap(128, 32); 629 | var copyBitmap = GenerateRainbowBitmap(32, 64); 630 | 631 | var sourceRectangle = new Rectangle(0, 0, 32, 64); 632 | var targetRectangle = new Rectangle(48, -16, 32, 64); 633 | 634 | FastBitmap.CopyRegion(copyBitmap, canvasBitmap, sourceRectangle, targetRectangle); 635 | 636 | BitmapSnapshot.Snapshot(canvasBitmap, TestContext); 637 | } 638 | 639 | #endregion 640 | 641 | /// 642 | /// Tests the FastBitmapLocker struct returned by lock calls 643 | /// 644 | [TestMethod] 645 | public void TestFastBitmapLocker() 646 | { 647 | var bitmap = new Bitmap(64, 64); 648 | var fastBitmap = new FastBitmap(bitmap); 649 | 650 | // Immediate lock and dispose 651 | fastBitmap.Lock().Dispose(); 652 | Assert.IsFalse(fastBitmap.Locked, "After disposing of the FastBitmapLocker object, the underlying fast bitmap must be unlocked"); 653 | 654 | using (var locker = fastBitmap.Lock()) 655 | { 656 | fastBitmap.SetPixel(0, 0, 0); 657 | 658 | Assert.AreEqual(fastBitmap, locker.FastBitmap, "The fast bitmap referenced in the fast bitmap locker must be the one that had the original Lock() call"); 659 | } 660 | 661 | Assert.IsFalse(fastBitmap.Locked, "After disposing of the FastBitmapLocker object, the underlying fast bitmap must be unlocked"); 662 | 663 | // Test the conditional unlocking of the fast bitmap locker by unlocking the fast bitmap before exiting the 'using' block 664 | using (fastBitmap.Lock()) 665 | { 666 | fastBitmap.SetPixel(0, 0, 0); 667 | fastBitmap.Unlock(); 668 | } 669 | } 670 | 671 | [TestMethod] 672 | public void TestLockExtensionMethod() 673 | { 674 | var bitmap = new Bitmap(64, 64); 675 | 676 | using (var fast = bitmap.FastLock()) 677 | { 678 | fast.SetPixel(0, 0, Color.Red); 679 | } 680 | 681 | // Test unlocking by trying to modify the bitmap 682 | bitmap.SetPixel(0, 0, Color.Blue); 683 | } 684 | 685 | [TestMethod] 686 | public void TestDataArray() 687 | { 688 | // TODO: Devise a way to test the returned array in a more consistent way, because currently this test only deals with ARGB pixel values because Bitmap.GetPixel().ToArgb() only returns 0xAARRGGBB format values 689 | var bitmap = GenerateRainbowBitmap(64, 64); 690 | var fastBitmap = new FastBitmap(bitmap); 691 | 692 | Assert.IsFalse(fastBitmap.Locked, "After accessing the .Data property on a fast bitmap previously unlocked, the .Locked property must be false"); 693 | 694 | var pixels = fastBitmap.DataArray; 695 | 696 | for (int y = 0; y < bitmap.Height; y++) 697 | { 698 | for (int x = 0; x < bitmap.Width; x++) 699 | { 700 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), pixels[y * bitmap.Width + x], ""); 701 | } 702 | } 703 | } 704 | 705 | [TestMethod] 706 | public void TestGetDataAsArray() 707 | { 708 | // TODO: Devise a way to test the returned array in a more consistent way, because currently this test only deals with ARGB pixel values because Bitmap.GetPixel().ToArgb() only returns 0xAARRGGBB format values 709 | var bitmap = GenerateRainbowBitmap(64, 64); 710 | var fastBitmap = new FastBitmap(bitmap); 711 | 712 | Assert.IsFalse(fastBitmap.Locked, "After accessing the .Data property on a fast bitmap previously unlocked, the .Locked property must be false"); 713 | 714 | var pixels = fastBitmap.GetDataAsArray(); 715 | 716 | for (int y = 0; y < bitmap.Height; y++) 717 | { 718 | for (int x = 0; x < bitmap.Width; x++) 719 | { 720 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), pixels[y * bitmap.Width + x], ""); 721 | } 722 | } 723 | } 724 | 725 | [TestMethod] 726 | public void TestCopyFromArray() 727 | { 728 | var bitmap = new Bitmap(4, 4); 729 | int[] colors = 730 | { 731 | 0xFFFFFF, 0xFFFFEF, 0xABABAB, 0xABCDEF, 732 | 0x111111, 0x123456, 0x654321, 0x000000, 733 | 0xFFFFFF, 0xFFFFEF, 0xABABAB, 0xABCDEF, 734 | 0x111111, 0x123456, 0x654321, 0x000000 735 | }; 736 | 737 | using (var fastBitmap = bitmap.FastLock()) 738 | { 739 | fastBitmap.CopyFromArray(colors); 740 | } 741 | 742 | // Test now the resulting bitmap 743 | for (int y = 0; y < bitmap.Height; y++) 744 | { 745 | for (int x = 0; x < bitmap.Width; x++) 746 | { 747 | int index = y * bitmap.Width + x; 748 | 749 | Assert.AreEqual(colors[index], bitmap.GetPixel(x, y).ToArgb(), 750 | "After a call to CopyFromArray, the values provided on the on the array must match the values in the bitmap pixels"); 751 | } 752 | } 753 | } 754 | 755 | [TestMethod] 756 | public void TestCopyFromArrayIgnoreZeroes() 757 | { 758 | var bitmap = new Bitmap(4, 4); 759 | 760 | FillBitmapRegion(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), Color.Red); 761 | 762 | int[] colors = 763 | { 764 | 0xFFFFFF, 0xFFFFEF, 0xABABAB, 0xABCDEF, 765 | 0x111111, 0x123456, 0x654321, 0x000000, 766 | 0x000000, 0xFFFFEF, 0x000000, 0xABCDEF, 767 | 0x000000, 0x000000, 0x654321, 0x000000 768 | }; 769 | 770 | using (var fastBitmap = bitmap.FastLock()) 771 | { 772 | fastBitmap.CopyFromArray(colors, true); 773 | } 774 | 775 | // Test now the resulting bitmap 776 | for (int y = 0; y < bitmap.Height; y++) 777 | { 778 | for (int x = 0; x < bitmap.Width; x++) 779 | { 780 | int index = y * bitmap.Width + x; 781 | int arrayColor = colors[index]; 782 | int bitmapColor = bitmap.GetPixel(x, y).ToArgb(); 783 | 784 | if (arrayColor != 0) 785 | { 786 | Assert.AreEqual(arrayColor, bitmapColor, 787 | "After a call to CopyFromArray(_, true), the non-zeroes values provided on the on the array must match the values in the bitmap pixels"); 788 | } 789 | else 790 | { 791 | Assert.AreEqual(Color.Red.ToArgb(), bitmapColor, 792 | "After a call to CopyFromArray(_, true), the 0 values on the original array must not be copied over"); 793 | } 794 | } 795 | } 796 | } 797 | 798 | [TestMethod] 799 | public void TestClearRegionSmall() 800 | { 801 | var bitmap = new Bitmap(16, 16); 802 | 803 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 16, 16), Color.Red); 804 | 805 | using (var fastBitmap = bitmap.FastLock()) 806 | { 807 | fastBitmap.ClearRegion(new Rectangle(1, 1, 4, 4), Color.White); 808 | } 809 | 810 | // Verify expected pixels 811 | for (int y = 0; y < bitmap.Height; y++) 812 | { 813 | for (int x = 0; x < bitmap.Width; x++) 814 | { 815 | if (x >= 1 && x <= 4 && y >= 1 && y <= 4) 816 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.White.ToArgb(), $"{{{x},{y}}}"); 817 | else 818 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.Red.ToArgb(), $"{{{x},{y}}}"); 819 | } 820 | } 821 | } 822 | 823 | [TestMethod] 824 | public void TestClearRegionEntireBitmap() 825 | { 826 | var bitmap = new Bitmap(16, 16); 827 | 828 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 16, 16), Color.Red); 829 | 830 | using (var fastBitmap = bitmap.FastLock()) 831 | { 832 | fastBitmap.ClearRegion(new Rectangle(0, 0, 16, 16), Color.White); 833 | } 834 | 835 | // Verify expected pixels 836 | for (int y = 0; y < bitmap.Height; y++) 837 | { 838 | for (int x = 0; x < bitmap.Width; x++) 839 | { 840 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.White.ToArgb(), $"{{{x},{y}}}"); 841 | } 842 | } 843 | } 844 | 845 | [TestMethod] 846 | public void TestClearRegionRowBlockCopyOptimization() 847 | { 848 | var bitmap = new Bitmap(63, 63); // Non-dibisible by 8 bitmap, used to test loop unrolling 849 | 850 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 63, 63), Color.Red); 851 | 852 | var region = new Rectangle(4, 4, 16, 16); 853 | 854 | using (var fastBitmap = bitmap.FastLock()) 855 | { 856 | fastBitmap.ClearRegion(region, Color.Blue); 857 | } 858 | 859 | // Verify expected pixels 860 | for (int y = 0; y < bitmap.Height; y++) 861 | { 862 | for (int x = 0; x < bitmap.Width; x++) 863 | { 864 | if (x >= region.Left && x < region.Right && y >= region.Top && y < region.Bottom) 865 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.Blue.ToArgb(), $"{{{x},{y}}}"); 866 | else 867 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.Red.ToArgb(), $"{{{x},{y}}}"); 868 | } 869 | } 870 | } 871 | 872 | [TestMethod] 873 | public void TestClearRegionMemSetOptimization() 874 | { 875 | // If a provided color has the same byte values for each component 876 | // (e.g. 0xFFFFFFFF, 0xABABABAB, 0x66666666, etc.) the code takes a 877 | // fast path that simply mem-sets each row of the target image 878 | 879 | { 880 | var bitmap = new Bitmap(63, 63); // Non-dibisible by 8 bitmap, used to test loop unrolling 881 | 882 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 63, 63), Color.Red); 883 | 884 | var region = new Rectangle(4, 4, 16, 16); 885 | 886 | using (var fastBitmap = bitmap.FastLock()) 887 | { 888 | fastBitmap.ClearRegion(region, Color.White); 889 | } 890 | 891 | // Verify expected pixels 892 | for (int y = 0; y < bitmap.Height; y++) 893 | { 894 | for (int x = 0; x < bitmap.Width; x++) 895 | { 896 | if (x >= region.Left && x < region.Right && y >= region.Top && y < region.Bottom) 897 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.White.ToArgb(), $"{{{x},{y}}}"); 898 | else 899 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.Red.ToArgb(), $"{{{x},{y}}}"); 900 | } 901 | } 902 | } 903 | 904 | { 905 | // Now try with a black transparent colors 906 | 907 | var bitmap = new Bitmap(63, 63); 908 | 909 | FillBitmapRegion(bitmap, new Rectangle(0, 0, 63, 63), Color.Red); 910 | 911 | var region = new Rectangle(4, 4, 16, 16); 912 | 913 | using (var fastBitmap = bitmap.FastLock()) 914 | { 915 | fastBitmap.ClearRegion(region, Color.FromArgb(0)); 916 | } 917 | 918 | // Verify expected pixels 919 | for (int y = 0; y < bitmap.Height; y++) 920 | { 921 | for (int x = 0; x < bitmap.Width; x++) 922 | { 923 | if (x >= region.Left && x < region.Right && y >= region.Top && y < region.Bottom) 924 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), 0, $"{{{x},{y}}}"); 925 | else 926 | Assert.AreEqual(bitmap.GetPixel(x, y).ToArgb(), Color.Red.ToArgb(), $"{{{x},{y}}}"); 927 | } 928 | } 929 | } 930 | } 931 | 932 | [TestMethod] 933 | public void TestLockFormatArgbToPArgb() 934 | { 935 | var bitmap = new Bitmap(16, 16, PixelFormat.Format32bppArgb); 936 | var fastBitmap = new FastBitmap(bitmap); 937 | 938 | fastBitmap.Lock(FastBitmapLockFormat.Format32bppPArgb); 939 | 940 | fastBitmap.Unlock(); 941 | } 942 | 943 | [TestMethod] 944 | public void TestLockFormatArgbToRgb() 945 | { 946 | var bitmap = new Bitmap(16, 16, PixelFormat.Format32bppArgb); 947 | var fastBitmap = new FastBitmap(bitmap); 948 | 949 | fastBitmap.Lock(FastBitmapLockFormat.Format32bppRgb); 950 | 951 | fastBitmap.Unlock(); 952 | } 953 | 954 | [TestMethod] 955 | public void TestLockFormatArgbToArgb() 956 | { 957 | var bitmap = new Bitmap(16, 16, PixelFormat.Format32bppArgb); 958 | var fastBitmap = new FastBitmap(bitmap); 959 | 960 | fastBitmap.Lock(FastBitmapLockFormat.Format32bppArgb); 961 | 962 | fastBitmap.Unlock(); 963 | } 964 | 965 | #region Exception Tests 966 | 967 | [TestMethod] 968 | [ExpectedException(typeof(InvalidOperationException), 969 | "When trying to unlock a FastBitmap that is not locked, an exception must be thrown")] 970 | public void TestUnlockWhileUnlockedException() 971 | { 972 | var bitmap = new Bitmap(64, 64); 973 | var fastBitmap = new FastBitmap(bitmap); 974 | 975 | fastBitmap.Unlock(); 976 | } 977 | 978 | [TestMethod] 979 | [ExpectedException(typeof(InvalidOperationException), 980 | "When trying to lock a FastBitmap that is already locked, an exception must be thrown")] 981 | public void TestLockWhileLockedException() 982 | { 983 | var bitmap = new Bitmap(64, 64); 984 | var fastBitmap = new FastBitmap(bitmap); 985 | 986 | fastBitmap.Lock(); 987 | fastBitmap.Lock(); 988 | } 989 | 990 | [TestMethod] 991 | [ExpectedException(typeof(InvalidOperationException), 992 | "When trying to read or write to the FastBitmap via GetPixel(x, y) while it is unlocked, an exception must be thrown" 993 | )] 994 | public void TestGetPixelUnlockedException() 995 | { 996 | var bitmap = new Bitmap(64, 64); 997 | var fastBitmap = new FastBitmap(bitmap); 998 | 999 | fastBitmap.GetPixel(0, 0); 1000 | } 1001 | 1002 | [TestMethod] 1003 | [ExpectedException(typeof(InvalidOperationException), 1004 | "When trying to read or write to the FastBitmap via GetPixelInt(x, y) while it is unlocked, an exception must be thrown" 1005 | )] 1006 | public void TestGetPixelIntUnlockedException() 1007 | { 1008 | var bitmap = new Bitmap(64, 64); 1009 | var fastBitmap = new FastBitmap(bitmap); 1010 | 1011 | fastBitmap.GetPixelInt(0, 0); 1012 | } 1013 | 1014 | [TestMethod] 1015 | [ExpectedException(typeof(InvalidOperationException), 1016 | "When trying to read or write to the FastBitmap via GetPixelUInt(x, y) while it is unlocked, an exception must be thrown" 1017 | )] 1018 | public void TestGetPixelUIntUnlockedException() 1019 | { 1020 | var bitmap = new Bitmap(64, 64); 1021 | var fastBitmap = new FastBitmap(bitmap); 1022 | 1023 | fastBitmap.GetPixelUInt(0, 0); 1024 | } 1025 | 1026 | [TestMethod] 1027 | [ExpectedException(typeof(InvalidOperationException), 1028 | "When trying to read or write to the FastBitmap via GetPixelUInt(index) while it is unlocked, an exception must be thrown" 1029 | )] 1030 | public void TestGetPixelUIntIndexUnlockedException() 1031 | { 1032 | var bitmap = new Bitmap(64, 64); 1033 | var fastBitmap = new FastBitmap(bitmap); 1034 | 1035 | fastBitmap.GetPixelUInt(0); 1036 | } 1037 | 1038 | [TestMethod] 1039 | [ExpectedException(typeof(InvalidOperationException), 1040 | "When trying to read or write to the FastBitmap via SetPixel(x, y) while it is unlocked, an exception must be thrown" 1041 | )] 1042 | public void TestSetPixelUnlockedException() 1043 | { 1044 | var bitmap = new Bitmap(64, 64); 1045 | var fastBitmap = new FastBitmap(bitmap); 1046 | 1047 | fastBitmap.SetPixel(0, 0, 0); 1048 | } 1049 | 1050 | [TestMethod] 1051 | [ExpectedException(typeof(InvalidOperationException), 1052 | "When trying to read or write to the FastBitmap via SetPixel(index) while it is unlocked, an exception must be thrown" 1053 | )] 1054 | public void TestSetPixelIndexUnlockedException() 1055 | { 1056 | var bitmap = new Bitmap(64, 64); 1057 | var fastBitmap = new FastBitmap(bitmap); 1058 | 1059 | fastBitmap.SetPixel(0, 0); 1060 | } 1061 | 1062 | [TestMethod] 1063 | public void TestGetPixelBoundsException() 1064 | { 1065 | var bitmap = new Bitmap(64, 64); 1066 | var fastBitmap = new FastBitmap(bitmap); 1067 | 1068 | fastBitmap.Lock(); 1069 | 1070 | try 1071 | { 1072 | fastBitmap.GetPixel(-1, -1); 1073 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixel(x, y), an exception must be thrown"); 1074 | } 1075 | catch (ArgumentOutOfRangeException) { } 1076 | 1077 | try 1078 | { 1079 | fastBitmap.GetPixel(fastBitmap.Width, 0); 1080 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixel(x, y), an exception must be thrown"); 1081 | } 1082 | catch (ArgumentOutOfRangeException) { } 1083 | 1084 | try 1085 | { 1086 | fastBitmap.GetPixel(0, fastBitmap.Height); 1087 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixel(x, y), an exception must be thrown"); 1088 | } 1089 | catch (ArgumentOutOfRangeException) { } 1090 | 1091 | fastBitmap.GetPixel(fastBitmap.Width - 1, fastBitmap.Height - 1); 1092 | } 1093 | 1094 | [TestMethod] 1095 | public void TestGetPixelIntBoundsException() 1096 | { 1097 | var bitmap = new Bitmap(64, 64); 1098 | var fastBitmap = new FastBitmap(bitmap); 1099 | 1100 | fastBitmap.Lock(); 1101 | 1102 | try 1103 | { 1104 | fastBitmap.GetPixelInt(-1, -1); 1105 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelInt(x, y), an exception must be thrown"); 1106 | } 1107 | catch (ArgumentOutOfRangeException) { } 1108 | 1109 | try 1110 | { 1111 | fastBitmap.GetPixelInt(fastBitmap.Width, 0); 1112 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelInt(x, y), an exception must be thrown"); 1113 | } 1114 | catch (ArgumentOutOfRangeException) { } 1115 | 1116 | try 1117 | { 1118 | fastBitmap.GetPixelInt(0, fastBitmap.Height); 1119 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelInt(x, y), an exception must be thrown"); 1120 | } 1121 | catch (ArgumentOutOfRangeException) { } 1122 | 1123 | fastBitmap.GetPixelInt(fastBitmap.Width - 1, fastBitmap.Height - 1); 1124 | } 1125 | 1126 | [TestMethod] 1127 | public void TestGetPixelUIntBoundsException() 1128 | { 1129 | var bitmap = new Bitmap(64, 64); 1130 | var fastBitmap = new FastBitmap(bitmap); 1131 | 1132 | fastBitmap.Lock(); 1133 | 1134 | try 1135 | { 1136 | fastBitmap.GetPixelUInt(-1, -1); 1137 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelUInt(x, y), an exception must be thrown"); 1138 | } 1139 | catch (ArgumentOutOfRangeException) { } 1140 | 1141 | try 1142 | { 1143 | fastBitmap.GetPixelUInt(fastBitmap.Width, 0); 1144 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelUInt(x, y), an exception must be thrown"); 1145 | } 1146 | catch (ArgumentOutOfRangeException) { } 1147 | 1148 | try 1149 | { 1150 | fastBitmap.GetPixelUInt(0, fastBitmap.Height); 1151 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelUInt(x, y), an exception must be thrown"); 1152 | } 1153 | catch (ArgumentOutOfRangeException) { } 1154 | 1155 | fastBitmap.GetPixelUInt(fastBitmap.Width - 1, fastBitmap.Height - 1); 1156 | } 1157 | 1158 | [TestMethod] 1159 | public void TestGetPixelUIntIndexBoundsException() 1160 | { 1161 | var bitmap = new Bitmap(64, 64); 1162 | var fastBitmap = new FastBitmap(bitmap); 1163 | 1164 | fastBitmap.Lock(); 1165 | 1166 | try 1167 | { 1168 | fastBitmap.GetPixelUInt(-1); 1169 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelUInt(index), an exception must be thrown"); 1170 | } 1171 | catch (ArgumentOutOfRangeException) { } 1172 | 1173 | try 1174 | { 1175 | fastBitmap.GetPixelUInt(fastBitmap.Height * fastBitmap.Stride); 1176 | Assert.Fail("When trying to access a coordinate that is out of bounds via GetPixelUInt(index), an exception must be thrown"); 1177 | } 1178 | catch (ArgumentOutOfRangeException) { } 1179 | 1180 | fastBitmap.GetPixelUInt(fastBitmap.Height * fastBitmap.Stride - 1); 1181 | } 1182 | 1183 | [TestMethod] 1184 | public void TestSetPixelBoundsException() 1185 | { 1186 | var bitmap = new Bitmap(64, 64); 1187 | var fastBitmap = new FastBitmap(bitmap); 1188 | 1189 | fastBitmap.Lock(); 1190 | 1191 | try 1192 | { 1193 | fastBitmap.SetPixel(-1, -1, 0); 1194 | Assert.Fail("When trying to access a coordinate that is out of bounds via SetPixel, an exception must be thrown"); 1195 | } 1196 | catch (ArgumentOutOfRangeException) { } 1197 | 1198 | try 1199 | { 1200 | fastBitmap.SetPixel(fastBitmap.Width, 0, 0); 1201 | Assert.Fail("When trying to access a coordinate that is out of bounds via SetPixel, an exception must be thrown"); 1202 | } 1203 | catch (ArgumentOutOfRangeException) { } 1204 | 1205 | try 1206 | { 1207 | fastBitmap.SetPixel(0, fastBitmap.Height, 0); 1208 | Assert.Fail("When trying to access a coordinate that is out of bounds via SetPixel, an exception must be thrown"); 1209 | } 1210 | catch (ArgumentOutOfRangeException) { } 1211 | 1212 | try 1213 | { 1214 | fastBitmap.SetPixel(fastBitmap.Height * fastBitmap.Stride, 0); 1215 | Assert.Fail("When trying to access a coordinate that is out of bounds via SetPixel, an exception must be thrown"); 1216 | } 1217 | catch (ArgumentOutOfRangeException) { } 1218 | 1219 | fastBitmap.SetPixel(fastBitmap.Width - 1, fastBitmap.Height - 1, 0); 1220 | fastBitmap.SetPixel(fastBitmap.Height * fastBitmap.Stride - 1, 0); 1221 | } 1222 | 1223 | [TestMethod] 1224 | [ExpectedException(typeof(ArgumentException), "An ArgumentException exception must be thrown when trying to copy regions across the same bitmap")] 1225 | public void TestSameBitmapCopyRegionException() 1226 | { 1227 | var bitmap = new Bitmap(64, 64); 1228 | 1229 | var sourceRectangle = new Rectangle(0, 0, 64, 64); 1230 | var targetRectangle = new Rectangle(0, 0, 64, 64); 1231 | 1232 | FastBitmap.CopyRegion(bitmap, bitmap, sourceRectangle, targetRectangle); 1233 | } 1234 | 1235 | [TestMethod] 1236 | [ExpectedException(typeof(ArgumentException), 1237 | "an ArgumentException exception must be raised when calling CopyFromArray() with an array of colors that does not match the pixel count of the bitmap")] 1238 | public void TestCopyFromArrayMismatchedLengthException() 1239 | { 1240 | var bitmap = new Bitmap(4, 4); 1241 | 1242 | FillBitmapRegion(bitmap, new Rectangle(0, 0, bitmap.Width, bitmap.Height), Color.Red); 1243 | 1244 | int[] colors = 1245 | { 1246 | 0xFFFFFF, 0xFFFFEF, 0xABABAB, 0xABCDEF, 1247 | 0x111111, 0x123456, 0x654321, 0x000000, 1248 | 0x000000, 0xFFFFEF, 0x000000, 0xABCDEF, 1249 | 0x000000, 0x000000, 0x654321, 0x000000, 1250 | 0x000000, 0x000000, 0x654321, 0x000000 1251 | }; 1252 | 1253 | using (var fastBitmap = bitmap.FastLock()) 1254 | { 1255 | fastBitmap.CopyFromArray(colors, true); 1256 | } 1257 | } 1258 | 1259 | #endregion 1260 | 1261 | /// 1262 | /// Generates a frame image with a given set of parameters. 1263 | /// The seed is used to randomize the frame, and any call with the same width, height and seed will generate the same image 1264 | /// 1265 | /// The width of the image to generate 1266 | /// The height of the image to generate 1267 | /// The seed for the image, used to seed the random number generator that will generate the image contents 1268 | /// An image with the passed parameters 1269 | public static Bitmap GenerateRainbowBitmap(int width, int height, int seed = 0) 1270 | { 1271 | uint RainbowRgb(float hue) 1272 | { 1273 | // |_ _|__ 1 1274 | // Red: | \__/ |__ 0 1275 | // |______| 1276 | // | __ |__ 1 1277 | // Green: |/ \__|__ 0 1278 | // |______| 1279 | // | __ |__ 1 1280 | // Blue: |__/ \|__ 0 1281 | // |_.__._| 1282 | // 0 | | 1 1283 | // 1/3 | 1284 | // 2/3 1285 | 1286 | float r; 1287 | float g; 1288 | float b; 1289 | if (hue < 1 / 3.0f) 1290 | { 1291 | r = 2 - hue * 6; 1292 | g = hue * 6; 1293 | b = 0; 1294 | } 1295 | else if (hue < 2 / 3.0f) 1296 | { 1297 | r = 0; 1298 | g = 4 - hue * 6; 1299 | b = hue * 6 - 2; 1300 | } 1301 | else 1302 | { 1303 | r = hue * 6 - 4; 1304 | g = 0; 1305 | b = (1 - hue) * 6; 1306 | } 1307 | 1308 | if (r > 1) r = 1; 1309 | if (g > 1) g = 1; 1310 | if (b > 1) b = 1; 1311 | 1312 | uint rInt = (uint) (r * 255); 1313 | uint gInt = (uint) (g * 255); 1314 | uint bInt = (uint) (b * 255); 1315 | 1316 | return ((uint)0xFF << 24) | ((rInt & 0xFF) << 16) | ((gInt & 0xFF) << 8) | (bInt & 0xFF); 1317 | } 1318 | 1319 | var bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb); 1320 | var fastBitmap = new FastBitmap(bitmap); 1321 | fastBitmap.Lock(); 1322 | for (int y = 0; y < height; y++) 1323 | { 1324 | for (int x = 0; x < width; x++) 1325 | { 1326 | uint pixelColor = RainbowRgb((float)x / width); 1327 | 1328 | fastBitmap.SetPixel(x, y, pixelColor); 1329 | } 1330 | } 1331 | fastBitmap.Unlock(); 1332 | return bitmap; 1333 | } 1334 | 1335 | /// 1336 | /// Fills a rectangle region of bitmap with a specified color 1337 | /// 1338 | /// The bitmap to operate on 1339 | /// The region to fill on the bitmap 1340 | /// The color to fill the bitmap with 1341 | public static void FillBitmapRegion([NotNull] Bitmap bitmap, Rectangle region, Color color) 1342 | { 1343 | for (int y = Math.Max(0, region.Top); y < Math.Min(bitmap.Height, region.Bottom); y++) 1344 | { 1345 | for (int x = Math.Max(0, region.Left); x < Math.Min(bitmap.Width, region.Right); x++) 1346 | { 1347 | bitmap.SetPixel(x, y, color); 1348 | } 1349 | } 1350 | } 1351 | 1352 | /// 1353 | /// Helper method that tests the equality of two bitmaps and fails with a provided assert message when they are not pixel-by-pixel equal 1354 | /// 1355 | /// The first bitmap object to compare 1356 | /// The second bitmap object to compare 1357 | /// The message to display when the comparision fails 1358 | public static void AssertBitmapEquals([NotNull] Bitmap bitmap1, [NotNull] Bitmap bitmap2, string message = "") 1359 | { 1360 | if (bitmap1.PixelFormat != bitmap2.PixelFormat) 1361 | Assert.Fail(message); 1362 | 1363 | for (int y = 0; y < bitmap1.Height; y++) 1364 | { 1365 | for (int x = 0; x < bitmap1.Width; x++) 1366 | { 1367 | Assert.AreEqual(bitmap1.GetPixel(x, y).ToArgb(), bitmap2.GetPixel(x, y).ToArgb(), message); 1368 | } 1369 | } 1370 | } 1371 | 1372 | public TestContext TestContext { get; set; } 1373 | } 1374 | } -------------------------------------------------------------------------------- /FastBitmapTests/FastBitmapTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net452 5 | false 6 | 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /FastBitmapTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("FastBitmapTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("FastBitmapTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("4d3a1905-de3d-4ec2-96a7-7f3d78b14d51")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestClippingCopyRegion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuizZak/FastBitmap/a52e23f0be5c4153c671e57a98e41cd8ed6d1fc9/FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestClippingCopyRegion.png -------------------------------------------------------------------------------- /FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestComplexCopyRegion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuizZak/FastBitmap/a52e23f0be5c4153c671e57a98e41cd8ed6d1fc9/FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestComplexCopyRegion.png -------------------------------------------------------------------------------- /FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestInvalidCopyRegion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuizZak/FastBitmap/a52e23f0be5c4153c671e57a98e41cd8ed6d1fc9/FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestInvalidCopyRegion.png -------------------------------------------------------------------------------- /FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestOutOfBoundsCopyRegion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuizZak/FastBitmap/a52e23f0be5c4153c671e57a98e41cd8ed6d1fc9/FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestOutOfBoundsCopyRegion.png -------------------------------------------------------------------------------- /FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestSimpleCopyRegion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuizZak/FastBitmap/a52e23f0be5c4153c671e57a98e41cd8ed6d1fc9/FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestSimpleCopyRegion.png -------------------------------------------------------------------------------- /FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestSlicedDestinationCopyRegion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuizZak/FastBitmap/a52e23f0be5c4153c671e57a98e41cd8ed6d1fc9/FastBitmapTests/Snapshot/Files/FastBitmapTests.FastBitmapTests/TestSlicedDestinationCopyRegion.png -------------------------------------------------------------------------------- /FastBitmapTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Luiz Fernando 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FastBitmap - The Fast C# Bitmap Layer 2 | ===================================== 3 | Based on work found on Visual C# Kicks: http://www.vcskicks.com/fast-image-processing.php 4 | 5 | [![Build status](https://ci.appveyor.com/api/projects/status/fwt610ekt3knglp3?svg=true)](https://ci.appveyor.com/project/LuizZak/fastbitmap) 6 | [![codecov](https://codecov.io/gh/LuizZak/FastBitmap/branch/master/graph/badge.svg)](https://codecov.io/gh/LuizZak/FastBitmap) 7 | 8 | FastBitmap is a bitmap wrapper class that intends to provide fast bitmap read/write operations on top of a safe layer of abstraction. 9 | 10 | It provides operations for setting/getting pixel colors, copying regions accross bitmaps, clearing whole bitmaps, and copying whole bitmaps. 11 | 12 | Editing pixels of a bitmap is just as easy as: 13 | 14 | ```C# 15 | Bitmap bitmap = new Bitmap(64, 64); 16 | 17 | using(var fastBitmap = bitmap.FastLock()) 18 | { 19 | // Do your changes here... 20 | fastBitmap.Clear(Color.White); 21 | fastBitmap.SetPixel(1, 1, Color.Red); 22 | } 23 | ``` 24 | 25 | Or alternatively, albeit longer: 26 | 27 | ```C# 28 | Bitmap bitmap = new Bitmap(64, 64); 29 | FastBitmap fastBitmap = new FastBitmap(bitmap); 30 | 31 | // Locking bitmap before doing operations 32 | fastBitmap.Lock(); 33 | 34 | // Do your changes here... 35 | fastBitmap.Clear(Color.White); 36 | fastBitmap.SetPixel(1, 1, Color.Red); 37 | 38 | // Don't forget to unlock! 39 | fastBitmap.Unlock(); 40 | ``` 41 | 42 | This project contains the FastBitmap class and acompanying unit test suite. 43 | Note that to compile this class you must have the /unsafe compiler flag turned on in your project settings. 44 | 45 | **Note: This code currently only works with 32bpp bitmaps** 46 | 47 | Installation 48 | --- 49 | 50 | FastBitmap is available as a [Nuget package](https://www.nuget.org/packages/FastBitmapLib): 51 | 52 | ``` 53 | PM > Install-Package FastBitmapLib 54 | ``` 55 | 56 | Speed 57 | ------ 58 | 59 | The following profiling tests showcase comparisions with the native System.Drawing.Bitmap equivalent method calls 60 | 61 | **SetPixel profiling** 62 | 1024 x 1024 Bitmap SetPixel: 2054ms 63 | 1024 x 1024 FastBitmap SetPixel: 398ms 64 | 1024 x 1024 FastBitmap Int SetPixel: 331ms 65 | 66 | Results: FastBitmap **6,21x** faster 67 | 68 | **GetPixel profiling** 69 | 1024 x 1024 Bitmap GetPixel: 1498ms 70 | 1024 x 1024 FastBitmap GetPixel: 382ms 71 | 1024 x 1024 FastBitmap Int GetPixel: 327ms 72 | 73 | Results: FastBitmap **4,58x** faster 74 | 75 | **Bitmap copying profiling** 76 | 1024 x 1024 Bitmap SetPixel: 2888ms 77 | 1024 x 1024 FastBitmap CopyPixels: 5ms 78 | 79 | Results: FastBitmap **577,60x** faster 80 | 81 | **Bitmap clearing profiling** 82 | 1024 x 1024 Bitmap SetPixel: 1795ms 83 | 1024 x 1024 FastBitmap Clear: 5ms 84 | 85 | Results: FastBitmap **359,00x** faster 86 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | after_test: 2 | - .\packages\OpenCover.4.6.519\tools\OpenCover.Console.exe -register:user -target:"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\MSTest.exe" -targetargs:"/noresults /noisolation /testcontainer:"".\FastBitmapTests\bin\Debug\FastBitmapTests.dll" -filter:"+[FastBitmapLib]* -[FastBitmapLib]FastBitmap.Properties.*" -excludebyattribute:*.ExcludeFromCodeCoverage* -hideskipped:All -output:.\FastBitmap_coverage.xml 3 | - "SET PATH=C:\\Python34;C:\\Python34\\Scripts;%PATH%" 4 | - pip install codecov 5 | - codecov -f "FastBitmap_coverage.xml" --------------------------------------------------------------------------------