Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle source
sourceRectangle,
this.options))
{
amount.Span.Fill(this.options.BlendPercentage);
amount.Span.Fill(1f);

Parallel.For(
minY,
Expand Down
173 changes: 129 additions & 44 deletions tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public WithSolidFilledImagesAttribute(
{
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;
Expand Down
19 changes: 16 additions & 3 deletions tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -346,13 +346,26 @@ public static Image<TPixel> ComparePixelBufferTo<TPixel>(
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;
Expand Down
5 changes: 5 additions & 0 deletions tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ internal static bool HasAll(this PixelTypes pixelTypes, PixelTypes flagsToCheck)
/// <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:
Expand Down