// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Buffers; using System.Numerics; using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.PixelFormats; using SixLabors.Memory; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// /// Provides an implementation of a brush that can recolor an image /// /// The pixel format. public class RecolorBrush : IBrush where TPixel : struct, IPixel { /// /// 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(TPixel sourceColor, TPixel targetColor, float threshold) { this.SourceColor = sourceColor; this.Threshold = threshold; this.TargetColor = targetColor; } /// /// Gets the threshold. /// public float Threshold { get; } /// /// Gets the source color. /// /// /// The color of the source. /// public TPixel SourceColor { get; } /// /// Gets the target color. /// public TPixel TargetColor { get; } /// public BrushApplicator CreateApplicator(ImageFrame source, RectangleF region, GraphicsOptions options) { return new RecolorBrushApplicator(source, this.SourceColor, this.TargetColor, this.Threshold, options); } /// /// The recolor brush applicator. /// private class RecolorBrushApplicator : BrushApplicator { /// /// The source color. /// private readonly Vector4 sourceColor; /// /// The target color. /// private readonly Vector4 targetColor; /// /// The threshold. /// private readonly float threshold; private readonly TPixel targetColorPixel; /// /// Initializes a new instance of the class. /// /// The source image. /// Color of the source. /// Color of the target. /// The threshold . /// The options public RecolorBrushApplicator(ImageFrame source, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) : base(source, options) { this.sourceColor = sourceColor.ToVector4(); this.targetColor = targetColor.ToVector4(); this.targetColorPixel = targetColor; // Lets hack a min max extremes for a color space by letting the IPackedPixel clamp our values to something in the correct spaces :) var maxColor = default(TPixel); maxColor.FromVector4(new Vector4(float.MaxValue)); var minColor = default(TPixel); minColor.FromVector4(new Vector4(float.MinValue)); this.threshold = Vector4.DistanceSquared(maxColor.ToVector4(), minColor.ToVector4()) * threshold; } /// /// Gets the color for a single pixel. /// /// The x. /// The y. /// /// The color /// internal override TPixel this[int x, int y] { get { // Offset the requested pixel by the value in the rectangle (the shapes position) TPixel result = this.Target[x, y]; var background = result.ToVector4(); float distance = Vector4.DistanceSquared(background, this.sourceColor); if (distance <= this.threshold) { float lerpAmount = (this.threshold - distance) / this.threshold; return this.Blender.Blend( result, this.targetColorPixel, lerpAmount); } return result; } } /// public override void Dispose() { } /// internal override void Apply(Span scanline, int x, int y) { MemoryAllocator memoryAllocator = this.Target.MemoryAllocator; using (IMemoryOwner amountBuffer = memoryAllocator.Allocate(scanline.Length)) using (IMemoryOwner overlay = memoryAllocator.Allocate(scanline.Length)) { Span amountSpan = amountBuffer.GetSpan(); Span overlaySpan = overlay.GetSpan(); for (int i = 0; i < scanline.Length; i++) { amountSpan[i] = scanline[i] * this.Options.BlendPercentage; int offsetX = x + i; // No doubt this one can be optimized further but I can't imagine its // actually being used and can probably be removed/internalized for now overlaySpan[i] = this[offsetX, y]; } Span destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); this.Blender.Blend(memoryAllocator, destinationRow, destinationRow, overlaySpan, amountSpan); } } } } }