-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added basic tests for
BitMask
(#278)
* Sealed `BitMask` class and changed existing comments to documentation comments * Added checks to BitMask width and height arguments. I was undecided if 0 was or not a valid value, but I decided it is based on how Cairo does things. See: https://cgit.freedesktop.org/cairo/tree/src/cairo-image-surface.c in function _cairo_image_surface_is_size_valid * Added basic tests to `BitMask` --------- Co-authored-by: Lehonti Ramos <john@doe>
- Loading branch information
Showing
2 changed files
with
177 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,69 +1,73 @@ | ||
using System; | ||
using System.Collections; | ||
|
||
namespace Pinta.Core | ||
namespace Pinta.Core; | ||
|
||
/// <summary> | ||
/// Represents a two-dimensional matrix of bits used to store a | ||
/// true/false value for each pixel in an image. | ||
/// </summary> | ||
public sealed class BitMask | ||
{ | ||
// Represent a two dimensional matrix of bits used to store a | ||
// true/false value for each pixel in an image. | ||
public class BitMask | ||
private readonly BitArray array; | ||
|
||
public BitMask (int width, int height) | ||
{ | ||
private readonly BitArray array; | ||
if (width < 0) | ||
throw new ArgumentOutOfRangeException (nameof (width)); | ||
if (height < 0) | ||
throw new ArgumentOutOfRangeException (nameof (height)); | ||
|
||
public BitMask (int width, int height) | ||
{ | ||
Width = width; | ||
Height = height; | ||
Width = width; | ||
Height = height; | ||
|
||
array = new BitArray (width * height); | ||
} | ||
|
||
public bool this[PointI pt] { | ||
get => this[pt.X, pt.Y]; | ||
set => this[pt.X, pt.Y] = value; | ||
} | ||
array = new BitArray (width * height); | ||
} | ||
|
||
public bool this[int x, int y] { | ||
get => Get (x, y); | ||
set => Set (x, y, value); | ||
} | ||
public bool this[PointI pt] { | ||
get => this[pt.X, pt.Y]; | ||
set => this[pt.X, pt.Y] = value; | ||
} | ||
|
||
public int Width { get; } | ||
public bool this[int x, int y] { | ||
get => Get (x, y); | ||
set => Set (x, y, value); | ||
} | ||
|
||
public int Height { get; } | ||
public int Width { get; } | ||
|
||
public bool IsEmpty => array.Length == 0; | ||
public int Height { get; } | ||
|
||
public void Clear (bool newValue) => array.SetAll (newValue); | ||
public bool IsEmpty => array.Length == 0; | ||
|
||
public bool Get (int x, int y) => array.Get (GetIndex (x, y)); | ||
public void Clear (bool newValue) => array.SetAll (newValue); | ||
|
||
public void Invert (int x, int y) | ||
{ | ||
var index = GetIndex (x, y); | ||
public bool Get (int x, int y) => array.Get (GetIndex (x, y)); | ||
|
||
array[index] = !array[index]; | ||
} | ||
public void Invert (int x, int y) | ||
{ | ||
var index = GetIndex (x, y); | ||
|
||
public void Invert (Scanline scan) | ||
{ | ||
var x = scan.X; | ||
array[index] = !array[index]; | ||
} | ||
|
||
while (x < scan.X + scan.Length) { | ||
Invert (x, scan.Y); | ||
++x; | ||
} | ||
public void Invert (Scanline scan) | ||
{ | ||
var x = scan.X; | ||
while (x < scan.X + scan.Length) { | ||
Invert (x, scan.Y); | ||
++x; | ||
} | ||
} | ||
|
||
public void Set (int x, int y, bool newValue) => array[GetIndex (x, y)] = newValue; | ||
|
||
public void Set (RectangleI rect, bool newValue) | ||
{ | ||
for (var y = rect.Y; y <= rect.Bottom; ++y) { | ||
for (var x = rect.X; x <= rect.Right; ++x) { | ||
Set (x, y, newValue); | ||
} | ||
} | ||
} | ||
public void Set (int x, int y, bool newValue) => array[GetIndex (x, y)] = newValue; | ||
|
||
private int GetIndex (int x, int y) => (y * Width) + x; | ||
public void Set (RectangleI rect, bool newValue) | ||
{ | ||
for (var y = rect.Y; y <= rect.Bottom; ++y) | ||
for (var x = rect.X; x <= rect.Right; ++x) | ||
Set (x, y, newValue); | ||
} | ||
|
||
private int GetIndex (int x, int y) => (y * Width) + x; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using NUnit.Framework; | ||
|
||
namespace Pinta.Core.Tests; | ||
|
||
[TestFixture] | ||
internal sealed class BitMaskTest | ||
{ | ||
const int DEFAULT_SIZE = 1; | ||
const int DEFAULT_OFFSET = 0; | ||
|
||
const int DEFAULT_HEIGHT = DEFAULT_SIZE; | ||
const int DEFAULT_HEIGHT_INDEX = DEFAULT_OFFSET; | ||
|
||
const int DEFAULT_WIDTH = DEFAULT_SIZE; | ||
const int DEFAULT_WIDTH_INDEX = DEFAULT_OFFSET; | ||
|
||
[TestCase (-1)] | ||
public void Constructor_RejectsInvalidWidth (int width) | ||
{ | ||
Assert.Throws<ArgumentOutOfRangeException> (() => new BitMask (width, DEFAULT_HEIGHT)); | ||
} | ||
|
||
[TestCase (-1)] | ||
public void Constructor_RejectsInvalidHeight (int height) | ||
{ | ||
Assert.Throws<ArgumentOutOfRangeException> (() => new BitMask (DEFAULT_WIDTH, height)); | ||
} | ||
|
||
[TestCaseSource (nameof (out_of_bounds_access_cases))] | ||
public void WidthAccessOutOfBoundsFails (int desiredWidth, int indexToAccess) | ||
{ | ||
var mask = new BitMask (desiredWidth, DEFAULT_HEIGHT); | ||
var coordinates = new PointI (indexToAccess, DEFAULT_HEIGHT_INDEX); | ||
Assert.Throws<ArgumentOutOfRangeException> (() => _ = mask[indexToAccess, DEFAULT_HEIGHT_INDEX]); | ||
Assert.Throws<ArgumentOutOfRangeException> (() => _ = mask[coordinates]); | ||
} | ||
|
||
[TestCaseSource (nameof (within_bounds_access_cases))] | ||
public void WidthAccessWithinBoundsSucceeds (int desiredWidth, int indexToAccess) | ||
{ | ||
var mask = new BitMask (desiredWidth, DEFAULT_HEIGHT); | ||
var coordinates = new PointI (indexToAccess, DEFAULT_HEIGHT_INDEX); | ||
Assert.DoesNotThrow (() => _ = mask[indexToAccess, DEFAULT_HEIGHT_INDEX]); | ||
Assert.DoesNotThrow (() => _ = mask[coordinates]); | ||
} | ||
|
||
[TestCaseSource (nameof (out_of_bounds_access_cases))] | ||
public void HeightAccessOutOfBoundsFails (int desiredHeight, int indexToAccess) | ||
{ | ||
var mask = new BitMask (DEFAULT_WIDTH, desiredHeight); | ||
var coordinates = new PointI (DEFAULT_WIDTH_INDEX, indexToAccess); | ||
Assert.Throws<ArgumentOutOfRangeException> (() => _ = mask[DEFAULT_WIDTH_INDEX, indexToAccess]); | ||
Assert.Throws<ArgumentOutOfRangeException> (() => _ = mask[coordinates]); | ||
} | ||
|
||
[TestCaseSource (nameof (within_bounds_access_cases))] | ||
public void HeightAccessWithinBoundsSucceeds (int desiredHeight, int indexToAccess) | ||
{ | ||
var mask = new BitMask (DEFAULT_WIDTH, desiredHeight); | ||
var coordinates = new PointI (DEFAULT_WIDTH_INDEX, indexToAccess); | ||
Assert.DoesNotThrow (() => _ = mask[DEFAULT_WIDTH_INDEX, indexToAccess]); | ||
Assert.DoesNotThrow (() => _ = mask[coordinates]); | ||
} | ||
|
||
[TestCase (DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_WIDTH_INDEX, DEFAULT_HEIGHT_INDEX)] | ||
public void BitInitializedToFalse (int maskWidth, int maskHeight, int bitToTestX, int bitToTestY) | ||
{ | ||
var mask = new BitMask (maskWidth, maskHeight); | ||
var bit = mask[bitToTestX, bitToTestY]; | ||
Assert.IsFalse (bit); | ||
} | ||
|
||
[TestCase (DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_WIDTH_INDEX, DEFAULT_HEIGHT_INDEX)] | ||
public void BitInvertsWithXY (int maskWidth, int maskHeight, int bitToInvertX, int bitToInvertY) | ||
{ | ||
var mask = new BitMask (maskWidth, maskHeight); | ||
mask.Invert (bitToInvertX, bitToInvertY); | ||
var bit = mask[bitToInvertX, bitToInvertY]; | ||
Assert.IsTrue (bit); | ||
} | ||
|
||
[TestCase (DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_WIDTH_INDEX, DEFAULT_HEIGHT_INDEX)] | ||
public void BitInvertsWithScanline (int maskWidth, int maskHeight, int bitToInvertX, int bitToInvertY) | ||
{ | ||
var mask = new BitMask (maskWidth, maskHeight); | ||
var scanline = new Scanline (bitToInvertX, bitToInvertY, 1); | ||
mask.Invert (scanline); | ||
var bit = mask[bitToInvertX, bitToInvertY]; | ||
Assert.IsTrue (bit); | ||
} | ||
|
||
[TestCase (DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_WIDTH_INDEX, DEFAULT_HEIGHT_INDEX, new[] { true, false, true, false })] | ||
public void BitGetsSetXY (int maskWidth, int maskHeight, int bitToSetX, int bitToSetY, bool[] valuesToSetAndTest) | ||
{ | ||
var mask = new BitMask (maskWidth, maskHeight); | ||
var coordinates = new PointI (bitToSetX, bitToSetY); | ||
foreach (var value in valuesToSetAndTest) { | ||
mask.Set (bitToSetX, bitToSetY, value); | ||
Assert.AreEqual (value, mask[bitToSetX, bitToSetY]); | ||
Assert.AreEqual (value, mask[coordinates]); | ||
} | ||
} | ||
|
||
static readonly IReadOnlyList<TestCaseData> out_of_bounds_access_cases = new TestCaseData[] | ||
{ | ||
new (0, 0), | ||
new (0, 1), | ||
new (1, 1), | ||
new (1, 2), | ||
new (1, -1), | ||
new (1, int.MinValue), | ||
new (1, int.MinValue + 1), | ||
new (1, int.MaxValue), | ||
new (1, int.MaxValue - 1), | ||
new (2, 2), | ||
}; | ||
|
||
static readonly IReadOnlyList<TestCaseData> within_bounds_access_cases = new TestCaseData[] | ||
{ | ||
new (DEFAULT_SIZE, DEFAULT_OFFSET), | ||
new (2, 1), | ||
}; | ||
} |