Browse Source

Merge pull request #152 from JimBobSquarePants/Image-Comparer

Add image comparer test helper
pull/157/head
Scott Williams 9 years ago
committed by GitHub
parent
commit
ab62eceb6c
  1. 7
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  2. 4
      src/ImageSharp/Formats/Png/PngInterlaceMode.cs
  3. 2
      tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs
  4. 56
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  5. 83
      tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
  6. 119
      tests/ImageSharp.Tests/ImageComparer.cs
  7. 34
      tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs
  8. 38
      tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs
  9. 27
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs
  10. 20
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs
  11. 38
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs
  12. 53
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
  13. 210
      tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
  14. 43
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  15. 20
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

7
src/ImageSharp/Formats/Png/PngEncoderCore.cs

@ -544,12 +544,11 @@ namespace ImageSharp.Formats
/// </summary>
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="imageBase">The image base.</param>
private void WritePhysicalChunk<TColor>(Stream stream, ImageBase<TColor> imageBase)
/// <param name="image">The image.</param>
private void WritePhysicalChunk<TColor>(Stream stream, Image<TColor> image)
where TColor : struct, IPixel<TColor>
{
Image<TColor> image = imageBase as Image<TColor>;
if (image != null && image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0)
if (image.MetaData.HorizontalResolution > 0 && image.MetaData.VerticalResolution > 0)
{
// 39.3700787 = inches in a meter.
int dpmX = (int)Math.Round(image.MetaData.HorizontalResolution * 39.3700787D);

4
src/ImageSharp/Formats/Png/PngInterlaceMode.cs

@ -13,11 +13,11 @@ namespace ImageSharp.Formats
/// <summary>
/// Non interlaced
/// </summary>
None,
None = 0,
/// <summary>
/// Adam 7 interlacing.
/// </summary>
Adam7
Adam7 = 1
}
}

2
tests/ImageSharp.Tests/Drawing/FillRegionProcessorTests.cs

@ -27,7 +27,7 @@ namespace ImageSharp.Tests.Drawing
[InlineData(false, 16, 4)] // we always do 4 sub=pixels when antialising is off.
public void MinimumAntialiasSubpixelDepth(bool antialias, int antialiasSubpixelDepth, int expectedAntialiasSubpixelDepth)
{
var bounds = new ImageSharp.Rectangle(0, 0, 1, 1);
ImageSharp.Rectangle bounds = new ImageSharp.Rectangle(0, 0, 1, 1);
Mock<IBrush<Color>> brush = new Mock<IBrush<Color>>();
Mock<Region> region = new Mock<Region>();

56
tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs

@ -8,47 +8,37 @@ using ImageSharp.Formats;
namespace ImageSharp.Tests
{
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using ImageSharp.IO;
using Xunit;
public class PngEncoderTests : FileTestBase
{
[Fact]
public void ImageCanSaveIndexedPng()
[Theory]
[WithBlankImages(1, 1, PixelTypes.All)]
public void WritesFileMarker<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPixel<TColor>
{
string path = CreateOutputDirectory("Png", "Indexed");
foreach (TestFile file in Files)
using (Image<TColor> image = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
using (Image image = file.CreateImage())
{
using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png"))
{
image.MetaData.Quality = 256;
image.Save(output, new PngFormat());
}
}
}
}
image.Save(ms, new PngEncoder());
byte[] data = ms.ToArray().Take(8).ToArray();
byte[] expected = {
0x89, // Set the high bit.
0x50, // P
0x4E, // N
0x47, // G
0x0D, // Line ending CRLF
0x0A, // Line ending CRLF
0x1A, // EOF
0x0A // LF
};
[Fact]
public void ImageCanSavePngInParallel()
{
string path = this.CreateOutputDirectory("Png");
Parallel.ForEach(
Files,
file =>
{
using (Image image = file.CreateImage())
{
using (FileStream output = File.OpenWrite($"{path}/{file.FileNameWithoutExtension}.png"))
{
image.SaveAsPng(output);
}
}
});
Assert.Equal(expected, data);
}
}
}
}

83
tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs

@ -0,0 +1,83 @@
// <copyright file="PngSmokeTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Formats.Png
{
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using Xunit;
using ImageSharp.Formats;
using System.Linq;
using ImageSharp.IO;
public class PngSmokeTests
{
[Theory]
[WithTestPatternImages(300, 300, PixelTypes.All)]
public void GeneralTest<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPixel<TColor>
{
// does saving a file then repoening mean both files are identical???
using (Image<TColor> image = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using (Image img2 = Image.Load(ms, new PngDecoder()))
{
// img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
ImageComparer.CheckSimilarity(image, img2);
}
}
}
[Theory]
[WithTestPatternImages(100, 100, PixelTypes.All)]
public void CanSaveIndexedPng<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPixel<TColor>
{
// does saving a file then repoening mean both files are identical???
using (Image<TColor> image = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("bmp"));
image.MetaData.Quality = 256;
image.Save(ms, new PngEncoder());
ms.Position = 0;
using (Image img2 = Image.Load(ms, new PngDecoder()))
{
// img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
ImageComparer.CheckSimilarity(image, img2);
}
}
}
[Theory]
[WithTestPatternImages(300, 300, PixelTypes.All)]
public void Resize<TColor>(TestImageProvider<TColor> provider)
where TColor : struct, IPixel<TColor>
{
// does saving a file then repoening mean both files are identical???
using (Image<TColor> image = provider.GetImage())
using (MemoryStream ms = new MemoryStream())
{
// image.Save(provider.Utility.GetTestOutputFileName("png"));
image.Resize(100, 100);
// image.Save(provider.Utility.GetTestOutputFileName("png", "resize"));
image.Save(ms, new PngEncoder());
ms.Position = 0;
using (Image img2 = Image.Load(ms, new PngDecoder()))
{
ImageComparer.CheckSimilarity(image, img2);
}
}
}
}
}

119
tests/ImageSharp.Tests/ImageComparer.cs

@ -0,0 +1,119 @@
namespace ImageSharp.Tests
{
using System;
using ImageSharp;
using Xunit;
/// <summary>
/// Class to perform simple image comparisons.
/// </summary>
public static class ImageComparer
{
const int DefaultScalingFactor = 32; // this is means the images get scaled into a 32x32 image to sample pixels
const int DefaultSegmentThreshold = 3; // the greyscale difference between 2 segements my be > 3 before it influances the overall difference
const float DefaultImageThreshold = 0.000f; // after segment threasholds the images must have no differences
/// <summary>
/// Does a visual comparison between 2 images and then asserts the difference is less then a configurable threshold
/// </summary>
/// <typeparam name="TColorA">The color of the expected image</typeparam>
/// <typeparam name="TColorB">The color type fo the the actual image</typeparam>
/// <param name="expected">The expected image</param>
/// <param name="actual">The actual image</param>
/// <param name="imageTheshold">
/// The threshold for the percentage difference where the images are asumed to be the same.
/// The default/undefined value is <see cref="ImageComparer.DefaultImageThreshold"/>
/// </param>
/// <param name="segmentThreshold">
/// The threashold of the individual segments before it acumulates towards the overall difference.
/// The default undefined value is <see cref="ImageComparer.DefaultSegmentThreshold"/>
/// </param>
/// <param name="scalingFactor">
/// This is a sampling factor we sample a grid of average pixels <paramref name="scalingFactor"/> width by <paramref name="scalingFactor"/> high
/// The default undefined value is <see cref="ImageComparer.DefaultScalingFactor"/>
/// </param>
public static void CheckSimilarity<TColorA, TColorB>(Image<TColorA> expected, Image<TColorB> actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor)
where TColorA : struct, IPixel<TColorA>
where TColorB : struct, IPixel<TColorB>
{
float percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor);
Assert.InRange(percentage, 0, imageTheshold);
}
/// <summary>
/// Does a visual comparison between 2 images and then and returns the percentage diffence between the 2
/// </summary>
/// <typeparam name="TColorA">The color of the source image</typeparam>
/// <typeparam name="TColorB">The color type for the target image</typeparam>
/// <param name="source">The source image</param>
/// <param name="target">The target image</param>
/// <param name="segmentThreshold">
/// The threashold of the individual segments before it acumulates towards the overall difference.
/// The default undefined value is <see cref="ImageComparer.DefaultSegmentThreshold"/>
/// </param>
/// <param name="scalingFactor">
/// This is a sampling factor we sample a grid of average pixels <paramref name="scalingFactor"/> width by <paramref name="scalingFactor"/> high
/// The default undefined value is <see cref="ImageComparer.DefaultScalingFactor"/>
/// </param>
/// <returns>Returns a number from 0 - 1 which represents the diference focter between the images.</returns>
public static float PercentageDifference<TColorA, TColorB>(this Image<TColorA> source, Image<TColorB> target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor)
where TColorA : struct, IPixel<TColorA>
where TColorB : struct, IPixel<TColorB>
{
// code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET
Fast2DArray<byte> differences = GetDifferences(source, target, scalingFactor);
int diffPixels = 0;
foreach (byte b in differences.Data)
{
if (b > segmentThreshold) { diffPixels++; }
}
return diffPixels / (scalingFactor * scalingFactor);
}
private static Fast2DArray<byte> GetDifferences<TColorA, TColorB>(Image<TColorA> source, Image<TColorB> target, int scalingFactor)
where TColorA : struct, IPixel<TColorA>
where TColorB : struct, IPixel<TColorB>
{
Fast2DArray<byte> differences = new Fast2DArray<byte>(scalingFactor, scalingFactor);
Fast2DArray<byte> firstGray = source.GetGrayScaleValues(scalingFactor);
Fast2DArray<byte> secondGray = target.GetGrayScaleValues(scalingFactor);
for (int y = 0; y < scalingFactor; y++)
{
for (int x = 0; x < scalingFactor; x++)
{
differences[x, y] = (byte)Math.Abs(firstGray[x, y] - secondGray[x, y]);
}
}
return differences;
}
private static Fast2DArray<byte> GetGrayScaleValues<TColorA>(this Image<TColorA> source, int scalingFactor)
where TColorA : struct, IPixel<TColorA>
{
byte[] buffer = new byte[4];
using (Image<TColorA> img = new Image<TColorA>(source).Resize(scalingFactor, scalingFactor).Grayscale())
{
using (PixelAccessor<TColorA> pixels = img.Lock())
{
Fast2DArray<byte> grayScale = new Fast2DArray<byte>(scalingFactor, scalingFactor);
for (int y = 0; y < scalingFactor; y++)
{
for (int x = 0; x < scalingFactor; x++)
{
pixels[x, y].ToXyzBytes(buffer, 0);
grayScale[x, y] = buffer[1];
}
}
return grayScale;
}
}
}
}
}

34
tests/ImageSharp.Tests/Processors/Filters/GrayscaleTest.cs

@ -9,30 +9,32 @@ namespace ImageSharp.Tests
using Xunit;
using ImageSharp.Processing;
using ImageSharp.Tests;
using System.Numerics;
public class GrayscaleTest : FileTestBase
{
public static readonly TheoryData<GrayscaleMode> GrayscaleValues
= new TheoryData<GrayscaleMode>
{
GrayscaleMode.Bt709 ,
GrayscaleMode.Bt601 ,
};
/// <summary>
/// Use test patterns over loaded images to save decode time.
/// </summary>
[Theory]
[MemberData(nameof(GrayscaleValues))]
public void ImageShouldApplyGrayscaleFilter(GrayscaleMode value)
[WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt709)]
[WithTestPatternImages(50, 50, PixelTypes.StandardImageClass, GrayscaleMode.Bt601)]
public void ImageShouldApplyGrayscaleFilterAll<TColor>(TestImageProvider<TColor> provider, GrayscaleMode value)
where TColor : struct, IPixel<TColor>
{
string path = this.CreateOutputDirectory("Grayscale");
foreach (TestFile file in Files)
using (Image<TColor> image = provider.GetImage())
{
string filename = file.GetFileName(value);
using (Image image = file.CreateImage())
using (FileStream output = File.OpenWrite($"{path}/{filename}"))
image.Grayscale(value);
byte[] data = new byte[3];
foreach (TColor p in image.Pixels)
{
image.Grayscale(value).Save(output);
p.ToXyzBytes(data, 0);
Assert.Equal(data[0], data[1]);
Assert.Equal(data[1], data[2]);
}
image.DebugSave(provider);
}
}
}

38
tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs

@ -0,0 +1,38 @@
// <copyright file="WithBlankImagesAttribute.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System;
using System.Reflection;
/// <summary>
/// Triggers passing <see cref="TestImageProvider{TColor}"/> instances which produce a blank image of size width * height.
/// One <see cref="TestImageProvider{TColor}"/> instance will be passed for each the pixel format defined by the pixelTypes parameter
/// </summary>
public class WithTestPatternImagesAttribute : ImageDataAttributeBase
{
/// <summary>
/// Triggers passing an <see cref="TestImageProvider{TColor}"/> that produces a test pattern image of size width * height
/// </summary>
/// <param name="width">The required width</param>
/// <param name="height">The required height</param>
/// <param name="pixelTypes">The requested parameter</param>
/// <param name="additionalParameters">Additional theory parameter values</param>
public WithTestPatternImagesAttribute(int width, int height, PixelTypes pixelTypes, params object[] additionalParameters)
: base(pixelTypes, additionalParameters)
{
this.Width = width;
this.Height = height;
}
public int Width { get; }
public int Height { get; }
protected override string GetFactoryMethodName(MethodInfo testMethod) => "TestPattern";
protected override object[] GetFactoryMethodArgs(MethodInfo testMethod, Type factoryType) => new object[] { this.Width, this.Height };
}
}

27
tests/ImageSharp.Tests/TestUtilities/ImageProviders/BlankProvider.cs

@ -6,25 +6,46 @@
namespace ImageSharp.Tests
{
using System;
using Xunit.Abstractions;
public abstract partial class TestImageProvider<TColor>
where TColor : struct, IPixel<TColor>
{
private class BlankProvider : TestImageProvider<TColor>
private class BlankProvider : TestImageProvider<TColor>, IXunitSerializable
{
public BlankProvider(int width, int height)
{
this.Width = width;
this.Height = height;
}
public BlankProvider()
{
this.Width = 100;
this.Height = 100;
}
public override string SourceFileOrDescription => $"Blank{this.Width}x{this.Height}";
protected int Height { get; }
protected int Height { get; private set; }
protected int Width { get; }
protected int Width { get; private set; }
public override Image<TColor> GetImage() => this.Factory.CreateImage(this.Width, this.Height);
public override void Deserialize(IXunitSerializationInfo info)
{
this.Width = info.GetValue<int>("width");
this.Height = info.GetValue<int>("height");
base.Deserialize(info);
}
public override void Serialize(IXunitSerializationInfo info)
{
info.AddValue("width", this.Width);
info.AddValue("height", this.Height);
base.Serialize(info);
}
}
}
}

20
tests/ImageSharp.Tests/TestUtilities/ImageProviders/FileProvider.cs

@ -7,11 +7,12 @@ namespace ImageSharp.Tests
{
using System;
using System.Collections.Concurrent;
using Xunit.Abstractions;
public abstract partial class TestImageProvider<TColor>
where TColor : struct, IPixel<TColor>
{
private class FileProvider : TestImageProvider<TColor>
private class FileProvider : TestImageProvider<TColor>, IXunitSerializable
{
// Need PixelTypes in the dictionary key, because result images of TestImageProvider<TColor>.FileProvider
// are shared between PixelTypes.Color & PixelTypes.StandardImageClass
@ -33,6 +34,10 @@ namespace ImageSharp.Tests
this.filePath = filePath;
}
public FileProvider()
{
}
public override string SourceFileOrDescription => this.filePath;
public override Image<TColor> GetImage()
@ -49,6 +54,19 @@ namespace ImageSharp.Tests
return this.Factory.CreateImage(cachedImage);
}
public override void Deserialize(IXunitSerializationInfo info)
{
this.filePath = info.GetValue<string>("path");
base.Deserialize(info); // must be called last
}
public override void Serialize(IXunitSerializationInfo info)
{
base.Serialize(info);
info.AddValue("path", this.filePath);
}
}
}
}

38
tests/ImageSharp.Tests/TestUtilities/ImageProviders/SolidProvider.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Tests
{
using System;
using Xunit.Abstractions;
/// <summary>
/// Provides <see cref="Image{TColor}" /> instances for parametric unit tests.
@ -14,15 +15,15 @@ namespace ImageSharp.Tests
public abstract partial class TestImageProvider<TColor>
where TColor : struct, IPixel<TColor>
{
private class SolidProvider : BlankProvider
private class SolidProvider : BlankProvider
{
private readonly byte a;
private byte a;
private readonly byte b;
private byte b;
private readonly byte g;
private byte g;
private readonly byte r;
private byte r;
public SolidProvider(int width, int height, byte r, byte g, byte b, byte a)
: base(width, height)
@ -33,6 +34,15 @@ namespace ImageSharp.Tests
this.a = a;
}
public SolidProvider()
: base()
{
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 0;
}
public override string SourceFileOrDescription
=> $"Solid{this.Width}x{this.Height}_({this.r},{this.g},{this.b},{this.a})";
@ -44,6 +54,24 @@ namespace ImageSharp.Tests
return image.Fill(color);
}
public override void Serialize(IXunitSerializationInfo info)
{
info.AddValue("red", this.r);
info.AddValue("green", this.g);
info.AddValue("blue", this.b);
info.AddValue("alpha", this.a);
base.Serialize(info);
}
public override void Deserialize(IXunitSerializationInfo info)
{
this.r = info.GetValue<byte>("red");
this.g = info.GetValue<byte>("green");
this.b = info.GetValue<byte>("blue");
this.a = info.GetValue<byte>("alpha");
base.Deserialize(info);
}
}
}
}

53
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs

@ -7,12 +7,13 @@ namespace ImageSharp.Tests
{
using System;
using System.Reflection;
using Xunit.Abstractions;
/// <summary>
/// Provides <see cref="Image{TColor}" /> instances for parametric unit tests.
/// </summary>
/// <typeparam name="TColor">The pixel format of the image</typeparam>
public abstract partial class TestImageProvider<TColor>
public abstract partial class TestImageProvider<TColor>
where TColor : struct, IPixel<TColor>
{
public PixelTypes PixelType { get; private set; } = typeof(TColor).GetPixelType();
@ -25,13 +26,22 @@ namespace ImageSharp.Tests
public ImagingTestCaseUtility Utility { get; private set; }
public GenericFactory<TColor> Factory { get; private set; } = new GenericFactory<TColor>();
public string TypeName { get; private set; }
public string MethodName { get; private set; }
public static TestImageProvider<TColor> Blank(
public static TestImageProvider<TColor> TestPattern(
int width,
int height,
MethodInfo testMethod = null,
PixelTypes pixelTypeOverride = PixelTypes.Undefined)
=> new BlankProvider(width, height).Init(testMethod, pixelTypeOverride);
=> new TestPatternProvider(width, height).Init(testMethod, pixelTypeOverride);
public static TestImageProvider<TColor> Blank(
int width,
int height,
MethodInfo testMethod = null,
PixelTypes pixelTypeOverride = PixelTypes.Undefined)
=> new BlankProvider(width, height).Init(testMethod, pixelTypeOverride);
public static TestImageProvider<TColor> File(
string filePath,
@ -65,12 +75,30 @@ namespace ImageSharp.Tests
/// </summary>
public abstract Image<TColor> GetImage();
protected TestImageProvider<TColor> Init(MethodInfo testMethod, PixelTypes pixelTypeOverride)
public virtual void Deserialize(IXunitSerializationInfo info)
{
PixelTypes pixelType = info.GetValue<PixelTypes>("PixelType");
string typeName = info.GetValue<string>("TypeName");
string methodName = info.GetValue<string>("MethodName");
this.Init(typeName, methodName, pixelType);
}
public virtual void Serialize(IXunitSerializationInfo info)
{
info.AddValue("PixelType", this.PixelType);
info.AddValue("TypeName", this.TypeName);
info.AddValue("MethodName", this.MethodName);
}
protected TestImageProvider<TColor> Init(string typeName, string methodName, PixelTypes pixelTypeOverride)
{
if (pixelTypeOverride != PixelTypes.Undefined)
{
this.PixelType = pixelTypeOverride;
}
this.TypeName = typeName;
this.MethodName = methodName;
if (pixelTypeOverride == PixelTypes.StandardImageClass)
{
@ -78,19 +106,24 @@ namespace ImageSharp.Tests
}
this.Utility = new ImagingTestCaseUtility()
{
SourceFileOrDescription = this.SourceFileOrDescription,
PixelTypeName = this.PixelType.ToString()
};
{
SourceFileOrDescription = this.SourceFileOrDescription,
PixelTypeName = this.PixelType.ToString()
};
if (testMethod != null)
if (methodName != null)
{
this.Utility.Init(testMethod);
this.Utility.Init(typeName, methodName);
}
return this;
}
protected TestImageProvider<TColor> Init(MethodInfo testMethod, PixelTypes pixelTypeOverride)
{
return Init(testMethod?.DeclaringType.Name, testMethod?.Name, pixelTypeOverride);
}
public override string ToString()
{
string provName = this.GetType().Name.Replace("Provider", "");

210
tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs

@ -0,0 +1,210 @@
// <copyright file="BlankProvider.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Numerics;
using Xunit.Abstractions;
public abstract partial class TestImageProvider<TColor>
where TColor : struct, IPixel<TColor>
{
/// <summary>
/// A test image provider that produces test patterns.
/// </summary>
/// <typeparam name="TColor"></typeparam>
private class TestPatternProvider : BlankProvider
{
static Dictionary<string, Image<TColor>> testImages = new Dictionary<string, Image<TColor>>();
public TestPatternProvider(int width, int height)
: base(width, height)
{
}
public TestPatternProvider()
: base()
{
}
public override string SourceFileOrDescription => $"TestPattern{this.Width}x{this.Height}";
public override Image<TColor> GetImage()
{
lock (testImages)
{
if (!testImages.ContainsKey(this.SourceFileOrDescription))
{
Image<TColor> image = new Image<TColor>(this.Width, this.Height);
DrawTestPattern(image);
testImages.Add(this.SourceFileOrDescription, image);
}
}
return new Image<TColor>(testImages[this.SourceFileOrDescription]);
}
/// <summary>
/// Draws the test pattern on an image by drawing 4 other patterns in the for quadrants of the image.
/// </summary>
/// <param name="image"></param>
private static void DrawTestPattern(Image<TColor> image)
{
// first lets split the image into 4 quadrants
using (PixelAccessor<TColor> pixels = image.Lock())
{
BlackWhiteChecker(pixels); // top left
VirticalBars(pixels); // top right
TransparentGradients(pixels); // bottom left
Rainbow(pixels); // bottom right
}
}
/// <summary>
/// Fills the top right quadrant with alternating solid vertical bars.
/// </summary>
/// <param name="pixels"></param>
private static void VirticalBars(PixelAccessor<TColor> pixels)
{
// topLeft
int left = pixels.Width / 2;
int right = pixels.Width;
int top = 0;
int bottom = pixels.Height / 2;
int stride = pixels.Width / 12;
TColor[] c = {
NamedColors<TColor>.HotPink,
NamedColors<TColor>.Blue
};
int p = 0;
for (int y = top; y < bottom; y++)
{
for (int x = left; x < right; x++)
{
if (x % stride == 0)
{
p++;
p = p % c.Length;
}
pixels[x, y] = c[p];
}
}
}
/// <summary>
/// fills the top left quadrant with a black and white checker board.
/// </summary>
/// <param name="pixels"></param>
private static void BlackWhiteChecker(PixelAccessor<TColor> pixels)
{
// topLeft
int left = 0;
int right = pixels.Width / 2;
int top = 0;
int bottom = pixels.Height / 2;
int stride = pixels.Width / 6;
TColor[] c = {
NamedColors<TColor>.Black,
NamedColors<TColor>.White
};
int p = 0;
for (int y = top; y < bottom; y++)
{
if (y % stride == 0)
{
p++;
p = p % c.Length;
}
int pstart = p;
for (int x = left; x < right; x++)
{
if (x % stride == 0)
{
p++;
p = p % c.Length;
}
pixels[x, y] = c[p];
}
p = pstart;
}
}
/// <summary>
/// Fills the bottom left quadrent with 3 horizental bars in Red, Green and Blue with a alpha gradient from left (transparent) to right (solid).
/// </summary>
/// <param name="pixels"></param>
private static void TransparentGradients(PixelAccessor<TColor> pixels)
{
// topLeft
int left = 0;
int right = pixels.Width / 2;
int top = pixels.Height / 2;
int bottom = pixels.Height;
int height = (int)Math.Ceiling(pixels.Height / 6f);
Vector4 red = Color.Red.ToVector4(); // use real color so we can see har it translates in the test pattern
Vector4 green = Color.Green.ToVector4(); // use real color so we can see har it translates in the test pattern
Vector4 blue = Color.Blue.ToVector4(); // use real color so we can see har it translates in the test pattern
TColor c = default(TColor);
for (int x = left; x < right; x++)
{
blue.W = red.W = green.W = (float)x / (float)right;
c.PackFromVector4(red);
int topBand = top;
for (int y = topBand; y < top + height; y++)
{
pixels[x, y] = c;
}
topBand = topBand + height;
c.PackFromVector4(green);
for (int y = topBand; y < topBand + height; y++)
{
pixels[x, y] = c;
}
topBand = topBand + height;
c.PackFromVector4(blue);
for (int y = topBand; y < bottom; y++)
{
pixels[x, y] = c;
}
}
}
/// <summary>
/// Fills the bottom right quadrant with all the colors producable by converting itterating over a uint and unpacking it.
/// A better algorithm could be used but it works
/// </summary>
/// <param name="pixels"></param>
private static void Rainbow(PixelAccessor<TColor> pixels)
{
int left = pixels.Width / 2;
int right = pixels.Width;
int top = pixels.Height / 2;
int bottom = pixels.Height;
int pixelCount = left * top;
uint stepsPerPixel = (uint)(uint.MaxValue / pixelCount);
TColor c = default(TColor);
Color t = new Color(0);
for (int x = left; x < right; x++)
for (int y = top; y < bottom; y++)
{
t.PackedValue += stepsPerPixel;
Vector4 v = t.ToVector4();
//v.W = (x - left) / (float)left;
c.PackFromVector4(v);
pixels[x, y] = c;
}
}
}
}
}

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

@ -44,13 +44,26 @@ namespace ImageSharp.Tests
/// </summary>
/// <param name="extension"></param>
/// <returns>The required extension</returns>
public string GetTestOutputFileName(string extension = null)
public string GetTestOutputFileName(string extension = null, string tag = null)
{
string fn = string.Empty;
if (string.IsNullOrWhiteSpace(extension))
{
extension = null;
}
fn = Path.GetFileNameWithoutExtension(this.SourceFileOrDescription);
extension = extension ?? Path.GetExtension(this.SourceFileOrDescription);
extension = extension ?? ".bmp";
if (string.IsNullOrWhiteSpace(extension))
{
extension = Path.GetExtension(this.SourceFileOrDescription);
}
if (string.IsNullOrWhiteSpace(extension))
{
extension = ".bmp";
}
if (extension[0] != '.')
{
@ -65,7 +78,14 @@ namespace ImageSharp.Tests
pixName = '_' + pixName;
}
return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{extension}";
tag = tag ?? string.Empty;
if (tag != string.Empty)
{
tag = '_' + tag;
}
return $"{this.GetTestOutputDir()}/{this.TestName}{pixName}{fn}{tag}{extension}";
}
/// <summary>
@ -80,7 +100,7 @@ namespace ImageSharp.Tests
where TColor : struct, IPixel<TColor>
{
string path = this.GetTestOutputFileName(extension);
extension = Path.GetExtension(path);
IImageFormat format = GetImageFormatByExtension(extension);
encoder = encoder ?? format.Encoder;
@ -91,16 +111,21 @@ namespace ImageSharp.Tests
}
}
internal void Init(string typeName, string methodName)
{
this.TestGroupName = typeName;
this.TestName = methodName;
}
internal void Init(MethodInfo method)
{
this.TestGroupName = method.DeclaringType.Name;
this.TestName = method.Name;
this.Init(method.DeclaringType.Name, method.Name);
}
private static IImageFormat GetImageFormatByExtension(string extension)
{
extension = extension.ToLower();
return Configuration.Default.ImageFormats.First(f => f.SupportedExtensions.Contains(extension));
extension = extension?.TrimStart('.');
return Configuration.Default.ImageFormats.First(f => f.SupportedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase));
}
private string GetTestOutputDir()

20
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -0,0 +1,20 @@

namespace ImageSharp.Tests
{
using System;
using System.Collections.Generic;
using System.Text;
public static class TestImageExtensions
{
public static void DebugSave<TColor>(this Image<TColor> img, TestImageProvider<TColor> provider, string extension = "png")
where TColor : struct, IPixel<TColor>
{
if(!bool.TryParse(Environment.GetEnvironmentVariable("CI"), out bool isCI) || !isCI)
{
// we are running locally then we want to save it out
provider.Utility.SaveTestOutputFile(img, extension);
}
}
}
}
Loading…
Cancel
Save