Skip to content
Closed
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
55 changes: 53 additions & 2 deletions src/ImageSharp.Drawing/Processing/FillRectangleExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.Primitives;
using SixLabors.Shapes;

Expand Down Expand Up @@ -38,7 +39,7 @@ public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessing
=> source.Fill(brush, new RectangularPolygon(shape.X, shape.Y, shape.Width, shape.Height));

/// <summary>
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
/// Flood fills the image in the shape of the provided rectangle with the specified color.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
Expand All @@ -51,7 +52,7 @@ public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessing
=> source.Fill(options, new SolidBrush<TPixel>(color), shape);

/// <summary>
/// Flood fills the image in the shape of the provided rectangle with the specified brush.
/// Flood fills the image in the shape of the provided rectangle with the specified color.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
Expand All @@ -61,5 +62,55 @@ public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessing
public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, RectangleF shape)
where TPixel : struct, IPixel<TPixel>
=> source.Fill(new SolidBrush<TPixel>(color), shape);

/// <summary>
/// Fills the image in the shape of the provided rectangle with the specified color.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="color">The color.</param>
/// <param name="rectangle">The rectangle shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessingContext<TPixel> source, TPixel color, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.Fill(GraphicsOptions.Default, new SolidBrush<TPixel>(color), rectangle);

/// <summary>
/// Fills the image in the shape of the provided rectangle with the specified brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <param name="rectangle">The rectangle shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessingContext<TPixel> source, IBrush<TPixel> brush, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.Fill(GraphicsOptions.Default, brush, rectangle);

/// <summary>
/// Fills the image in the shape of the provided rectangle with the specified color.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="color">The color.</param>
/// <param name="rectangle">The rectangle shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessingContext<TPixel> source, GraphicsOptions options, TPixel color, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.Fill(options, new SolidBrush<TPixel>(color), rectangle);

/// <summary>
/// Fills the image in the shape of the provided rectangle with the specified brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="options">The graphics options.</param>
/// <param name="brush">The details how to fill the region of interest.</param>
/// <param name="rectangle">The rectangle shape.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Fill<TPixel>(this IImageProcessingContext<TPixel> source, GraphicsOptions options, IBrush<TPixel> brush, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new FillProcessor<TPixel>(brush, options), rectangle);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand @tocsoft 's comment well, it would be the best to select between FillRegionProcessor and FillProcessor at this point, depending on the value of options.Antialias. All overloads should delegate into this method, including the ones which haven't been touched.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Been thinking about this, and now I'm unsure about the expected default behavior for these use cases.

  • If a rectangle is being filled with a solid color, is it expected to antialias corners by default?
  • What about non-solid brushes?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the internal processor code checked to see if the bush was a solid color?

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@

using System;
using System.Buffers;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.ParallelUtils;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Memory;
using SixLabors.Primitives;

namespace SixLabors.ImageSharp.Processing.Processors.Drawing
Expand All @@ -20,23 +18,27 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
internal class FillProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The brush.
/// </summary>
private readonly IBrush<TPixel> brush;
private readonly GraphicsOptions options;

/// <summary>
/// <summary>
/// Initializes a new instance of the <see cref="FillProcessor{TPixel}"/> class.
/// </summary>
/// <param name="brush">The brush to source pixel colors from.</param>
/// <param name="options">The options</param>
public FillProcessor(IBrush<TPixel> brush, GraphicsOptions options)
{
this.brush = brush;
this.options = options;
this.Brush = brush;
this.Options = options;
}

/// <summary>
/// Gets the brush.
/// </summary>
public IBrush<TPixel> Brush { get; }

/// <summary>
/// Gets the options.
/// </summary>
public GraphicsOptions Options { get; }

/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
Expand Down Expand Up @@ -85,10 +87,10 @@ protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle source
}

using (IMemoryOwner<float> amount = source.MemoryAllocator.Allocate<float>(width))
using (BrushApplicator<TPixel> applicator = this.brush.CreateApplicator(
using (BrushApplicator<TPixel> applicator = this.Brush.CreateApplicator(
source,
sourceRectangle,
this.options))
this.Options))
{
amount.GetSpan().Fill(1f);

Expand All @@ -111,14 +113,14 @@ protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle source

private bool IsSolidBrushWithoutBlending(out SolidBrush<TPixel> solidBrush)
{
solidBrush = this.brush as SolidBrush<TPixel>;
solidBrush = this.Brush as SolidBrush<TPixel>;

if (solidBrush == null)
{
return false;
}

return this.options.IsOpaqueColorWithoutBlending(solidBrush.Color);
return this.Options.IsOpaqueColorWithoutBlending(solidBrush.Color);
}
}
}
69 changes: 16 additions & 53 deletions tests/ImageSharp.Tests/Drawing/Paths/FillRectangle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the Apache License, Version 2.0.

using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Primitives;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using Xunit;
Expand All @@ -11,83 +10,47 @@ namespace SixLabors.ImageSharp.Tests.Drawing.Paths
{
public class FillRectangle : BaseImageOperationsExtensionTest
{
GraphicsOptions noneDefault = new GraphicsOptions();
Rgba32 color = Rgba32.HotPink;
SolidBrush<Rgba32> brush = Brushes.Solid(Rgba32.HotPink);
SixLabors.Primitives.Rectangle rectangle = new SixLabors.Primitives.Rectangle(10, 10, 77, 76);
private readonly GraphicsOptions noneDefault = new GraphicsOptions();
private readonly Rgba32 colorForTesting = Rgba32.HotPink;
private readonly SolidBrush<Rgba32> brushForTesting = Brushes.Solid(Rgba32.HotPink);
private readonly SixLabors.Primitives.Rectangle rectangleForTesting = new SixLabors.Primitives.Rectangle(10, 10, 77, 76);

[Fact]
public void CorrectlySetsBrushAndRectangle()
{
this.operations.Fill(this.brush, this.rectangle);
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>();

this.operations.Fill(this.brushForTesting, this.rectangleForTesting);
FillProcessor<Rgba32> processor = this.Verify<FillProcessor<Rgba32>>();
Assert.Equal(GraphicsOptions.Default, processor.Options);

ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);

Assert.Equal(this.brush, processor.Brush);
Assert.Equal(this.brushForTesting, processor.Brush);
}

[Fact]
public void CorrectlySetsBrushRectangleAndOptions()
{
this.operations.Fill(this.noneDefault, this.brush, this.rectangle);
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>();

this.operations.Fill(this.noneDefault, this.brushForTesting, this.rectangleForTesting);
FillProcessor<Rgba32> processor = this.Verify<FillProcessor<Rgba32>>();
Assert.Equal(this.noneDefault, processor.Options);

ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);

Assert.Equal(this.brush, processor.Brush);
Assert.Equal(this.brushForTesting, processor.Brush);
}

[Fact]
public void CorrectlySetsColorAndRectangle()
{
this.operations.Fill(this.color, this.rectangle);
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>();

this.operations.Fill(this.colorForTesting, this.rectangleForTesting);
FillProcessor<Rgba32> processor = this.Verify<FillProcessor<Rgba32>>();
Assert.Equal(GraphicsOptions.Default, processor.Options);

ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);

SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(processor.Brush);
Assert.Equal(this.color, brush.Color);
Assert.Equal(this.colorForTesting, brush.Color);
}

[Fact]
public void CorrectlySetsColorRectangleAndOptions()
{
this.operations.Fill(this.noneDefault, this.color, this.rectangle);
FillRegionProcessor<Rgba32> processor = this.Verify<FillRegionProcessor<Rgba32>>();

this.operations.Fill(this.noneDefault, this.colorForTesting, this.rectangleForTesting);
FillProcessor<Rgba32> processor = this.Verify<FillProcessor<Rgba32>>();
Assert.Equal(this.noneDefault, processor.Options);

ShapeRegion region = Assert.IsType<ShapeRegion>(processor.Region);
Shapes.RectangularPolygon rect = Assert.IsType<Shapes.RectangularPolygon>(region.Shape);
Assert.Equal(rect.Location.X, this.rectangle.X);
Assert.Equal(rect.Location.Y, this.rectangle.Y);
Assert.Equal(rect.Size.Width, this.rectangle.Width);
Assert.Equal(rect.Size.Height, this.rectangle.Height);

SolidBrush<Rgba32> brush = Assert.IsType<SolidBrush<Rgba32>>(processor.Brush);
Assert.Equal(this.color, brush.Color);
Assert.Equal(this.colorForTesting, brush.Color);
}
}
}