├── .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 | [](https://ci.appveyor.com/project/LuizZak/fastbitmap)
6 | [](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"
--------------------------------------------------------------------------------