// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.PixelFormats;
using System;
namespace SixLabors.ImageSharp.Drawing.Processing {
///
/// Provides an implementation of a pattern brush for painting patterns.
///
///
/// The patterns that are used to create a custom pattern brush are made up of a repeating matrix of flags,
/// where each flag denotes whether to draw the foreground color or the background color.
/// so to create a new bool[,] with your flags
///
/// For example if you wanted to create a diagonal line that repeat every 4 pixels you would use a pattern like so
/// 1000
/// 0100
/// 0010
/// 0001
///
///
/// or you want a horizontal stripe which is 3 pixels apart you would use a pattern like
/// 1
/// 0
/// 0
///
///
public sealed class PatternBrush : Brush
{
///
/// Initializes a new instance of the class.
///
/// Color of the fore.
/// Color of the back.
/// The pattern.
public PatternBrush(Color foreColor, Color backColor, bool[,] pattern)
: this(foreColor, backColor, new DenseMatrix(pattern))
{
}
///
/// Initializes a new instance of the class.
///
/// Color of the fore.
/// Color of the back.
/// The pattern.
internal PatternBrush(Color foreColor, Color backColor, in DenseMatrix pattern)
{
this.Pattern = new DenseMatrix(pattern.Columns, pattern.Rows);
for (int i = 0; i < pattern.Data.Length; i++)
{
if (pattern.Data[i])
{
this.Pattern.Data[i] = foreColor;
}
else
{
this.Pattern.Data[i] = backColor;
}
}
}
///
/// Initializes a new instance of the class.
///
/// The brush.
internal PatternBrush(PatternBrush brush) => this.Pattern = brush.Pattern;
///
/// Gets the pattern color matrix.
///
public DenseMatrix Pattern { get; }
///
public override bool Equals(Brush? other)
{
if (other is PatternBrush sb)
{
return sb.Pattern.Equals(this.Pattern);
}
return false;
}
///
public override int GetHashCode()
=> this.Pattern.GetHashCode();
///
public override BrushRenderer CreateRenderer(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
RectangleF region)
=>
new PatternBrushRenderer(
configuration,
options,
canvasWidth,
this.Pattern.ToPixelMatrix());
///
/// The pattern brush applicator.
///
/// The pixel format.
private sealed class PatternBrushRenderer : BrushRenderer
where TPixel : unmanaged, IPixel
{
private readonly DenseMatrix pattern;
///
/// 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 pattern.
public PatternBrushRenderer(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
in DenseMatrix pattern)
: base(configuration, options, canvasWidth)
=> this.pattern = pattern;
internal TPixel this[int x, int y]
{
get
{
x %= this.pattern.Columns;
y %= this.pattern.Rows;
// 2d array index at row/column
return this.pattern[y, x];
}
}
///
public override void Apply(
Span destinationRow,
ReadOnlySpan scanline,
int x,
int y,
BrushWorkspace workspace)
{
int patternY = y % this.pattern.Rows;
Span amounts = workspace.GetAmounts(scanline.Length);
Span overlays = workspace.GetOverlays(scanline.Length);
for (int i = 0; i < scanline.Length; i++)
{
amounts[i] = Math.Clamp(scanline[i] * this.Options.BlendPercentage, 0, 1F);
int patternX = (x + i) % this.pattern.Columns;
overlays[i] = this.pattern[patternY, patternX];
}
this.Blender.Blend(
this.Configuration,
destinationRow,
destinationRow,
overlays,
amounts,
workspace.GetBlendScratch(scanline.Length, 3));
}
}
}
}