// 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)); } } } }