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
50 changes: 25 additions & 25 deletions src/ImageSharp.Drawing/Processing/ImageBrush.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ public class ImageBrush : IBrush
/// </summary>
private readonly Image image;

private readonly RectangleF? region;
/// <summary>
/// The region of the source image we will be using to paint.
/// </summary>
private readonly RectangleF region;

/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush"/> class.
/// </summary>
/// <param name="image">The image.</param>
public ImageBrush(Image image)
=> this.image = image;
: this(image, image.Bounds())
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ImageBrush"/> class.
Expand All @@ -49,16 +54,13 @@ public BrushApplicator<TPixel> CreateApplicator<TPixel>(
RectangleF region)
where TPixel : unmanaged, IPixel<TPixel>
{
RectangleF interest = this.region ?? region;

if (this.image is Image<TPixel> specificImage)
{
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, interest, false);
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, this.region, false);
}

specificImage = this.image.CloneAs<TPixel>();

return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, interest, true);
return new ImageBrushApplicator<TPixel>(configuration, options, source, specificImage, region, this.region, true);
}

/// <summary>
Expand All @@ -75,14 +77,9 @@ private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
private readonly bool shouldDisposeImage;

/// <summary>
/// The y-length.
/// The region of the source image we will be using to draw from.
/// </summary>
private readonly int yLength;

/// <summary>
/// The x-length.
/// </summary>
private readonly int xLength;
private readonly Rectangle sourceRegion;

/// <summary>
/// The Y offset.
Expand All @@ -93,7 +90,6 @@ private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
/// The X offset.
/// </summary>
private readonly int offsetX;

private bool isDisposed;

/// <summary>
Expand All @@ -103,32 +99,35 @@ private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
/// <param name="options">The graphics options.</param>
/// <param name="target">The target image.</param>
/// <param name="image">The image.</param>
/// <param name="region">The region.</param>
/// <param name="targetRegion">The region of the target image we will be drawing to.</param>
/// <param name="sourceRegion">The region of the source image we will be using to source pixels to draw from.</param>
/// <param name="shouldDisposeImage">Whether to dispose the image on disposal of the applicator.</param>
public ImageBrushApplicator(
Configuration configuration,
GraphicsOptions options,
ImageFrame<TPixel> target,
Image<TPixel> image,
RectangleF region,
RectangleF targetRegion,
RectangleF sourceRegion,
bool shouldDisposeImage)
: base(configuration, options, target)
{
this.sourceImage = image;
this.sourceFrame = image.Frames.RootFrame;
this.shouldDisposeImage = shouldDisposeImage;
this.xLength = image.Width;
this.yLength = image.Height;
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0);

this.sourceRegion = Rectangle.Intersect(image.Bounds(), (Rectangle)sourceRegion);

this.offsetY = (int)MathF.Max(MathF.Floor(targetRegion.Top), 0);
this.offsetX = (int)MathF.Max(MathF.Floor(targetRegion.Left), 0);
}

internal TPixel this[int x, int y]
{
get
{
int srcX = (x - this.offsetX) % this.xLength;
int srcY = (y - this.offsetY) % this.yLength;
int srcX = ((x - this.offsetX) % this.sourceRegion.Width) + this.sourceRegion.X;
int srcY = ((y - this.offsetY) % this.sourceRegion.Width) + this.sourceRegion.Y;
return this.sourceFrame[srcX, srcY];
}
}
Expand Down Expand Up @@ -161,15 +160,16 @@ public override void Apply(Span<float> scanline, int x, int y)
Span<float> amountSpan = amountBuffer.Memory.Span;
Span<TPixel> overlaySpan = overlay.Memory.Span;

int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX;
int sourceY = ((y - this.offsetY) % this.sourceRegion.Width) + this.sourceRegion.Y;
Span<TPixel> sourceRow = this.sourceFrame.GetPixelRowSpan(sourceY);

for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;

int sourceX = (i + offsetX) % this.xLength;
int sourceX = ((i + offsetX) % this.sourceRegion.Width) + this.sourceRegion.X;

overlaySpan[i] = sourceRow[sourceX];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public void Execute()
// Use an image brush to apply cloned image as the source for filling the shape.
// We pass explicit bounds to avoid the need to crop the clone;
RectangleF bounds = this.recursiveImageProcessor.Path.Bounds;
var brush = new ImageBrush(clone, new RectangleF(0, 0, bounds.Width, bounds.Height));
var brush = new ImageBrush(clone, bounds);

// Grab hold of an image processor that can fill paths with a brush to allow it to do the hard pixel pushing for us
var processor = new FillPathProcessor(this.recursiveImageProcessor.Options, brush, this.recursiveImageProcessor.Path);
Expand Down
11 changes: 6 additions & 5 deletions tests/ImageSharp.Drawing.Tests/Drawing/ClipTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ namespace SixLabors.ImageSharp.Drawing.Tests.Drawing
public class ClipTests
{
[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 0, 0)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, -20, -20)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 20, 20)]
public void Clip<TPixel>(TestImageProvider<TPixel> provider, float dx, float dy)
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 0, 0, 0.5)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, -20, -20, 0.5)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 20, 20, 0.5)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, 40, 60, 0.2)]
public void Clip<TPixel>(TestImageProvider<TPixel> provider, float dx, float dy, float sizeMult)
where TPixel : unmanaged, IPixel<TPixel>
{
FormattableString testDetails = $"offset_x{dx}_y{dy}";
provider.RunValidatingProcessorTest(
x =>
{
Size size = x.GetCurrentSize();
int outerRadii = Math.Min(size.Width, size.Height) / 2;
int outerRadii = (int)(Math.Min(size.Width, size.Height) * sizeMult);
var star = new Star(new PointF(size.Width / 2, size.Height / 2), 5, outerRadii / 2, outerRadii);

var builder = Matrix3x2.CreateTranslation(new Vector2(dx, dy));
Expand Down
27 changes: 27 additions & 0 deletions tests/ImageSharp.Drawing.Tests/Drawing/FillPolygonTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,33 @@ public void FillPolygon_ImageBrush<TPixel>(TestImageProvider<TPixel> provider, s
}
}

[Theory]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Png.Ducky)]
[WithBasicTestPatternImages(250, 350, PixelTypes.Rgba32, TestImages.Bmp.Car)]
public void FillPolygon_ImageBrush_Rect<TPixel>(TestImageProvider<TPixel> provider, string brushImageName)
where TPixel : unmanaged, IPixel<TPixel>
{
PointF[] simplePath =
{
new Vector2(10, 10), new Vector2(200, 50), new Vector2(50, 200)
};

using (var brushImage = Image.Load<TPixel>(TestFile.Create(brushImageName).Bytes))
{
float top = brushImage.Height / 4;
float left = brushImage.Width / 4;
float height = top * 2;
float width = left * 2;

var brush = new ImageBrush(brushImage, new RectangleF(left, top, width, height));

provider.RunValidatingProcessorTest(
c => c.FillPolygon(brush, simplePath),
System.IO.Path.GetFileNameWithoutExtension(brushImageName) + "_rect",
appendSourceFileOrDescription: false);
}
}

[Theory]
[WithBasicTestPatternImages(250, 250, PixelTypes.Rgba32)]
public void Fill_RectangularPolygon<TPixel>(TestImageProvider<TPixel> provider)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.