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