mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
15 changed files with 670 additions and 84 deletions
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 }; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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…
Reference in new issue