// 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.Primitives; namespace SixLabors.ImageSharp.Processing { /// /// Base class for Gradient brushes /// public abstract class GradientBrush : IBrush { /// /// Defines how the colors are repeated beyond the interval [0..1] /// The gradient colors. protected GradientBrush( 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( Configuration configuration, GraphicsOptions options, ImageFrame source, RectangleF region) where TPixel : struct, IPixel; /// /// Base class for gradient brush applicators /// internal abstract class GradientBrushApplicator : BrushApplicator where TPixel : struct, IPixel { private static readonly TPixel Transparent = Color.Transparent.ToPixel(); private readonly ColorStop[] colorStops; private readonly GradientRepetitionMode repetitionMode; /// /// Initializes a new instance of the class. /// /// The configuration instance to use when performing operations. /// The graphics options. /// The target image. /// An array of color stops sorted by their position. /// Defines if and how the gradient should be repeated. protected GradientBrushApplicator( Configuration configuration, GraphicsOptions options, ImageFrame target, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(configuration, options, target) { this.colorStops = colorStops; // TODO: requires colorStops to be sorted by position - should that be checked? this.repetitionMode = repetitionMode; } /// internal override TPixel this[int x, int y] { get { float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f); 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 %= 1; break; case GradientRepetitionMode.Reflect: positionOnCompleteGradient %= 2; if (positionOnCompleteGradient > 1) { positionOnCompleteGradient = 2 - positionOnCompleteGradient; } break; case GradientRepetitionMode.DontFill: if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0) { return Transparent; } break; default: throw new ArgumentOutOfRangeException(); } (ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient); if (from.Color.Equals(to.Color)) { return from.Color.ToPixel(); } else { float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio); return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel(); } } } /// /// calculates the position on the gradient for a given point. /// This method is abstract as it's content depends on the shape of the gradient. /// /// The x-coordinate of the point. /// The y-coordinate of the point. /// /// The position the given point 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(float x, float y); private (ColorStop from, ColorStop to) GetGradientSegment( float positionOnCompleteGradient) { ColorStop 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 (ColorStop colorStop in this.colorStops) { localGradientTo = colorStop; if (colorStop.Ratio > positionOnCompleteGradient) { // we're done here, so break it! break; } localGradientFrom = localGradientTo; } return (localGradientFrom, localGradientTo); } } } }