// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Drawing.Processing {
///
/// Provides an implementation of an image brush for painting images within areas.
///
/// The pixel format of the source image.
public sealed class ImageBrush : ImageBrush
where TPixel : unmanaged, IPixel
{
///
/// Initializes a new instance of the class.
///
/// The source image to draw.
public ImageBrush(Image image)
: base(image)
=> this.SourceImage = image;
///
/// Initializes a new instance of the class.
///
/// The source image to draw.
/// An offset to apply to the image while drawing the texture.
public ImageBrush(Image image, Point offset)
: base(image, offset)
=> this.SourceImage = image;
///
/// Initializes a new instance of the class.
///
/// The source image to draw.
/// The region of interest within the source image.
public ImageBrush(Image image, RectangleF region)
: base(image, region)
=> this.SourceImage = image;
///
/// Initializes a new instance of the class.
///
/// The source image to draw.
/// The region of interest within the source image.
/// An offset to apply to the image while drawing the texture.
public ImageBrush(Image image, RectangleF region, Point offset)
: base(image, region, offset)
=> this.SourceImage = image;
///
/// Gets the typed source image used by this brush.
///
public Image SourceImage { get; }
}
///
/// The untyped base class for image brushes, used to support non-generic brush references in drawing contexts.
///
public abstract class ImageBrush : Brush
{
///
/// Initializes a new instance of the class.
///
/// The source image to draw.
protected ImageBrush(Image image)
: this(image, image.Bounds)
{
}
///
/// Initializes a new instance of the class.
///
/// The image.
///
/// An offset to apply the to image image while drawing apply the texture.
///
protected ImageBrush(Image image, Point offset)
: this(image, image.Bounds, offset)
{
}
///
/// Initializes a new instance of the class.
///
/// The image.
///
/// The region of interest.
/// This overrides any region used to initialize the brush applicator.
///
protected ImageBrush(Image image, RectangleF region)
: this(image, region, Point.Empty)
{
}
///
/// Initializes a new instance of the class.
///
/// The image.
///
/// The region of interest.
/// This overrides any region used to initialize the brush applicator.
///
///
/// An offset to apply the to image image while drawing apply the texture.
///
protected ImageBrush(Image image, RectangleF region, Point offset)
{
this.UntypedImage = image;
this.SourceRegion = RectangleF.Intersect(image.Bounds, region);
this.Offset = offset;
}
///
/// Gets the source image used by this brush.
///
public Image UntypedImage { get; }
///
/// Gets the source region within the image.
///
public RectangleF SourceRegion { get; }
///
/// Gets the offset applied to the brush origin.
///
public Point Offset { get; }
///
public override bool Equals(Brush? other)
{
if (other is ImageBrush ib)
{
return ib.UntypedImage == this.UntypedImage && ib.SourceRegion == this.SourceRegion;
}
return false;
}
///
public override int GetHashCode() => HashCode.Combine(this.UntypedImage, this.SourceRegion);
///
public override BrushRenderer CreateRenderer(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
RectangleF region)
{
if (this.UntypedImage is Image image)
{
return new ImageBrushRenderer(configuration, options, canvasWidth, image, region, this.SourceRegion, this.Offset);
}
// This will never be hit as the brush is always normalized by the drawing canvas
// but we do it to satisfy the type system.
ThrowIfInvalidImagePixelFormat();
return null;
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
private static void ThrowIfInvalidImagePixelFormat()
=> throw new UnreachableException("The pixel format of the image is not supported by this brush renderer");
///
/// The image brush applicator.
///
/// The pixel format.
private sealed class ImageBrushRenderer : BrushRenderer
where TPixel : unmanaged, IPixel
{
private readonly ImageFrame sourceFrame;
///
/// The region of the source image we will be using to draw from.
///
private readonly Rectangle sourceRegion;
///
/// The Y offset.
///
private readonly int offsetY;
///
/// The X offset.
///
private readonly int offsetX;
///
/// Initializes a new instance of the class.
///
/// The configuration instance to use when performing operations.
/// The graphics options.
/// The canvas width for the current render pass.
/// The image.
/// The region of the target image we will be drawing to.
/// The region of the source image we will be using to source pixels to draw from.
/// An offset to apply to the texture while drawing.
public ImageBrushRenderer(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
Image image,
RectangleF targetRegion,
RectangleF sourceRegion,
Point offset)
: base(configuration, options, canvasWidth)
{
this.sourceFrame = image.Frames.RootFrame;
this.sourceRegion = Rectangle.Intersect(image.Bounds, (Rectangle)sourceRegion);
this.offsetY = (int)MathF.Floor(targetRegion.Top) + offset.Y;
this.offsetX = (int)MathF.Floor(targetRegion.Left) + offset.X;
}
internal TPixel this[int x, int y]
{
get
{
int srcX = ((x - this.offsetX) % this.sourceRegion.Width) + this.sourceRegion.X;
int srcY = ((y - this.offsetY) % this.sourceRegion.Height) + this.sourceRegion.Y;
return this.sourceFrame[srcX, srcY];
}
}
///
public override void Apply(
Span destinationRow,
ReadOnlySpan scanline,
int x,
int y,
BrushWorkspace workspace)
{
Span amountSpan = workspace.GetAmounts(scanline.Length);
Span overlaySpan = workspace.GetOverlays(scanline.Length);
int offsetX = x - this.offsetX;
int sourceY = ((((y - this.offsetY) % this.sourceRegion.Height) // clamp the number between -height and +height
+ this.sourceRegion.Height) % this.sourceRegion.Height) // clamp the number between 0 and +height
+ this.sourceRegion.Y;
Span sourceRow = this.sourceFrame.PixelBuffer.DangerousGetRowSpan(sourceY);
for (int i = 0; i < scanline.Length; i++)
{
amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
int sourceX = ((((i + offsetX) % this.sourceRegion.Width) // clamp the number between -width and +width
+ this.sourceRegion.Width) % this.sourceRegion.Width) // clamp the number between 0 and +width
+ this.sourceRegion.X;
overlaySpan[i] = sourceRow[sourceX];
}
this.Blender.Blend(
this.Configuration,
destinationRow,
destinationRow,
overlaySpan,
amountSpan,
workspace.GetBlendScratch(scanline.Length, 3));
}
}
}
}