// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// /// Provides an implementation of an image brush for painting images within areas. /// public class ImageBrush : IBrush { /// /// The image to paint. /// private readonly Image image; /// /// Initializes a new instance of the class. /// /// The image. public ImageBrush(Image image) { this.image = image; } /// public BrushApplicator CreateApplicator( ImageFrame source, RectangleF region, GraphicsOptions options) where TPixel : struct, IPixel { if (this.image is Image specificImage) { return new ImageBrushApplicator(source, specificImage, region, options, false); } specificImage = this.image.CloneAs(); return new ImageBrushApplicator(source, specificImage, region, options, true); } /// /// The image brush applicator. /// private class ImageBrushApplicator : BrushApplicator where TPixel : struct, IPixel { private ImageFrame sourceFrame; private Image sourceImage; private readonly bool shouldDisposeImage; /// /// The y-length. /// private readonly int yLength; /// /// The x-length. /// private readonly int xLength; /// /// The Y offset. /// private readonly int offsetY; /// /// The X offset. /// private readonly int offsetX; /// /// Initializes a new instance of the class. /// /// The target image. /// The image. /// The region. /// The options /// Whether to dispose the image on disposal of the applicator. public ImageBrushApplicator( ImageFrame target, Image image, RectangleF region, GraphicsOptions options, bool shouldDisposeImage) : base(target, options) { 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); } /// /// Gets the color for a single pixel. /// /// The x. /// The y. /// /// The color /// internal override TPixel this[int x, int y] { get { int srcX = (x - this.offsetX) % this.xLength; int srcY = (y - this.offsetY) % this.yLength; return this.sourceFrame[srcX, srcY]; } } /// public override void Dispose() { if (this.shouldDisposeImage) { this.sourceImage?.Dispose(); this.sourceImage = null; this.sourceFrame = null; } } /// internal override void Apply(Span scanline, int x, int y) { // Create a span for colors using (IMemoryOwner amountBuffer = this.Target.MemoryAllocator.Allocate(scanline.Length)) using (IMemoryOwner overlay = this.Target.MemoryAllocator.Allocate(scanline.Length)) { Span amountSpan = amountBuffer.GetSpan(); Span overlaySpan = overlay.GetSpan(); int sourceY = (y - this.offsetY) % this.yLength; int offsetX = x - this.offsetX; Span 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; TPixel pixel = sourceRow[sourceX]; overlaySpan[i] = pixel; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); this.Blender.Blend( this.sourceFrame.Configuration, destinationRow, destinationRow, overlaySpan, amountSpan); } } } } }