diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 7950d260c..e97eaed58 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -545,11 +545,10 @@ namespace ImageSharp.Formats
/// The pixel format.
/// The containing image data.
/// The image base.
- private void WritePhysicalChunk(Stream stream, ImageBase imageBase)
+ private void WritePhysicalChunk(Stream stream, Image image)
where TColor : struct, IPixel
{
- Image image = imageBase as Image;
- 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);
diff --git a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs
index e32e808c1..ec3b8ebe7 100644
--- a/src/ImageSharp/Formats/Png/PngInterlaceMode.cs
+++ b/src/ImageSharp/Formats/Png/PngInterlaceMode.cs
@@ -13,11 +13,11 @@ namespace ImageSharp.Formats
///
/// Non interlaced
///
- None,
+ None = 0,
///
/// Adam 7 interlacing.
///
- Adam7
+ Adam7 = 1
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
index 49be75139..16906c1fa 100644
--- a/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
@@ -9,12 +9,12 @@ namespace ImageSharp.Tests
{
using System.IO;
using System.Threading.Tasks;
-
+ using ImageSharp.IO;
using Xunit;
public class PngEncoderTests : FileTestBase
{
- [Fact]
+ [Fact(Skip ="Slow intergration test")]
public void ImageCanSaveIndexedPng()
{
string path = CreateOutputDirectory("Png", "Indexed");
@@ -32,7 +32,7 @@ namespace ImageSharp.Tests
}
}
- [Fact]
+ [Fact(Skip = "Slow intergration test")]
public void ImageCanSavePngInParallel()
{
string path = this.CreateOutputDirectory("Png");
@@ -50,5 +50,63 @@ namespace ImageSharp.Tests
}
});
}
+
+ [Theory]
+ [WithBlankImages(1, 1, PixelTypes.All)]
+ public void WritesFileMarker(TestImageProvider provider)
+ where TColor : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ using (EndianBinaryReader reader = Encode(image, null))
+ {
+
+ byte[] data = reader.ReadBytes(8);
+ 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
+ };
+
+ Assert.Equal(expected, data);
+ }
+ }
+
+ [Theory]
+ [WithBlankImages(1, 1, PixelTypes.All)]
+ [WithBlankImages(10, 10, PixelTypes.StandardImageClass)]
+ public void WritesFileHeaderHasHeightAndWidth(TestImageProvider provider)
+ where TColor : struct, IPixel
+ {
+ using (Image image = provider.GetImage())
+ using (EndianBinaryReader reader = Encode(image, null))
+ {
+ reader.ReadBytes(8); // throw away the file header
+ uint width = reader.ReadUInt32();
+ uint height = reader.ReadUInt32();
+
+ byte bitDepth = reader.ReadByte();
+ byte colorType = reader.ReadByte();
+ byte compressionMethod = reader.ReadByte();
+ byte filterMethod = reader.ReadByte();
+ byte interlaceMethod = reader.ReadByte();
+
+ Assert.Equal(image.Width, (int)width);
+ Assert.Equal(image.Height, (int)height);
+ }
+ }
+
+ private static EndianBinaryReader Encode(Image img, IEncoderOptions options)
+ where TColor : struct, IPixel
+ {
+ MemoryStream stream = new MemoryStream();
+ new PngEncoder().Encode(img, stream, null);
+ stream.Position = 0;
+ return new EndianBinaryReader(Endianness.BigEndian, stream);
+ }
}
}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
new file mode 100644
index 000000000..7a15e30e9
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Png/PngSmokeTests.cs
@@ -0,0 +1,62 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+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 WritesFileMarker(TestImageProvider provider)
+ where TColor : struct, IPixel
+ {
+ // does saving a file then repoening mean both files are identical???
+ using (Image 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 = new Image(ms, new Configuration(new PngFormat())))
+ {
+ img2.Save(provider.Utility.GetTestOutputFileName("bmp", "_loaded"), new BmpEncoder());
+ ImageComparer.VisualComparer(image, img2);
+ }
+ }
+ }
+
+ [Theory]
+ [WithTestPatternImages(300, 300, PixelTypes.All)]
+ public void Resize(TestImageProvider provider)
+ where TColor : struct, IPixel
+ {
+ // does saving a file then repoening mean both files are identical???
+ using (Image 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 = new Image(ms, new Configuration(new PngFormat())))
+ {
+ ImageComparer.VisualComparer(image, img2);
+ }
+ }
+ }
+ }
+}
diff --git a/tests/ImageSharp.Tests/ImageComparer.cs b/tests/ImageSharp.Tests/ImageComparer.cs
new file mode 100644
index 000000000..091edd343
--- /dev/null
+++ b/tests/ImageSharp.Tests/ImageComparer.cs
@@ -0,0 +1,84 @@
+namespace ImageSharp.Tests
+{
+ using System;
+ using ImageSharp;
+ using Xunit;
+
+ ///
+ /// Class to perform simple image comparisons.
+ ///
+ public static class ImageComparer
+ {
+ const int DefaultScalingFactor = 32;
+ const int DefaultSegmentThreshold = 3;
+ const float DefaultImageThreshold = 0.000f;
+
+ public static void VisualComparer(Image expected, Image actual, float imageTheshold = DefaultImageThreshold, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor)
+ where TColorA : struct, IPixel
+ where TColorB : struct, IPixel
+ {
+ var percentage = expected.PercentageDifference(actual, segmentThreshold, scalingFactor);
+
+ Assert.InRange(percentage, 0, imageTheshold);
+ }
+
+ public static float PercentageDifference(this Image source, Image target, byte segmentThreshold = DefaultSegmentThreshold, int scalingFactor = DefaultScalingFactor)
+ where TColorA : struct, IPixel
+ where TColorB : struct, IPixel
+ {
+ // code adapted from https://www.codeproject.com/Articles/374386/Simple-image-comparison-in-NET
+ Fast2DArray 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 GetDifferences(Image source, Image target, int scalingFactor)
+ where TColorA : struct, IPixel
+ where TColorB : struct, IPixel
+ {
+ Fast2DArray differences = new Fast2DArray(scalingFactor, scalingFactor);
+ Fast2DArray firstGray = source.GetGrayScaleValues(scalingFactor);
+ Fast2DArray 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 GetGrayScaleValues(this Image source, int scalingFactor)
+ where TColorA : struct, IPixel
+ {
+ byte[] buffer = new byte[4];
+ using (Image img = new Image(source).Resize(scalingFactor, scalingFactor).Grayscale())
+ {
+ using (PixelAccessor pixels = img.Lock())
+ {
+ Fast2DArray grayScale = new Fast2DArray(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;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs
new file mode 100644
index 000000000..98bc45f5b
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithTestPatternImageAttribute.cs
@@ -0,0 +1,38 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System;
+ using System.Reflection;
+
+ ///
+ /// Triggers passing instances which produce a blank image of size width * height.
+ /// One instance will be passed for each the pixel format defined by the pixelTypes parameter
+ ///
+ public class WithTestPatternImagesAttribute : ImageDataAttributeBase
+ {
+ ///
+ /// Triggers passing an that produces a test pattern image of size width * height
+ ///
+ /// The required width
+ /// The required height
+ /// The requested parameter
+ /// Additional theory parameter values
+ 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 };
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
index cdb31ab69..4ec0fb507 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestImageProvider.cs
@@ -25,13 +25,20 @@ namespace ImageSharp.Tests
public ImagingTestCaseUtility Utility { get; private set; }
public GenericFactory Factory { get; private set; } = new GenericFactory();
-
- public static TestImageProvider Blank(
+
+ public static TestImageProvider 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 Blank(
+ int width,
+ int height,
+ MethodInfo testMethod = null,
+ PixelTypes pixelTypeOverride = PixelTypes.Undefined)
+ => new BlankProvider(width, height).Init(testMethod, pixelTypeOverride);
public static TestImageProvider File(
string filePath,
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
new file mode 100644
index 000000000..8d5e77554
--- /dev/null
+++ b/tests/ImageSharp.Tests/TestUtilities/ImageProviders/TestPatternProvider.cs
@@ -0,0 +1,184 @@
+//
+// Copyright (c) James Jackson-South and contributors.
+// Licensed under the Apache License, Version 2.0.
+//
+
+namespace ImageSharp.Tests
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Numerics;
+
+ public abstract partial class TestImageProvider
+ where TColor : struct, IPixel
+ {
+ private class TestPatternProvider : TestImageProvider
+ {
+ static Dictionary> testImages = new Dictionary>();
+
+ public TestPatternProvider(int width, int height)
+ {
+ this.Width = width;
+ this.Height = height;
+ }
+
+ public override string SourceFileOrDescription => $"TestPattern{this.Width}x{this.Height}";
+
+ protected int Height { get; }
+
+ protected int Width { get; }
+
+ public override Image GetImage()
+ {
+ lock (testImages)
+ {
+ if (!testImages.ContainsKey(this.SourceFileOrDescription))
+ {
+ var image = new Image(this.Width, this.Height);
+ DrawTestPattern(image);
+ testImages.Add(this.SourceFileOrDescription, image);
+ }
+
+ return new Image(testImages[this.SourceFileOrDescription]);
+ }
+ }
+
+ private static void DrawTestPattern(Image image)
+ {
+ // first lets split the image into 4 quadrants
+ using (var pixels = image.Lock())
+ {
+ BlackWhiteChecker(pixels); // top left
+ HorizontalLines(pixels); // top right
+ TransparentGradients(pixels); // bottom left
+ Raninbow(pixels); // bottom right
+ }
+ }
+
+ private static void HorizontalLines(PixelAccessor 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.HotPink,
+ NamedColors.Blue
+ };
+ int p = 0;
+ for (var y = top; y < bottom; y++)
+ {
+ for (var x = left; x < right; x++)
+ {
+ if (x % stride == 0)
+ {
+ p++;
+ p = p % c.Length;
+ }
+ pixels[x, y] = c[p];
+ }
+ }
+ }
+
+ private static void BlackWhiteChecker(PixelAccessor 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.Black,
+ NamedColors.White
+ };
+
+ int p = 0;
+ for (var y = top; y < bottom; y++)
+ {
+ if (y % stride == 0)
+ {
+ p++;
+ p = p % c.Length;
+ }
+ var pstart = p;
+ for (var x = left; x < right; x++)
+ {
+ if (x % stride == 0)
+ {
+ p++;
+ p = p % c.Length;
+ }
+ pixels[x, y] = c[p];
+ }
+ p = pstart;
+ }
+ }
+
+ private static void TransparentGradients(PixelAccessor 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 (var x = left; x < right; x++)
+ {
+ blue.W = red.W = green.W = (float)x / (float)right;
+
+ c.PackFromVector4(red);
+ var topBand = top;
+ for (var y = topBand; y < top + height; y++)
+ {
+ pixels[x, y] = c;
+ }
+ topBand = topBand + height;
+ c.PackFromVector4(green);
+ for (var y = topBand; y < topBand + height; y++)
+ {
+ pixels[x, y] = c;
+ }
+ topBand = topBand + height;
+ c.PackFromVector4(blue);
+ for (var y = topBand; y < bottom; y++)
+ {
+ pixels[x, y] = c;
+ }
+ }
+
+ }
+ private static void Raninbow(PixelAccessor 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);
+ uint inital = 0;
+ for (var x = left; x < right; x++)
+ for (var y = top; y < bottom; y++)
+ {
+ t.PackedValue += stepsPerPixel;
+ var v = t.ToVector4();
+ //v.W = (x - left) / (float)left;
+ c.PackFromVector4(v);
+ pixels[x, y] = c;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
index bcccd1b44..1c960e0e8 100644
--- a/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
@@ -44,13 +44,26 @@ namespace ImageSharp.Tests
///
///
/// The required extension
- 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}";
}
///
@@ -80,7 +100,7 @@ namespace ImageSharp.Tests
where TColor : struct, IPixel
{
string path = this.GetTestOutputFileName(extension);
-
+ extension = Path.GetExtension(path);
IImageFormat format = GetImageFormatByExtension(extension);
encoder = encoder ?? format.Encoder;
@@ -99,8 +119,8 @@ namespace ImageSharp.Tests
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()