diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs
index 3417b8faa..44329e359 100644
--- a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs
@@ -91,7 +91,7 @@ namespace SixLabors.ImageSharp.Processing.Drawing.Processors
sourceRectangle,
this.options))
{
- amount.Span.Fill(this.options.BlendPercentage);
+ amount.Span.Fill(1f);
Parallel.For(
minY,
diff --git a/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig
new file mode 100644
index 000000000..7da01b24f
--- /dev/null
+++ b/src/ImageSharp.Drawing/Processing/Drawing/Processors/FillProcessor.cs.orig
@@ -0,0 +1,121 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using System;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp.Advanced;
+using SixLabors.ImageSharp.Memory;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing.Drawing.Brushes;
+using SixLabors.ImageSharp.Processing.Processors;
+using SixLabors.Primitives;
+
+namespace SixLabors.ImageSharp.Processing.Drawing.Processors
+{
+ ///
+ /// Using the brush as a source of pixels colors blends the brush color with source.
+ ///
+ /// The pixel format.
+ internal class FillProcessor : ImageProcessor
+ where TPixel : struct, IPixel
+ {
+ ///
+ /// The brush.
+ ///
+ private readonly IBrush brush;
+ private readonly GraphicsOptions options;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The brush to source pixel colors from.
+ /// The options
+ public FillProcessor(IBrush brush, GraphicsOptions options)
+ {
+ this.brush = brush;
+ this.options = options;
+ }
+
+ ///
+ protected override void OnFrameApply(ImageFrame source, Rectangle sourceRectangle, Configuration configuration)
+ {
+ int startX = sourceRectangle.X;
+ int endX = sourceRectangle.Right;
+ int startY = sourceRectangle.Y;
+ int endY = sourceRectangle.Bottom;
+
+ // Align start/end positions.
+ int minX = Math.Max(0, startX);
+ int maxX = Math.Min(source.Width, endX);
+ int minY = Math.Max(0, startY);
+ int maxY = Math.Min(source.Height, endY);
+
+ int width = maxX - minX;
+
+<<<<<<< HEAD
+ var solidBrush = this.brush as SolidBrush;
+=======
+ using (IBuffer amount = source.MemoryManager.Allocate(width))
+ using (BrushApplicator applicator = this.brush.CreateApplicator(
+ source,
+ sourceRectangle,
+ this.options))
+ {
+ amount.Span.Fill(1f);
+>>>>>>> master
+
+ // If there's no reason for blending, then avoid it.
+ if (solidBrush != null &&
+ (
+ (this.options.BlenderMode == PixelBlenderMode.Normal && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) ||
+ (this.options.BlenderMode == PixelBlenderMode.Over && this.options.BlendPercentage == 1f && solidBrush.Color.ToVector4().W == 1f) ||
+ (this.options.BlenderMode == PixelBlenderMode.Src)))
+ {
+ Parallel.For(
+ minY,
+ maxY,
+ configuration.ParallelOptions,
+ y =>
+ {
+ int offsetY = y - startY;
+ int offsetX = minX - startX;
+ source.GetPixelRowSpan(y).Slice(minX, width).Fill(solidBrush.Color);
+ });
+ }
+ else
+ {
+ // Reset offset if necessary.
+ if (minX > 0)
+ {
+ startX = 0;
+ }
+
+ if (minY > 0)
+ {
+ startY = 0;
+ }
+
+ using (IBuffer amount = source.MemoryManager.Allocate(width))
+ using (BrushApplicator applicator = this.brush.CreateApplicator(
+ source,
+ sourceRectangle,
+ this.options))
+ {
+ amount.Span.Fill(this.options.BlendPercentage);
+
+ Parallel.For(
+ minY,
+ maxY,
+ configuration.ParallelOptions,
+ y =>
+ {
+ int offsetY = y - startY;
+ int offsetX = minX - startX;
+
+ applicator.Apply(amount.Span, offsetX, offsetY);
+ });
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs b/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
index 02e34092e..83f4fbde6 100644
--- a/tests/ImageSharp.Tests/Drawing/FillSolidBrushTests.cs
+++ b/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(TestImageProvider provider)
+ where TPixel : struct, IPixel
{
- string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
- using (var image = new Image(500, 500))
+ using (Image image = provider.GetImage())
{
- image.Mutate(x => x.Fill(Rgba32.HotPink));
- image.Save($"{path}/DefaultBack.png");
-
- using (PixelAccessor sourcePixels = image.Lock())
- {
- Assert.Equal(Rgba32.HotPink, sourcePixels[9, 9]);
+ TPixel color = NamedColors.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(TestImageProvider provider)
+ where TPixel : struct, IPixel
{
- string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
- using (var image = new Image(500, 500))
+ using (Image image = provider.GetImage())
{
- image.Mutate(x => x
- .BackgroundColor(Rgba32.Blue)
- .Fill(Rgba32.HotPink));
- image.Save($"{path}/Simple.png");
+ TPixel color = NamedColors.HotPink;
+ image.Mutate(c => c.Fill(color));
- using (PixelAccessor 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(TestImageProvider provider, string newColorName)
+ where TPixel : struct, IPixel
{
- string path = TestEnvironment.CreateOutputDirectory("Fill", "SolidBrush");
- using (var image = new Image(500, 500))
+ using (Image image = provider.GetImage())
{
- var color = new Rgba32(Rgba32.HotPink.R, Rgba32.HotPink.G, Rgba32.HotPink.B, 150);
+ TPixel color = TestUtils.GetPixelOfNamedColor(newColorName);
+ image.Mutate(c => c.Fill(color));
+
+ image.DebugSave(provider, newColorName, appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);
+ image.ComparePixelBufferTo(color);
+ }
+ }
+
+ public static readonly TheoryData BlendData =
+ new TheoryData()
+ {
+ { 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 sourcePixels = image.Lock())
+ [Theory]
+ [WithSolidFilledImages(nameof(BlendData), 16, 16, "Red", PixelTypes.Rgba32)]
+ public void BlendFillColorOverBackround(
+ TestImageProvider provider,
+ bool triggerFillRegion,
+ string newColorName,
+ float alpha,
+ PixelBlenderMode blenderMode,
+ float blendPercentage)
+ where TPixel : struct, IPixel
+ {
+ var vec = TestUtils.GetPixelOfNamedColor(newColorName).ToVector4();
+ vec.W = alpha;
+
+ TPixel fillColor = default;
+ fillColor.PackFromVector4(vec);
+
+ using (Image 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(fillColor), region));
+ }
+ else
{
- Assert.Equal(mergedColor, sourcePixels[9, 9]);
- Assert.Equal(mergedColor, sourcePixels[199, 149]);
+ image.Mutate(c => c.Fill(options, new SolidBrush(fillColor)));
}
+
+ var testOutputDetails = new
+ {
+ triggerFillRegion = triggerFillRegion,
+ newColorName = newColorName,
+ alpha = alpha,
+ blenderMode = blenderMode,
+ blendPercentage = blendPercentage
+ };
+
+ image.DebugSave(
+ provider,
+ testOutputDetails,
+ appendPixelTypeToFileName: false,
+ appendSourceFileOrDescription: false);
+
+ PixelBlender blender = PixelOperations.Instance.GetPixelBlender(blenderMode);
+ TPixel expectedPixel = blender.Blend(bgColor, fillColor, blendPercentage);
+
+ image.ComparePixelBufferTo(expectedPixel);
}
}
-
}
}
diff --git a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs b/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs
index 991f7108f..f95db45f7 100644
--- a/tests/ImageSharp.Tests/TestUtilities/Attributes/WithSolidFilledImagesAttribute.cs
+++ b/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(colorName);
this.R = c.R;
this.G = c.G;
this.B = c.B;
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
index c2c0eb487..f37df48dc 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
@@ -346,13 +346,26 @@ namespace SixLabors.ImageSharp.Tests
Span expectedPixels)
where TPixel : struct, IPixel
{
- Span actual = image.GetPixelSpan();
+ Span 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 ComparePixelBufferTo(this Image image, TPixel expectedPixel)
+ where TPixel : struct, IPixel
+ {
+ Span 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;
diff --git a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
index 4f9a558d4..85729acd3 100644
--- a/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
+++ b/tests/ImageSharp.Tests/TestUtilities/TestUtils.cs
@@ -147,6 +147,11 @@ namespace SixLabors.ImageSharp.Tests
/// The pixel types
internal static PixelTypes[] GetAllPixelTypes() => (PixelTypes[])Enum.GetValues(typeof(PixelTypes));
+ internal static TPixel GetPixelOfNamedColor(string colorName)
+ where TPixel : struct, IPixel
+ {
+ return (TPixel)typeof(NamedColors).GetTypeInfo().GetField(colorName).GetValue(null);
+ }
///
/// Utility for testing image processor extension methods: