private static bool Validate(Bitmap bitmap, ExpectedPixels expectation, StringBuilder report) { var failMessage = default(string); for (var offsetX = 0; offsetX <= expectation.OffsetTolerance.x; offsetX++) { for (var offsetY = 0; offsetY <= expectation.OffsetTolerance.y; offsetY++) { if (ValidatePixels(offsetX, offsetY) || (offsetX > 0 && ValidatePixels(-offsetX, offsetY)) || (offsetX > 0 && offsetY > 0 && ValidatePixels(-offsetX, -offsetY)) || (offsetY > 0 && ValidatePixels(offsetX, -offsetY))) { report.AppendLine("OK"); return(true); } } } report.AppendLine(failMessage); return(false); bool ValidatePixels(int offsetX, int offsetY) { var isSuccess = true; var result = new StringBuilder(); for (var lin = 0; lin < expectation.Values.GetLength(0); lin++) { for (var col = 0; col < expectation.Values.GetLength(1); col++) { var expectedColor = expectation.Values[lin, col]; if (expectedColor.IsEmpty) { continue; } var pixelX = expectation.X + col + offsetX; var pixelY = expectation.Y + lin + offsetY; var pixel = bitmap.GetPixel(pixelX, pixelY); var expected = ToArgbCode(expectedColor); var actual = ToArgbCode(pixel); if (!AreSameColor(expectedColor, pixel, expectation.ColorTolerance)) { isSuccess = false; result.AppendLine($"{col},{lin}: [{pixelX},{pixelY}] (expected: {expected} | actual: {actual})"); } } } if (failMessage == default) // so we keep only for offset 0,0 { failMessage = result.ToString(); } return(isSuccess); } }
private static bool Validate(ExpectedPixels expectation, Bitmap actualBitmap, double expectedToActualScale, StringBuilder report) { report?.AppendLine($"{expectation.Name}:"); bool isSuccess; switch (expectation.Tolerance.OffsetKind) { case LocationToleranceKind.PerRange: isSuccess = GetLocationOffsets(expectation) .Any(offset => GetPixelCoordinates(expectation) .All(pixel => ValidatePixel(actualBitmap, expectation, expectedToActualScale, pixel, offset, report))); break; case LocationToleranceKind.PerPixel: isSuccess = GetPixelCoordinates(expectation) .All(pixel => GetLocationOffsets(expectation) .Any(offset => ValidatePixel(actualBitmap, expectation, expectedToActualScale, pixel, offset, report))); break; default: throw new ArgumentOutOfRangeException(nameof(expectation.Tolerance.OffsetKind)); } if (isSuccess) // otherwise the report has already been full-filled { report?.AppendLine("\tOK"); } return(isSuccess); }
private static void AreEqualImpl( FileInfo expected, Rectangle expectedRect, FileInfo actual, Bitmap actualBitmap, Rectangle actualRect, double expectedToActualScale, PixelTolerance tolerance, int line) { if (expectedRect != FirstQuadrant && actualRect != FirstQuadrant) { Assert.AreEqual(expectedRect.Size, actualRect.Size, WithContext("Compare rects don't have the same size")); } using (var expectedBitmap = new Bitmap(expected.FullName)) { if (expectedRect == FirstQuadrant && actualRect == FirstQuadrant) { var effectiveExpectedBitmapSize = new Size( (int)(expectedBitmap.Size.Width * expectedToActualScale), (int)(expectedBitmap.Size.Height * expectedToActualScale)); Assert.AreEqual(effectiveExpectedBitmapSize, actualBitmap.Size, WithContext("Screenshots don't have the same size")); } expectedRect = Normalize(expectedRect, expectedBitmap.Size); actualRect = Normalize(actualRect, actualBitmap.Size); var expectedPixels = ExpectedPixels .At(actualRect.Location) .Pixels(expectedBitmap, expectedRect) .Named(expected.Name) .WithTolerance(tolerance); var report = GetContext(); if (Validate(expectedPixels, actualBitmap, expectedToActualScale, report)) { Console.WriteLine(report.ToString()); } else { Assert.Fail(report.ToString()); } } StringBuilder GetContext() => new StringBuilder() .AppendLine($"ImageAssert.AreEqual @ line {line}") .AppendLine("pixelTolerance: " + tolerance) .AppendLine("expected: " + expected?.Name + (expectedRect == FirstQuadrant ? null : $" in {expectedRect}")) .AppendLine("actual : " + (actual?.Name ?? "--unknown--") + (actualRect == FirstQuadrant ? null : $" in {actualRect}")) .AppendLine("===================="); string WithContext(string message) => GetContext() .AppendLine(message) .ToString(); }
private static IEnumerable <Point> GetPixelCoordinates(ExpectedPixels expectation) { var stepX = (int)Math.Max(1, expectation.Tolerance.DiscreteValidation.x); var stepY = (int)Math.Max(1, expectation.Tolerance.DiscreteValidation.y); for (var lin = 0; lin < expectation.Values.GetLength(0); lin += stepY) { for (var col = 0; col < expectation.Values.GetLength(1); col += stepX) { yield return(new Point(col, lin)); } } }
private static (bool areEqual, string context) EqualityCheck( ScreenshotInfo expected, Rectangle expectedRect, ScreenshotInfo actual, Bitmap actualBitmap, Rectangle actualRect, double expectedToActualScale, PixelTolerance tolerance, [CallerLineNumber] int line = 0) { if (expectedRect != FirstQuadrant && actualRect != FirstQuadrant) { Assert.AreEqual(expectedRect.Size, actualRect.Size, WithContext("Compare rects don't have the same size")); } using (var expectedBitmap = new Bitmap(expected.File.FullName)) { if (expectedRect == FirstQuadrant && actualRect == FirstQuadrant) { var effectiveExpectedBitmapSize = new Size( (int)(expectedBitmap.Size.Width * expectedToActualScale), (int)(expectedBitmap.Size.Height * expectedToActualScale)); Assert.AreEqual(effectiveExpectedBitmapSize, actualBitmap.Size, WithContext("Screenshots don't have the same size")); } expectedRect = Normalize(expectedRect, expectedBitmap.Size); actualRect = Normalize(actualRect, actualBitmap.Size); var expectedPixels = ExpectedPixels .At(actualRect.Location) .Pixels(expectedBitmap, expectedRect) .Named(expected.StepName) .WithTolerance(tolerance); var report = GetContext(); var result = Validate(expectedPixels, actualBitmap, expectedToActualScale, report); return(result, report.ToString()); } StringBuilder GetContext() => new StringBuilder() .AppendLine($"ImageAssert.AreEqual @ line {line}") .AppendLine("pixelTolerance: " + tolerance) .AppendLine($"expected: {expected?.StepName} ({expected?.File.Name}){(expectedRect == FirstQuadrant ? null : $" in {expectedRect}")}")
private static IEnumerable <(int x, int y)> GetLocationOffsets(ExpectedPixels expectation) { for (var offsetX = 0; offsetX <= expectation.Tolerance.Offset.x; offsetX++) { for (var offsetY = 0; offsetY <= expectation.Tolerance.Offset.y; offsetY++) { yield return(offsetX, offsetY); if (offsetX > 0) { yield return(-offsetX, offsetY); } if (offsetY > 0) { yield return(offsetX, -offsetY); } if (offsetX > 0 && offsetY > 0) { yield return(-offsetX, -offsetY); } } } }
private static bool Validate(Bitmap bitmap, ExpectedPixels expectation, StringBuilder report) { var failMessage = default(string); for (var offsetX = 0; offsetX <= expectation.OffsetTolerance.x; offsetX++) { for (var offsetY = 0; offsetY <= expectation.OffsetTolerance.y; offsetY++) { if (ValidatePixels(offsetX, offsetY) || (offsetX > 0 && ValidatePixels(-offsetX, offsetY)) || (offsetX > 0 && offsetY > 0 && ValidatePixels(-offsetX, -offsetY)) || (offsetY > 0 && ValidatePixels(offsetX, -offsetY))) { report.AppendLine("OK"); return(true); } } } report.AppendLine(failMessage); return(false); bool ValidatePixels(int offsetX, int offsetY) { var isSuccess = true; var result = new StringBuilder(); for (var lin = 0; lin < expectation.Values.GetLength(0); lin++) { for (var col = 0; col < expectation.Values.GetLength(1); col++) { var expectedColor = expectation.Values[lin, col]; if (expectedColor.IsEmpty) { continue; } var pixelX = expectation.X + col + offsetX; var pixelY = expectation.Y + lin + offsetY; var pixel = bitmap.GetPixel(pixelX, pixelY); var expected = ToArgbCode(expectedColor); var actual = ToArgbCode(pixel); //Convert to ARGB value, because 'named colors' are not considered equal to their unnamed equivalents(!) if (Math.Abs(pixel.A - expectedColor.A) > expectation.ColorTolerance) { isSuccess = false; result.AppendLine($"{col},{lin}: [{pixelX},{pixelY}] Alpha (expected: {expected} | actual: {actual})"); } if (Math.Abs(pixel.R - expectedColor.R) > expectation.ColorTolerance) { isSuccess = false; result.AppendLine($"{col},{lin}: [{pixelX},{pixelY}] Red (expected: {expected} | actual: {actual})"); } if (Math.Abs(pixel.G - expectedColor.G) > expectation.ColorTolerance) { isSuccess = false; result.AppendLine($"{col},{lin}: [{pixelX},{pixelY}] Green (expected: {expected} | actual: {actual})"); } if (Math.Abs(pixel.B - expectedColor.B) > expectation.ColorTolerance) { isSuccess = false; result.AppendLine($"{col},{lin}: [{pixelX},{pixelY}] Blue (expected: {expected} | actual: {actual})"); } } } if (failMessage == default) // so we keep only for offset 0,0 { failMessage = result.ToString(); } return(isSuccess); } }
private static bool ValidatePixel( Bitmap actualBitmap, ExpectedPixels expectation, double expectedToActualScale, Point pixel, (int x, int y) offset,