Building Image Unit Tests
Atalasoft makes heavy use on unit testing in our development. While we don’t follow the strict regimen of writing the test first, we do write the tests in close conjunction with code being developed. Similarly, we try for every bug report to have a unit test that reproduces it.
As a result, we also have a tremendously large (and growing) image database that includes both in and out of spec images for testing.
Sometimes you just want a small image with particular contents and it can be a hassle to make these sample images (think MS Paint at full magnification). Instead, I decided to put together a little chunk of code to do that for me:
AtalaImage FromStringArray(string[] bits);
before I go into the implementation (and I will), let me show you the typical usage (pulled from a unit test)":
string[] im = new string[] {
"################",
"+++++++++++++++#",
"##############+#",
"#++++++++++++#+#",
"#+##########+#+#",
"#+#++++++++#+#+#",
"#+#+######+#+#+#",
"#+#+#+#++#+#+#+#",
"#+#+#+#++#+#+#+#",
"#+#+#+####+#+#+#",
"#+#+#++++++#+#+#",
"#+#+########+#+#",
"#+#++++++++++#+#",
"#+############+#",
"#++++++++++++++#",
"################",
};
AtalaImage image = FromStringArray(im);
This chunk of code will take the array of strings and create a 1 bit AtalaImage with every + representing a white pixel and every # representing a black pixel. And before you can say “Holy ASCII art, Batman!” we have a simple way of authoring small images for doing highly specific tests.
The process is simple – find the dimensions of the target image, construct it, then fill in the pixels.
AtalaImage FromStringArray(string[] bits)
{
int height = bits.Length;
int width = bits[0].Length;
AtalaImage image = new AtalaImage(width, height, PixelFormat.Pixel1bppIndexed);
for (int y = 0; y < height; y++)
{
string s = bits[y];
for (int x = 0; x < Math.Min(s.Length, width); x++)
{
if (s[x] == '+')
{
SetPixel(image, x, y, false);
}
else if (s[x] == '#')
{
SetPixel(image, x, y, true);
}
else
{
Assert.Fail();
}
}
}
return image;
}
There are a couple of points to note – I assume the width of the image is the width of the first string. From there on out, for each string I only scan up to the lesser of the width the given string. This lets the unit test author be a little sloppy in the strings, but it may result it images that are clipped if the first string is particularly short. This laissez-faire approach is at odds with the Assert.Fail() if an unrecognized character is in the string. I don’t particularly like that philosophical inconsistency, but so be it. One could also argue that the better approach to getting the width is to instead use the maximum length of all the strings or to fail if they’re not all the same length.
In addition, you’ll see that I’m specifically NOT using AtalaImage.SetPixelColor(). I could – and that wouldn’t be so bad, but SetPixelColor does have a certain amount of overhead, and I also like to keep my PixelAccessor skills up to date, so I wrote a version:
byte[] _bits = { 0x80, 0x40, 0x20, 0x10, 0x8, 0x4, 0x2, 0x1 };
void SetPixel(AtalaImage image, int x, int y, bool set)
{
if (image.PixelFormat != PixelFormat.Pixel1bppIndexed)
Assert.Fail();
using (PixelAccessor pa = image.PixelMemory.AcquirePixelAccessor())
{
byte[] row = pa.AcquireScanline(y);
int index = x >> 3;
if (set)
{
row[index] |= _bits[(x & 7)];
}
else
{
row[index] &= (byte)~_bits[(x & 7)];
}
pa.ReleaseScanline();
}
}
This is a fairly low overhead way to set pixels in a 1-bit image. Of course, it is not production code – there is no range checking on x and y (AcquireScanline will throw and the array access will throw) and the code doesn’t use the colors in the image’s palette to determine whether it should set or clear the pixel. This code will work with the default palette (which is the problem domain), but not with the most general cases. All of these cases are handled by SetPixelColor.
If nothing else, this is a lesson in what not to do and why, but again in this problem domain I think it’s useful to see how one could implement SetPixel using the tools built into dotImage.
In my case, I’m doing some research code which involves a lot of funny edge cases which can be broken out in unit tests much more easily with this tool than otherwise. It also keeps the source images immune to changes in either images or codecs, which can be very handy.