Browse Source

Merge remote-tracking branch 'origin/master' into GradientBrush

af/merge-core
Unknown 8 years ago
parent
commit
13ae03e598
  1. 2
      src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs
  2. 5
      src/ImageSharp/Formats/Png/IPngEncoderOptions.cs
  3. 7
      src/ImageSharp/Formats/Png/PngEncoder.cs
  4. 38
      src/ImageSharp/Formats/Png/PngEncoderCore.cs
  5. 46
      src/ImageSharp/Formats/PngFilterMethod.cs
  6. 92
      tests/ImageSharp.Tests/Drawing/BlendedShapes.cs
  7. 173
      tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
  8. 154
      tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs
  9. 46
      tests/ImageSharp.Tests/Formats/Png/PngEncoderTests.cs
  10. 2
      tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs
  11. 52
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  12. 41
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  13. 5
      tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

2
src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs

@ -68,7 +68,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors
sourceRectangle,
this.options))
{
amount.Span.Fill(this.options.BlendPercentage);
amount.Span.Fill(1f);
Parallel.For(
minY,

5
src/ImageSharp/Formats/Png/IPngEncoderOptions.cs

@ -15,6 +15,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
PngColorType PngColorType { get; }
/// <summary>
/// Gets the png filter method.
/// </summary>
PngFilterMethod PngFilterMethod { get; }
/// <summary>
/// Gets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>

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

@ -14,10 +14,15 @@ namespace SixLabors.ImageSharp.Formats.Png
public sealed class PngEncoder : IImageEncoder, IPngEncoderOptions
{
/// <summary>
/// Gets or sets the png color type
/// Gets or sets the png color type.
/// </summary>
public PngColorType PngColorType { get; set; } = PngColorType.RgbWithAlpha;
/// <summary>
/// Gets or sets the png filter method.
/// </summary>
public PngFilterMethod PngFilterMethod { get; set; } = PngFilterMethod.Adaptive;
/// <summary>
/// Gets or sets the compression level 1-9.
/// <remarks>Defaults to 6.</remarks>

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

@ -46,6 +46,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
private readonly PngColorType pngColorType;
/// <summary>
/// The png filter method.
/// </summary>
private readonly PngFilterMethod pngFilterMethod;
/// <summary>
/// The quantizer for reducing the color count.
/// </summary>
@ -145,6 +150,7 @@ namespace SixLabors.ImageSharp.Formats.Png
{
this.memoryManager = memoryManager;
this.pngColorType = options.PngColorType;
this.pngFilterMethod = options.PngFilterMethod;
this.compressionLevel = options.CompressionLevel;
this.gamma = options.Gamma;
this.quantizer = options.Quantizer;
@ -272,7 +278,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param>
private void CollecTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
private void CollectTPixelBytes<TPixel>(ReadOnlySpan<TPixel> rowSpan)
where TPixel : struct, IPixel<TPixel>
{
if (this.bytesPerPixel == 4)
@ -292,7 +298,7 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="rowSpan">The row span.</param>
/// <param name="row">The row.</param>
/// <returns>The <see cref="T:byte[]"/></returns>
/// <returns>The <see cref="IManagedByteBuffer"/></returns>
private IManagedByteBuffer EncodePixelRow<TPixel>(ReadOnlySpan<TPixel> rowSpan, int row)
where TPixel : struct, IPixel<TPixel>
{
@ -307,11 +313,35 @@ namespace SixLabors.ImageSharp.Formats.Png
this.CollectGrayscaleBytes(rowSpan);
break;
default:
this.CollecTPixelBytes(rowSpan);
this.CollectTPixelBytes(rowSpan);
break;
}
return this.GetOptimalFilteredScanline();
switch (this.pngFilterMethod)
{
case PngFilterMethod.None:
NoneFilter.Encode(this.rawScanline.Span, this.result.Span);
return this.result;
case PngFilterMethod.Sub:
SubFilter.Encode(this.rawScanline.Span, this.sub.Span, this.bytesPerPixel, out int _);
return this.sub;
case PngFilterMethod.Up:
UpFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.up.Span, out int _);
return this.up;
case PngFilterMethod.Average:
AverageFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.average.Span, this.bytesPerPixel, out int _);
return this.average;
case PngFilterMethod.Paeth:
PaethFilter.Encode(this.rawScanline.Span, this.previousScanline.Span, this.paeth.Span, this.bytesPerPixel, out int _);
return this.paeth;
default:
return this.GetOptimalFilteredScanline();
}
}
/// <summary>

46
src/ImageSharp/Formats/PngFilterMethod.cs

@ -0,0 +1,46 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
namespace SixLabors.ImageSharp.Formats
{
/// <summary>
/// Provides enumeration of available PNG filter methods.
/// </summary>
public enum PngFilterMethod
{
/// <summary>
/// With the None filter, the scanline is transmitted unmodified.
/// </summary>
None,
/// <summary>
/// The Sub filter transmits the difference between each byte and the value of the corresponding
/// byte of the prior pixel.
/// </summary>
Sub,
/// <summary>
/// The Up filter is just like the <see cref="Sub"/> filter except that the pixel immediately above the current pixel,
/// rather than just to its left, is used as the predictor.
/// </summary>
Up,
/// <summary>
/// The Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel.
/// </summary>
Average,
/// <summary>
/// The Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left),
/// then chooses as predictor the neighboring pixel closest to the computed value.
/// </summary>
Paeth,
/// <summary>
/// Computes the output scanline using all five filters, and selects the filter that gives the smallest sum of
/// absolute values of outputs.
/// This method usually outperforms any single fixed filter choice.
/// </summary>
Adaptive,
}
}

92
tests/ImageSharp.Tests/Drawing/BlendedShapes.cs

@ -1,92 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests.Drawing
{
using SixLabors.ImageSharp.Processing;
public class BlendedShapes
{
public static IEnumerable<object[]> modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode)))
.Select(x => new object[] { x });
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x
.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)
));
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_transparent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.Transparent, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_transparent50Percent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
var c = NamedColors<TPixel>.Red.ToVector4();
c.W *= 0.5f;
TPixel pixel = default(TPixel);
pixel.PackFromVector4(c);
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, pixel, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_doldidEllips<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Mutate(x => x.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(x => x.Fill(new GraphicsOptions(true) { BlenderMode = mode }, NamedColors<TPixel>.Black, new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
img.DebugSave(provider, new { mode });
}
}
}
}

173
tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs

@ -1,79 +1,164 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.Numerics;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Processing.Overlays;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing.Drawing.Brushes;
using SixLabors.Shapes;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
public class FillSolidBrushTests : FileTestBase
[GroupOutput("Drawing")]
public class FillSolidBrushTests
{
[Fact]
public void ImageShouldBeFloodFilledWithColorOnDefaultBackground()
[Theory]
[WithBlankImages(1, 1, PixelTypes.Rgba32)]
[WithBlankImages(7, 4, PixelTypes.Rgba32)]
[WithBlankImages(16, 7, PixelTypes.Rgba32)]
[WithBlankImages(33, 32, PixelTypes.Rgba32)]
[WithBlankImages(400, 500, PixelTypes.Rgba32)]
public void DoesNotDependOnSize<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
using (var image = new Image<Rgba32>(500, 500))
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x.Fill(Rgba32.HotPink));
image.Save($"{path}/DefaultBack.png");
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]);
TPixel color = NamedColors<TPixel>.HotPink;
image.Mutate(c => c.Fill(color));
Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]);
}
image.DebugSave(provider, appendPixelTypeToFileName: false);
image.ComparePixelBufferTo(color);
}
}
[Fact]
public void ImageShouldBeFloodFilledWithColor()
[Theory]
[WithBlankImages(16, 16, PixelTypes.Rgba32 | PixelTypes.Argb32 | PixelTypes.RgbaVector)]
public void DoesNotDependOnSinglePixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
using (var image = new Image<Rgba32>(500, 500))
using (Image<TPixel> image = provider.GetImage())
{
image.Mutate(x => x
.BackgroundColor(Rgba32.Blue)
.Fill(Rgba32.HotPink));
image.Save($"{path}/Simple.png");
TPixel color = NamedColors<TPixel>.HotPink;
image.Mutate(c => c.Fill(color));
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
{
Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]);
Assert.Equal(Rgba32.HotPink, sourcePixels[199, 149]);
}
image.DebugSave(provider, appendSourceFileOrDescription: false);
image.ComparePixelBufferTo(color);
}
}
[Fact]
public void ImageShouldBeFloodFilledWithColorOpacity()
[Theory]
[WithSolidFilledImages(16, 16, "Red", PixelTypes.Rgba32, "Blue")]
[WithSolidFilledImages(16, 16, "Yellow", PixelTypes.Rgba32, "Khaki")]
public void WhenColorIsOpaque_OverridePreviousColor<TPixel>(TestImageProvider<TPixel> provider, string newColorName)
where TPixel : struct, IPixel<TPixel>
{
string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
using (var image = new Image<Rgba32>(500, 500))
using (Image<TPixel> image = provider.GetImage())
{
var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
TPixel color = TestUtils.GetPixelOfNamedColor<TPixel>(newColorName);
image.Mutate(c => c.Fill(color));
image.DebugSave(provider, newColorName, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
image.ComparePixelBufferTo(color);
}
}
public static readonly TheoryData<bool, string, float, PixelBlenderMode, float> BlendData =
new TheoryData<bool, string, float, PixelBlenderMode, float>()
{
{ false, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f },
{ false, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f },
{ false, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f },
{ false, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f },
{ false, "Green", 0.5f, PixelBlenderMode.Add, 0.3f },
{ false, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f },
image.Mutate(x => x
.BackgroundColor(Rgba32.Blue)
.Fill(color));
image.Save($"{path}/Opacity.png");
{ true, "Blue", 0.5f, PixelBlenderMode.Normal, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Normal, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Normal, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Normal, 0.8f },
//shift background color towards forground color by the opacity amount
var mergedColor = new Rgba32(Vector4.Lerp(Rgba32.Blue.ToVector4(), Rgba32.HotPink.ToVector4(), 150f / 255f));
{ true, "Blue", 0.5f, PixelBlenderMode.Multiply, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Multiply, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Multiply, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Multiply, 0.8f },
{ true, "Blue", 0.5f, PixelBlenderMode.Add, 1.0f },
{ true, "Blue", 1.0f, PixelBlenderMode.Add, 0.5f },
{ true, "Green", 0.5f, PixelBlenderMode.Add, 0.3f },
{ true, "HotPink", 0.8f, PixelBlenderMode.Add, 0.8f },
};
using (PixelAccessor<Rgba32> sourcePixels = image.Lock())
[Theory]
[WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)]
public void BlendFillColorOverBackround<TPixel>(
TestImageProvider<TPixel> provider,
bool triggerFillRegion,
string newColorName,
float alpha,
PixelBlenderMode blenderMode,
float blendPercentage)
where TPixel : struct, IPixel<TPixel>
{
var vec = TestUtils.GetPixelOfNamedColor<RgbaVector>(newColorName).ToVector4();
vec.W = alpha;
TPixel fillColor = default;
fillColor.PackFromVector4(vec);
using (Image<TPixel> image = provider.GetImage())
{
TPixel bgColor = image[0, 0];
var options = new GraphicsOptions(false)
{
BlenderMode = blenderMode,
BlendPercentage = blendPercentage
};
if (triggerFillRegion)
{
var region = new ShapeRegion(new RectangularPolygon(0, 0, 16, 16));
image.Mutate(c => c.Fill(options, new SolidBrush<TPixel>(fillColor), region));
}
else
{
Assert.Equal(mergedColor, sourcePixels[9, 9]);
Assert.Equal(mergedColor, sourcePixels[199, 149]);
image.Mutate(c => c.Fill(options, new SolidBrush<TPixel>(fillColor)));
}
var testOutputDetails = new
{
triggerFillRegion = triggerFillRegion,
newColorName = newColorName,
alpha = alpha,
blenderMode = blenderMode,
blendPercentage = blendPercentage
};
image.DebugSave(
provider,
testOutputDetails,
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(blenderMode);
TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage);
image.ComparePixelBufferTo(expectedPixel);
}
}
}
}

154
tests/ImageSharp.Tests/Drawing/SolidFillBlendedShapesTests.cs

@ -0,0 +1,154 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Collections.Generic;
using System.Linq;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Drawing;
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison;
using SixLabors.Primitives;
using Xunit;
// ReSharper disable InconsistentNaming
namespace SixLabors.ImageSharp.Tests.Drawing
{
[GroupOutput("Drawing")]
public class SolidFillBlendedShapesTests
{
public static IEnumerable<object[]> modes =
((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x });
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>(
TestImageProvider<TPixel> provider,
PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = img.Width / 100;
int scaleY = img.Height / 100;
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)
)
.Fill(new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))
);
VerifyImage(provider, mode, img);
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = img.Width / 100;
int scaleY = img.Height / 100;
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.Transparent,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
VerifyImage(provider, mode, img);
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>(
TestImageProvider<TPixel> provider,
PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = (img.Width / 100);
int scaleY = (img.Height / 100);
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.HotPink,
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY)));
var c = NamedColors<TPixel>.Red.ToVector4();
c.W *= 0.5f;
var pixel = default(TPixel);
pixel.PackFromVector4(c);
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
pixel,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))
);
VerifyImage(provider, mode, img); ;
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> img = provider.GetImage())
{
int scaleX = (img.Width / 100);
int scaleY = (img.Height / 100);
img.Mutate(
x => x.Fill(
NamedColors<TPixel>.DarkBlue,
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY)));
img.Mutate(
x => x.Fill(
new GraphicsOptions(true) { BlenderMode = mode },
NamedColors<TPixel>.Black,
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)));
VerifyImage(provider, mode, img);
}
}
private static void VerifyImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode, Image<TPixel> img)
where TPixel : struct, IPixel<TPixel>
{
img.DebugSave(
provider,
new { mode },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
var comparer = ImageComparer.TolerantPercentage(0.01f, 3);
img.CompareFirstFrameToReferenceOutput(comparer,
provider,
new { mode },
appendPixelTypeToFileName: false,
appendSourceFileOrDescription: false);
}
}
}

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

@ -32,21 +32,31 @@ namespace SixLabors.ImageSharp.Tests
PngColorType.GrayscaleWithAlpha,
};
public static readonly TheoryData<PngFilterMethod> PngFilterMethods = new TheoryData<PngFilterMethod>
{
PngFilterMethod.None,
PngFilterMethod.Sub,
PngFilterMethod.Up,
PngFilterMethod.Average,
PngFilterMethod.Paeth,
PngFilterMethod.Adaptive
};
/// <summary>
/// All types except Palette
/// </summary>
public static readonly TheoryData<int> CompressionLevels = new TheoryData<int>
{
{
1, 2, 3, 4, 5, 6, 7, 8, 9
};
public static readonly TheoryData<int> PaletteSizes = new TheoryData<int>
{
{
30, 55, 100, 201, 255
};
public static readonly TheoryData<int> PaletteLargeOnly = new TheoryData<int>
{
{
80, 100, 120, 230
};
@ -60,7 +70,7 @@ namespace SixLabors.ImageSharp.Tests
public void WorksWithDifferentSizes<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
where TPixel : struct, IPixel<TPixel>
{
TestPngEncoderCore(provider, pngColorType, appendPngColorType: true);
TestPngEncoderCore(provider, pngColorType, PngFilterMethod.Adaptive, appendPngColorType: true);
}
[Theory]
@ -68,7 +78,15 @@ namespace SixLabors.ImageSharp.Tests
public void IsNotBoundToSinglePixelType<TPixel>(TestImageProvider<TPixel> provider, PngColorType pngColorType)
where TPixel : struct, IPixel<TPixel>
{
TestPngEncoderCore(provider, pngColorType, appendPixelType: true, appendPngColorType: true);
TestPngEncoderCore(provider, pngColorType, PngFilterMethod.Adaptive, appendPixelType: true, appendPngColorType: true);
}
[Theory]
[WithTestPatternImages(nameof(PngFilterMethods), 24, 24, PixelTypes.Rgba32)]
public void WorksWithAllFilterMethods<TPixel>(TestImageProvider<TPixel> provider, PngFilterMethod pngFilterMethod)
where TPixel : struct, IPixel<TPixel>
{
TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, pngFilterMethod, appendPngFilterMethod: true);
}
[Theory]
@ -76,7 +94,7 @@ namespace SixLabors.ImageSharp.Tests
public void WorksWithAllCompressionLevels<TPixel>(TestImageProvider<TPixel> provider, int compressionLevel)
where TPixel : struct, IPixel<TPixel>
{
TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, compressionLevel, appendCompressionLevel: true);
TestPngEncoderCore(provider, PngColorType.RgbWithAlpha, PngFilterMethod.Adaptive, compressionLevel, appendCompressionLevel: true);
}
[Theory]
@ -84,7 +102,7 @@ namespace SixLabors.ImageSharp.Tests
public void PaletteColorType_WuQuantizer<TPixel>(TestImageProvider<TPixel> provider, int paletteSize)
where TPixel : struct, IPixel<TPixel>
{
TestPngEncoderCore(provider, PngColorType.Palette, paletteSize: paletteSize, appendPaletteSize: true);
TestPngEncoderCore(provider, PngColorType.Palette, PngFilterMethod.Adaptive, paletteSize: paletteSize, appendPaletteSize: true);
}
private static bool HasAlpha(PngColorType pngColorType) =>
@ -93,9 +111,11 @@ namespace SixLabors.ImageSharp.Tests
private static void TestPngEncoderCore<TPixel>(
TestImageProvider<TPixel> provider,
PngColorType pngColorType,
PngFilterMethod pngFilterMethod,
int compressionLevel = 6,
int paletteSize = 255,
bool appendPngColorType = false,
bool appendPngFilterMethod = false,
bool appendPixelType = false,
bool appendCompressionLevel = false,
bool appendPaletteSize = false)
@ -111,14 +131,16 @@ namespace SixLabors.ImageSharp.Tests
var encoder = new PngEncoder
{
PngColorType = pngColorType,
PngFilterMethod = pngFilterMethod,
CompressionLevel = compressionLevel,
Quantizer = new WuQuantizer(paletteSize)
};
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : "";
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : "";
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : "";
string debugInfo = $"{pngColorTypeInfo}{compressionLevelInfo}{paletteSizeInfo}";
string pngColorTypeInfo = appendPngColorType ? pngColorType.ToString() : string.Empty;
string pngFilterMethodInfo = appendPngFilterMethod ? pngFilterMethod.ToString() : string.Empty;
string compressionLevelInfo = appendCompressionLevel ? $"_C{compressionLevel}" : string.Empty;
string paletteSizeInfo = appendPaletteSize ? $"_PaletteSize-{paletteSize}" : string.Empty;
string debugInfo = $"{pngColorTypeInfo}{pngFilterMethodInfo}{compressionLevelInfo}{paletteSizeInfo}";
//string referenceInfo = $"{pngColorTypeInfo}";
// Does DebugSave & load reference CompareToReferenceInput():
@ -131,7 +153,7 @@ namespace SixLabors.ImageSharp.Tests
}
IImageDecoder referenceDecoder = TestEnvironment.GetReferenceDecoder(actualOutputFile);
string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType);
string referenceOutputFile = ((ITestImageProvider)provider).Utility.GetReferenceOutputFileName("png", debugInfo, appendPixelType, true);
using (var actualImage = Image.Load<TPixel>(actualOutputFile, referenceDecoder))
using (var referenceImage = Image.Load<TPixel>(referenceOutputFile, referenceDecoder))

2
tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs

@ -133,7 +133,7 @@ namespace SixLabors.ImageSharp.Tests
{
Guard.NotNull(colorName, nameof(colorName));
var c = (Rgba32)typeof(Rgba32).GetTypeInfo().GetField(colorName).GetValue(null);
Rgba32 c = TestUtils.GetPixelOfNamedColor<Rgba32>(colorName);
this.R = c.R;
this.G = c.G;
this.B = c.B;

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

@ -41,16 +41,20 @@ namespace SixLabors.ImageSharp.Tests
/// </summary>
public string TestName { get; set; } = string.Empty;
private string GetTestOutputFileNameImpl(string extension, string details, bool appendPixelTypeToFileName)
private string GetTestOutputFileNameImpl(
string extension,
string details,
bool appendPixelTypeToFileName,
bool appendSourceFileOrDescription)
{
string fn = string.Empty;
if (string.IsNullOrWhiteSpace(extension))
{
extension = null;
}
fn = Path.GetFileNameWithoutExtension(this.SourceFileOrDescription);
string fn = appendSourceFileOrDescription
? Path.GetFileNameWithoutExtension(this.SourceFileOrDescription)
: "";
if (string.IsNullOrWhiteSpace(extension))
{
@ -92,20 +96,24 @@ namespace SixLabors.ImageSharp.Tests
}
private static string Inv(FormattableString formattable) => System.FormattableString.Invariant(formattable);
/// <summary>
/// Gets the recommended file name for the output of the test
/// </summary>
/// <param name="extension">The required extension</param>
/// <param name="testOutputDetails">The settings modifying the output path</param>
/// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to output file name.</param>
/// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
/// <returns>The file test name</returns>
public string GetTestOutputFileName(string extension = null, object testOutputDetails = null, bool appendPixelTypeToFileName = true)
public string GetTestOutputFileName(
string extension = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
{
string detailsString = null;
string s = testOutputDetails as string;
if (s != null)
if (testOutputDetails is string s)
{
detailsString = s;
}
@ -128,7 +136,12 @@ namespace SixLabors.ImageSharp.Tests
);
}
}
return this.GetTestOutputFileNameImpl(extension, detailsString, appendPixelTypeToFileName);
return this.GetTestOutputFileNameImpl(
extension,
detailsString,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
}
@ -139,15 +152,22 @@ namespace SixLabors.ImageSharp.Tests
/// <param name="image">The image instance</param>
/// <param name="extension">The requested extension</param>
/// <param name="encoder">Optional encoder</param>
/// /// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
public string SaveTestOutputFile<TPixel>(
Image<TPixel> image,
string extension = null,
IImageEncoder encoder = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
string path = this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName);
string path = this.GetTestOutputFileName(
extension,
testOutputDetails,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
encoder = encoder ?? TestEnvironment.GetReferenceEncoder(path);
using (FileStream stream = File.OpenWrite(path))
@ -161,9 +181,10 @@ namespace SixLabors.ImageSharp.Tests
int frameCount,
string extension = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
{
string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName);
string baseDir = this.GetTestOutputFileName("", testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription);
if (!Directory.Exists(baseDir))
{
@ -211,10 +232,11 @@ namespace SixLabors.ImageSharp.Tests
internal string GetReferenceOutputFileName(
string extension,
object testOutputDetails,
bool appendPixelTypeToFileName)
bool appendPixelTypeToFileName,
bool appendSourceFileOrDescription)
{
return TestEnvironment.GetReferenceOutputFileName(
this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName)
this.GetTestOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName, appendSourceFileOrDescription)
);
}

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

@ -61,12 +61,14 @@ namespace SixLabors.ImageSharp.Tests
/// <param name="testOutputDetails">Details to be concatenated to the test output file, describing the parameters of the test.</param>
/// <param name="extension">The extension</param>
/// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to the output file name.</param>
/// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
public static Image<TPixel> DebugSave<TPixel>(
this Image<TPixel> image,
ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
if (TestEnvironment.RunsOnCI)
@ -79,7 +81,8 @@ namespace SixLabors.ImageSharp.Tests
image,
extension,
testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName);
appendPixelTypeToFileName: appendPixelTypeToFileName,
appendSourceFileOrDescription: appendSourceFileOrDescription);
return image;
}
@ -210,7 +213,8 @@ namespace SixLabors.ImageSharp.Tests
object testOutputDetails = null,
string extension = "png",
bool grayscale = false,
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
using (var firstFrameOnlyImage = new Image<TPixel>(image.Width, image.Height))
@ -218,7 +222,8 @@ namespace SixLabors.ImageSharp.Tests
provider,
testOutputDetails,
extension,
appendPixelTypeToFileName))
appendPixelTypeToFileName,
appendSourceFileOrDescription))
{
firstFrameOnlyImage.Frames.AddFrame(image.Frames.RootFrame);
firstFrameOnlyImage.Frames.RemoveFrame(0);
@ -255,10 +260,15 @@ namespace SixLabors.ImageSharp.Tests
public static Image<TPixel> GetReferenceOutputImage<TPixel>(this ITestImageProvider provider,
object testOutputDetails = null,
string extension = "png",
bool appendPixelTypeToFileName = true)
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(extension, testOutputDetails, appendPixelTypeToFileName);
string referenceOutputFile = provider.Utility.GetReferenceOutputFileName(
extension,
testOutputDetails,
appendPixelTypeToFileName,
appendSourceFileOrDescription);
if (!File.Exists(referenceOutputFile))
{
@ -336,13 +346,26 @@ namespace SixLabors.ImageSharp.Tests
Span<TPixel> expectedPixels)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> actual = image.GetPixelSpan();
Span<TPixel> actualPixels = image.GetPixelSpan();
Assert.True(expectedPixels.Length == actual.Length, "Buffer sizes are not equal!");
Assert.True(expectedPixels.Length == actualPixels.Length, "Buffer sizes are not equal!");
for (int i = 0; i < expectedPixels.Length; i++)
{
Assert.True(expectedPixels[i].Equals(actual[i]), $"Pixels are different on position {i}!");
Assert.True(expectedPixels[i].Equals(actualPixels[i]), $"Pixels are different on position {i}!");
}
return image;
}
public static Image<TPixel> ComparePixelBufferTo<TPixel>(this Image<TPixel> image, TPixel expectedPixel)
where TPixel : struct, IPixel<TPixel>
{
Span<TPixel> actualPixels = image.GetPixelSpan();
for (int i = 0; i < actualPixels.Length; i++)
{
Assert.True(expectedPixel.Equals(actualPixels[i]), $"Pixels are different on position {i}!");
}
return image;

5
tests/ImageSharp.Tests/TestUtilities/TestUtils.cs

@ -147,6 +147,11 @@ namespace SixLabors.ImageSharp.Tests
/// <returns>The pixel types</returns>
internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes));
internal static TPixel GetPixelOfNamedColor<TPixel>(string colorName)
where TPixel : struct, IPixel<TPixel>
{
return (TPixel)typeof(NamedColors<TPixel>).GetTypeInfo().GetField(colorName).GetValue(null);
}
/// <summary>
/// Utility for testing image processor extension methods:

Loading…
Cancel
Save