Browse Source

Use Rgba64 for image comparison.

af/merge-core
James Jackson-South 8 years ago
parent
commit
ae66072668
  1. 2
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs
  2. 2
      tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs
  3. 2
      tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs
  4. 2
      tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs
  5. 2
      tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs
  6. 14
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs
  7. 2
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs
  8. 19
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs
  9. 4
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs
  10. 10
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs
  11. 35
      tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs
  12. 19
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  13. 18
      tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs

2
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs

@ -36,7 +36,7 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
if (!CustomToleranceValues.TryGetValue(file, out float tolerance)) if (!CustomToleranceValues.TryGetValue(file, out float tolerance))
{ {
bool baseline = file.ToLower().Contains("baseline"); bool baseline = file.IndexOf("baseline", StringComparison.OrdinalIgnoreCase) >= 0;
tolerance = baseline ? BaselineTolerance : ProgressiveTolerance; tolerance = baseline ? BaselineTolerance : ProgressiveTolerance;
} }

2
tests/ImageSharp.Tests/Processing/Processors/Convolution/DetectEdgesTest.cs

@ -15,7 +15,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Convolution
public class DetectEdgesTest : FileTestBase public class DetectEdgesTest : FileTestBase
{ {
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.001f); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0456F);
public static readonly string[] CommonTestImages = { TestImages.Png.Bike }; public static readonly string[] CommonTestImages = { TestImages.Png.Bike };

2
tests/ImageSharp.Tests/Processing/Processors/Filters/FilterTest.cs

@ -16,7 +16,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Filters
[GroupOutput("Filters")] [GroupOutput("Filters")]
public class FilterTest public class FilterTest
{ {
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f, 3); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0218f, 3);
// Testing the generic FilterProcessor with more than one pixel type intentionally. // Testing the generic FilterProcessor with more than one pixel type intentionally.
// There is no need to do this with the specialized ones. // There is no need to do this with the specialized ones.

2
tests/ImageSharp.Tests/Processing/Processors/Transforms/ResizeTests.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms
{ {
public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial };
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.069F);
public static readonly TheoryData<string, IResampler> AllReSamplers = public static readonly TheoryData<string, IResampler> AllReSamplers =
new TheoryData<string, IResampler> new TheoryData<string, IResampler>

2
tests/ImageSharp.Tests/Processing/Transforms/AffineTransformTests.cs

@ -19,7 +19,7 @@ namespace SixLabors.ImageSharp.Tests.Processing.Transforms
{ {
private readonly ITestOutputHelper Output; private readonly ITestOutputHelper Output;
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f, 3); private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0085f, 3);
/// <summary> /// <summary>
/// angleDeg, sx, sy, tx, ty /// angleDeg, sx, sy, tx, ty

14
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ExactImageComparer.cs

@ -22,10 +22,10 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
int width = actual.Width; int width = actual.Width;
// TODO: Comparing through Rgba32 is not robust enough because of the existance of super high precision pixel types. // TODO: Comparing through Rgba64 may not be robust enough because of the existance of super high precision pixel types.
var aBuffer = new Rgba32[width]; var aBuffer = new Rgba64[width];
var bBuffer = new Rgba32[width]; var bBuffer = new Rgba64[width];
var differences = new List<PixelDifference>(); var differences = new List<PixelDifference>();
@ -34,13 +34,13 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
Span<TPixelA> aSpan = expected.GetPixelRowSpan(y); Span<TPixelA> aSpan = expected.GetPixelRowSpan(y);
Span<TPixelB> bSpan = actual.GetPixelRowSpan(y); Span<TPixelB> bSpan = actual.GetPixelRowSpan(y);
PixelOperations<TPixelA>.Instance.ToRgba32(aSpan, aBuffer, width); PixelOperations<TPixelA>.Instance.ToRgba64(aSpan, aBuffer, width);
PixelOperations<TPixelB>.Instance.ToRgba32(bSpan, bBuffer, width); PixelOperations<TPixelB>.Instance.ToRgba64(bSpan, bBuffer, width);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
Rgba32 aPixel = aBuffer[x]; Rgba64 aPixel = aBuffer[x];
Rgba32 bPixel = bBuffer[x]; Rgba64 bPixel = bBuffer[x];
if (aPixel != bPixel) if (aPixel != bPixel)
{ {

2
tests/ImageSharp.Tests/TestUtilities/ImageComparison/Exceptions/ImageDifferenceIsOverThresholdException.cs

@ -24,7 +24,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
int i = 0; int i = 0;
foreach (ImageSimilarityReport r in reports) foreach (ImageSimilarityReport r in reports)
{ {
sb.Append($"Report{i}: "); sb.Append($"Report ImageFrame {i}: ");
sb.Append(r); sb.Append(r);
sb.Append(Environment.NewLine); sb.Append(Environment.NewLine);
i++; i++;

19
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageComparer.cs

@ -28,9 +28,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
/// <summary> /// <summary>
/// Returns Tolerant(imageThresholdInPercents/100) /// Returns Tolerant(imageThresholdInPercents/100)
/// </summary> /// </summary>
public static ImageComparer TolerantPercentage(float imageThresholdInPercents, public static ImageComparer TolerantPercentage(float imageThresholdInPercents, int perPixelManhattanThreshold = 0)
int perPixelManhattanThreshold = 0) => => Tolerant(imageThresholdInPercents / 100F, perPixelManhattanThreshold);
Tolerant(imageThresholdInPercents / 100f, perPixelManhattanThreshold);
public abstract ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>( public abstract ImageSimilarityReport<TPixelA, TPixelB> CompareImagesOrFrames<TPixelA, TPixelB>(
ImageFrame<TPixelA> expected, ImageFrame<TPixelA> expected,
@ -120,18 +119,20 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
var cleanedReports = new List<ImageSimilarityReport<TPixelA, TPixelB>>(reports.Count()); var cleanedReports = new List<ImageSimilarityReport<TPixelA, TPixelB>>(reports.Count());
foreach (ImageSimilarityReport<TPixelA, TPixelB> r in reports) foreach (ImageSimilarityReport<TPixelA, TPixelB> r in reports)
{ {
IEnumerable<PixelDifference> outsideChanges = r.Differences.Where(x => !( IEnumerable<PixelDifference> outsideChanges = r.Differences.Where(
ignoredRegion.X <= x.Position.X && x =>
x.Position.X <= ignoredRegion.Right && !(ignoredRegion.X <= x.Position.X
ignoredRegion.Y <= x.Position.Y && && x.Position.X <= ignoredRegion.Right
x.Position.Y <= ignoredRegion.Bottom)); && ignoredRegion.Y <= x.Position.Y
&& x.Position.Y <= ignoredRegion.Bottom));
if (outsideChanges.Any()) if (outsideChanges.Any())
{ {
cleanedReports.Add(new ImageSimilarityReport<TPixelA, TPixelB>(r.ExpectedImage, r.ActualImage, outsideChanges, null)); cleanedReports.Add(new ImageSimilarityReport<TPixelA, TPixelB>(r.ExpectedImage, r.ActualImage, outsideChanges, null));
} }
} }
if (cleanedReports.Any()) if (cleanedReports.Count > 0)
{ {
throw new ImageDifferenceIsOverThresholdException(cleanedReports); throw new ImageDifferenceIsOverThresholdException(cleanedReports);
} }

4
tests/ImageSharp.Tests/TestUtilities/ImageComparison/ImageSimilarityReport.cs

@ -19,6 +19,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
this.TotalNormalizedDifference = totalNormalizedDifference; this.TotalNormalizedDifference = totalNormalizedDifference;
this.Differences = differences.ToArray(); this.Differences = differences.ToArray();
} }
public object ExpectedImage { get; } public object ExpectedImage { get; }
public object ActualImage { get; } public object ActualImage { get; }
@ -59,6 +60,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
var sb = new StringBuilder(); var sb = new StringBuilder();
if (this.TotalNormalizedDifference.HasValue) if (this.TotalNormalizedDifference.HasValue)
{ {
sb.AppendLine();
sb.AppendLine($"Total difference: {this.DifferencePercentageString}"); sb.AppendLine($"Total difference: {this.DifferencePercentageString}");
} }
int max = Math.Min(5, this.Differences.Length); int max = Math.Min(5, this.Differences.Length);
@ -68,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
sb.Append(this.Differences[i]); sb.Append(this.Differences[i]);
if (i < max - 1) if (i < max - 1)
{ {
sb.Append("; "); sb.AppendFormat(";{0}", Environment.NewLine);
} }
} }
if (this.Differences.Length >= 5) if (this.Differences.Length >= 5)

10
tests/ImageSharp.Tests/TestUtilities/ImageComparison/PixelDifference.cs

@ -19,12 +19,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
this.AlphaDifference = alphaDifference; this.AlphaDifference = alphaDifference;
} }
public PixelDifference(Point position, Rgba32 expected, Rgba32 actual) public PixelDifference(Point position, Rgba64 expected, Rgba64 actual)
: this(position, : this(position,
(int)actual.R - (int)expected.R, actual.R - expected.R,
(int)actual.G - (int)expected.G, actual.G - expected.G,
(int)actual.B - (int)expected.B, actual.B - expected.B,
(int)actual.A - (int)expected.A) actual.A - expected.A)
{ {
} }

35
tests/ImageSharp.Tests/TestUtilities/ImageComparison/TolerantImageComparer.cs

@ -12,7 +12,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
public class TolerantImageComparer : ImageComparer public class TolerantImageComparer : ImageComparer
{ {
// 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit // 1% of all pixels in a 100*100 pixel area are allowed to have a difference of 1 unit
public const float DefaultImageThreshold = 1.0f / (100 * 100 * 255); // 257 = (1 / 255) * 65535.
public const float DefaultImageThreshold = 257F / (100 * 100 * 65535);
/// <summary> /// <summary>
/// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'. /// Individual manhattan pixel difference is only added to total image difference when the individual difference is over 'perPixelManhattanThreshold'.
@ -28,23 +29,23 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
/// <summary> /// <summary>
/// The maximal tolerated difference represented by a value between 0.0 and 1.0. /// The maximal tolerated difference represented by a value between 0.0 and 1.0.
/// Examples of percentage differences on a single pixel: /// Examples of percentage differences on a single pixel:
/// 1. PixelA = (255,255,255,0) PixelB =(0,0,0,255) leads to 100% difference on a single pixel /// 1. PixelA = (65535,65535,65535,0) PixelB =(0,0,0,65535) leads to 100% difference on a single pixel
/// 2. PixelA = (255,255,255,0) PixelB =(255,255,255,255) leads to 25% difference on a single pixel /// 2. PixelA = (65535,65535,65535,0) PixelB =(65535,65535,65535,65535) leads to 25% difference on a single pixel
/// 3. PixelA = (255,255,255,0) PixelB =(128,128,128,128) leads to 50% difference on a single pixel /// 3. PixelA = (65535,65535,65535,0) PixelB =(128,128,128,128) leads to 50% difference on a single pixel
/// ///
/// The total differences is the sum of all pixel differences normalized by image dimensions! /// The total differences is the sum of all pixel differences normalized by image dimensions!
/// The individual distances are calculated using the Manhattan function: /// The individual distances are calculated using the Manhattan function:
/// <see> /// <see>
/// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref> /// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref>
/// </see> /// </see>
/// ImageThresholdInPercents = 1.0/255 means that we allow one byte difference per channel on a 1x1 image /// ImageThresholdInPercents = 1.0/65535 means that we allow one unit difference per channel on a 1x1 image
/// ImageThresholdInPercents = 1.0/(100*100*255) means that we allow only one byte difference per channel on a 100x100 image /// ImageThresholdInPercents = 1.0/(100*100*65535) means that we allow only one unit difference per channel on a 100x100 image
/// </summary> /// </summary>
public float ImageThreshold { get; } public float ImageThreshold { get; }
/// <summary> /// <summary>
/// The threshold of the individual pixels before they acumulate towards the overall difference. /// The threshold of the individual pixels before they acumulate towards the overall difference.
/// For an individual <see cref="Rgba32"/> pixel pair the value is the Manhattan distance of pixels: /// For an individual <see cref="Rgba64"/> pixel pair the value is the Manhattan distance of pixels:
/// <see> /// <see>
/// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref> /// <cref>https://en.wikipedia.org/wiki/Taxicab_geometry</cref>
/// </see> /// </see>
@ -60,12 +61,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
int width = actual.Width; int width = actual.Width;
// TODO: Comparing through Rgba32 is not robust enough because of the existance of super high precision pixel types. // TODO: Comparing through Rgba64 may not robust enough because of the existance of super high precision pixel types.
var aBuffer = new Rgba32[width]; var aBuffer = new Rgba64[width];
var bBuffer = new Rgba32[width]; var bBuffer = new Rgba64[width];
float totalDifference = 0.0f; float totalDifference = 0F;
var differences = new List<PixelDifference>(); var differences = new List<PixelDifference>();
@ -74,8 +75,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
Span<TPixelA> aSpan = expected.GetPixelRowSpan(y); Span<TPixelA> aSpan = expected.GetPixelRowSpan(y);
Span<TPixelB> bSpan = actual.GetPixelRowSpan(y); Span<TPixelB> bSpan = actual.GetPixelRowSpan(y);
PixelOperations<TPixelA>.Instance.ToRgba32(aSpan, aBuffer, width); PixelOperations<TPixelA>.Instance.ToRgba64(aSpan, aBuffer, width);
PixelOperations<TPixelB>.Instance.ToRgba32(bSpan, bBuffer, width); PixelOperations<TPixelB>.Instance.ToRgba64(bSpan, bBuffer, width);
for (int x = 0; x < width; x++) for (int x = 0; x < width; x++)
{ {
@ -91,8 +92,8 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
} }
} }
float normalizedDifference = totalDifference / ((float)actual.Width * (float)actual.Height); float normalizedDifference = totalDifference / (actual.Width * (float)actual.Height);
normalizedDifference /= 4.0f * 255.0f; normalizedDifference /= 4F * 65535F;
if (normalizedDifference > this.ImageThreshold) if (normalizedDifference > this.ImageThreshold)
{ {
@ -105,12 +106,12 @@ namespace SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetManhattanDistanceInRgbaSpace(ref Rgba32 a, ref Rgba32 b) private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b)
{ {
return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A); return Diff(a.R, b.R) + Diff(a.G, b.G) + Diff(a.B, b.B) + Diff(a.A, b.A);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int Diff(byte a, byte b) => Math.Abs(a - b); private static int Diff(ushort a, ushort b) => Math.Abs(a - b);
} }
} }

19
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -192,7 +192,7 @@ namespace SixLabors.ImageSharp.Tests
{ {
Directory.CreateDirectory(baseDir); Directory.CreateDirectory(baseDir);
} }
for (int i = 0; i < frameCount; i++) for (int i = 0; i < frameCount; i++)
{ {
string filePath = $"{baseDir}/{i:D2}.{extension}"; string filePath = $"{baseDir}/{i:D2}.{extension}";
@ -258,7 +258,7 @@ namespace SixLabors.ImageSharp.Tests
this.TestName = methodName; this.TestName = methodName;
this.OutputSubfolderName = outputSubfolderName; this.OutputSubfolderName = outputSubfolderName;
} }
internal string GetTestOutputDir() internal string GetTestOutputDir()
{ {
string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName); string testGroupName = Path.GetFileNameWithoutExtension(this.TestGroupName);
@ -281,25 +281,26 @@ namespace SixLabors.ImageSharp.Tests
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
TPixel pixel = img[x, y]; TPixel pixel = img[x, y];
var rgbaPixel = default(Rgba32); Rgba64 rgbaPixel = default;
pixel.ToRgba32(ref rgbaPixel); pixel.ToRgba64(ref rgbaPixel);
ushort change = (ushort)Math.Round((perChannelChange / 255F) * 65535F);
if (rgbaPixel.R + perChannelChange <= 255) if (rgbaPixel.R + perChannelChange <= 255)
{ {
rgbaPixel.R += perChannelChange; rgbaPixel.R += change;
} }
else else
{ {
rgbaPixel.R -= perChannelChange; rgbaPixel.R -= change;
} }
if (rgbaPixel.G + perChannelChange <= 255) if (rgbaPixel.G + perChannelChange <= 255)
{ {
rgbaPixel.G += perChannelChange; rgbaPixel.G += change;
} }
else else
{ {
rgbaPixel.G -= perChannelChange; rgbaPixel.G -= change;
} }
if (rgbaPixel.B + perChannelChange <= 255) if (rgbaPixel.B + perChannelChange <= 255)
@ -320,7 +321,7 @@ namespace SixLabors.ImageSharp.Tests
rgbaPixel.A -= perChannelChange; rgbaPixel.A -= perChannelChange;
} }
pixel.PackFromRgba32(rgbaPixel); pixel.PackFromRgba64(rgbaPixel);
img[x, y] = pixel; img[x, y] = pixel;
} }
} }

18
tests/ImageSharp.Tests/TestUtilities/Tests/ImageComparerTests.cs

@ -68,16 +68,14 @@ namespace SixLabors.ImageSharp.Tests
{ {
using (Image<TPixel> clone = image.Clone()) using (Image<TPixel> clone = image.Clone())
{ {
byte perChannelChange = 2; byte perChannelChange = 20;
ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange); ImagingTestCaseUtility.ModifyPixel(clone, 3, 1, perChannelChange);
var comparer = ImageComparer.Tolerant(); var comparer = ImageComparer.Tolerant();
ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny<ImageDifferenceIsOverThresholdException>( ImageDifferenceIsOverThresholdException ex = Assert.ThrowsAny<ImageDifferenceIsOverThresholdException>(
() => () => comparer.VerifySimilarity(image, clone));
{
comparer.VerifySimilarity(image, clone);
});
PixelDifference diff = ex.Reports.Single().Differences.Single(); PixelDifference diff = ex.Reports.Single().Differences.Single();
Assert.Equal(new Point(3, 1), diff.Position); Assert.Equal(new Point(3, 1), diff.Position);
} }
@ -85,7 +83,7 @@ namespace SixLabors.ImageSharp.Tests
} }
[Theory] [Theory]
[WithTestPatternImages(100, 100, PixelTypes.Rgba32)] [WithTestPatternImages(100, 100, PixelTypes.Rgba64)]
public void TolerantImageComparer_TestPerPixelThreshold<TPixel>(TestImageProvider<TPixel> provider) public void TolerantImageComparer_TestPerPixelThreshold<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
@ -93,11 +91,11 @@ namespace SixLabors.ImageSharp.Tests
{ {
using (Image<TPixel> clone = image.Clone()) using (Image<TPixel> clone = image.Clone())
{ {
ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 10); ImagingTestCaseUtility.ModifyPixel(clone, 0, 0, 1);
ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 10); ImagingTestCaseUtility.ModifyPixel(clone, 1, 0, 1);
ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 10); ImagingTestCaseUtility.ModifyPixel(clone, 2, 0, 1);
var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 42); var comparer = ImageComparer.Tolerant(perPixelManhattanThreshold: 257 * 3);
comparer.VerifySimilarity(image, clone); comparer.VerifySimilarity(image, clone);
} }
} }

Loading…
Cancel
Save