// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. using System; using System.Numerics; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats.PixelBlenders; using SixLabors.Primitives; namespace SixLabors.ImageSharp.Processing { /// /// Base class for Gradient brushes /// /// The pixel format public abstract class GradientBrushBase : IBrush where TPixel : struct, IPixel { /// /// Defines how the colors are repeated beyond the interval [0..1] /// The gradient colors. protected GradientBrushBase( GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) { this.RepetitionMode = repetitionMode; this.ColorStops = colorStops; } /// /// Gets how the colors are repeated beyond the interval [0..1]. /// protected GradientRepetitionMode RepetitionMode { get; } /// /// Gets the list of color stops for this gradient. /// protected ColorStop[] ColorStops { get; } /// public abstract BrushApplicator CreateApplicator( ImageFrame source, RectangleF region, GraphicsOptions options); /// /// Base class for gradient brush applicators /// protected abstract class GradientBrushApplicatorBase : BrushApplicator { private readonly ColorStop[] colorStops; private readonly GradientRepetitionMode repetitionMode; /// /// Initializes a new instance of the class. /// /// The target. /// The options. /// An array of color stops sorted by their position. /// Defines if and how the gradient should be repeated. protected GradientBrushApplicatorBase( ImageFrame target, GraphicsOptions options, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(target, options) { this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked? this.repetitionMode = repetitionMode; } /// /// Base implementation of the indexer for gradients /// (follows the facade pattern, using abstract methods) /// /// X coordinate of the Pixel. /// Y coordinate of the Pixel. internal override TPixel this[int x, int y] { get { float positionOnCompleteGradient = this.PositionOnGradient(x, y); switch (this.repetitionMode) { case GradientRepetitionMode.None: // do nothing. The following could be done, but is not necessary: // onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient)); break; case GradientRepetitionMode.Repeat: positionOnCompleteGradient = positionOnCompleteGradient % 1; break; case GradientRepetitionMode.Reflect: positionOnCompleteGradient = positionOnCompleteGradient % 2; if (positionOnCompleteGradient > 1) { positionOnCompleteGradient = 2 - positionOnCompleteGradient; } break; case GradientRepetitionMode.DontFill: if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) { return NamedColors.Transparent; } break; default: throw new ArgumentOutOfRangeException(); } var (from, to) = this.GetGradientSegment(positionOnCompleteGradient); if (from.Color.Equals(to.Color)) { return from.Color; } else { var fromAsVector = from.Color.ToVector4(); var toAsVector = to.Color.ToVector4(); float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / to.Ratio; // TODO: this should be changeble for different gradienting functions Vector4 result = PorterDuffFunctions.Normal( fromAsVector, toAsVector, onLocalGradient); TPixel resultColor = default; resultColor.PackFromVector4(result); return resultColor; } } } /// /// calculates the position on the gradient for a given pixel. /// This method is abstract as it's content depends on the shape of the gradient. /// /// The x coordinate of the pixel /// The y coordinate of the pixel /// /// The position the given pixel has on the gradient. /// The position is not bound to the [0..1] interval. /// Values outside of that interval may be treated differently, /// e.g. for the enum. /// protected abstract float PositionOnGradient(int x, int y); private (ColorStop from, ColorStop to) GetGradientSegment( float positionOnCompleteGradient) { var localGradientFrom = this.colorStops[0]; ColorStop localGradientTo = default; // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient) foreach (var colorStop in this.colorStops) { localGradientTo = colorStop; if (colorStop.Ratio > positionOnCompleteGradient) { // we're done here, so break it! break; } localGradientFrom = localGradientTo; } return (localGradientFrom, localGradientTo); } } } }