// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
namespace SixLabors.ImageSharp.Drawing.Processing {
///
/// Provides an implementation of a brush that can recolor an image
///
public sealed class RecolorBrush : Brush
{
///
/// Initializes a new instance of the class.
///
/// Color of the source.
/// Color of the target.
/// The threshold as a value between 0 and 1.
public RecolorBrush(Color sourceColor, Color targetColor, float threshold)
{
this.SourceColor = sourceColor;
this.Threshold = threshold;
this.TargetColor = targetColor;
}
///
/// Gets the threshold.
///
public float Threshold { get; }
///
/// Gets the source color.
///
public Color SourceColor { get; }
///
/// Gets the target color.
///
public Color TargetColor { get; }
///
public override BrushRenderer CreateRenderer(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
RectangleF region)
=> new RecolorBrushRenderer(
configuration,
options,
canvasWidth,
this.SourceColor.ToPixel(),
this.TargetColor.ToPixel(),
this.Threshold);
///
public override bool Equals(Brush? other)
{
if (other is RecolorBrush brush)
{
return this.SourceColor.Equals(brush.SourceColor)
&& this.TargetColor.Equals(brush.TargetColor)
&& this.Threshold == brush.Threshold;
}
return false;
}
///
public override int GetHashCode()
=> HashCode.Combine(this.Threshold, this.SourceColor, this.TargetColor);
///
/// The recolor brush applicator.
///
/// The pixel format.
private sealed class RecolorBrushRenderer : BrushRenderer
where TPixel : unmanaged, IPixel
{
private readonly Vector4 sourceColor;
private readonly float threshold;
private readonly TPixel targetColorPixel;
///
/// Initializes a new instance of the class.
///
/// The configuration instance to use when performing operations.
/// The options
/// The canvas width for the current render pass.
/// Color of the source.
/// Color of the target.
/// The threshold .
public RecolorBrushRenderer(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
TPixel sourceColor,
TPixel targetColor,
float threshold)
: base(configuration, options, canvasWidth)
{
this.sourceColor = sourceColor.ToScaledVector4();
this.targetColorPixel = targetColor;
// TODO: Review this. We can skip the conversion from/to Vector4.
// Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :)
TPixel maxColor = TPixel.FromVector4(new Vector4(float.MaxValue));
TPixel minColor = TPixel.FromVector4(new Vector4(float.MinValue));
this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold;
}
///
public override void Apply(
Span destinationRow,
ReadOnlySpan scanline,
int x,
int y,
BrushWorkspace workspace)
{
Span amounts = workspace.GetAmounts(scanline.Length);
Span overlays = workspace.GetOverlays(scanline.Length);
for (int i = 0; i < scanline.Length; i++)
{
amounts[i] = scanline[i] * this.Options.BlendPercentage;
TPixel result = destinationRow[i];
Vector4 background = result.ToVector4();
float distance = Vector4.DistanceSquared(background, this.sourceColor);
overlays[i] = distance <= this.threshold
? this.Blender.Blend(result, this.targetColorPixel, (this.threshold - distance) / this.threshold)
: result;
}
this.Blender.Blend(
this.Configuration,
destinationRow,
destinationRow,
overlays,
amounts,
workspace.GetBlendScratch(scanline.Length, 3));
}
}
}
}