// 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 for painting elliptical gradients. /// The ellipse is defined by a center point, a point on the longest axis, and the ratio /// between the longest and shortest axes. /// public sealed class EllipticGradientBrush : GradientBrush { /// /// The center of the elliptical gradient and 0 for the color stops. /// The end point of the reference axis of the ellipse. /// /// The ratio of the axis widths. /// The second axis is perpendicular to the reference axis and its length is the reference axis length /// multiplied by this factor. /// /// Defines how the colors of the gradients are repeated. /// The color stops. public EllipticGradientBrush( PointF center, PointF referenceAxisEnd, float axisRatio, GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) : base(repetitionMode, colorStops) { this.Center = center; this.ReferenceAxisEnd = referenceAxisEnd; this.AxisRatio = axisRatio; } /// /// Gets the center of the ellipse. /// public PointF Center { get; } /// /// Gets the end point of the reference axis. /// public PointF ReferenceAxisEnd { get; } /// /// Gets the ratio of the secondary axis to the primary axis. /// public float AxisRatio { get; } /// public override Brush Transform(Matrix4x4 matrix) { PointF tc = PointF.Transform(this.Center, matrix); PointF tRef = PointF.Transform(this.ReferenceAxisEnd, matrix); // Compute a point on the perpendicular (secondary) axis and transform it. float refDx = this.ReferenceAxisEnd.X - this.Center.X; float refDy = this.ReferenceAxisEnd.Y - this.Center.Y; float refLen = MathF.Sqrt((refDx * refDx) + (refDy * refDy)); float secondLen = refLen * this.AxisRatio; // Perpendicular direction (rotated 90 degrees). PointF secondEnd = new( this.Center.X + (-refDy / refLen * secondLen), this.Center.Y + (refDx / refLen * secondLen)); PointF tSec = PointF.Transform(secondEnd, matrix); // Derive new ratio from transformed lengths. float newRefLen = MathF.Sqrt( ((tRef.X - tc.X) * (tRef.X - tc.X)) + ((tRef.Y - tc.Y) * (tRef.Y - tc.Y))); float newSecLen = MathF.Sqrt( ((tSec.X - tc.X) * (tSec.X - tc.X)) + ((tSec.Y - tc.Y) * (tSec.Y - tc.Y))); float newRatio = newRefLen > 0f ? newSecLen / newRefLen : this.AxisRatio; return new EllipticGradientBrush(tc, tRef, newRatio, this.RepetitionMode, this.ColorStopsArray); } /// public override BrushRenderer CreateRenderer( Configuration configuration, GraphicsOptions options, int canvasWidth, RectangleF region) => new EllipticGradientBrushRenderer( configuration, options, canvasWidth, this, this.ColorStopsArray, this.RepetitionMode); /// private sealed class EllipticGradientBrushRenderer : GradientBrushRenderer where TPixel : unmanaged, IPixel { private readonly PointF center; private readonly float cosRotation; private readonly float sinRotation; private readonly float referenceRadiusSquared; private readonly float secondRadiusSquared; /// /// Initializes a new instance of the class. /// /// The configuration instance to use when performing operations. /// The graphics options. /// The canvas width for the current render pass. /// The elliptic gradient brush. /// Definition of colors. /// Defines how the gradient colors are repeated. public EllipticGradientBrushRenderer( Configuration configuration, GraphicsOptions options, int canvasWidth, EllipticGradientBrush brush, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(configuration, options, canvasWidth, colorStops, repetitionMode) { this.center = brush.Center; float refDx = brush.ReferenceAxisEnd.X - brush.Center.X; float refDy = brush.ReferenceAxisEnd.Y - brush.Center.Y; float rotation = MathF.Atan2(refDy, refDx); float referenceRadius = MathF.Sqrt((refDx * refDx) + (refDy * refDy)); float secondRadius = referenceRadius * brush.AxisRatio; this.referenceRadiusSquared = referenceRadius * referenceRadius; this.secondRadiusSquared = secondRadius * secondRadius; this.sinRotation = MathF.Sin(rotation); this.cosRotation = MathF.Cos(rotation); } /// protected override float PositionOnGradient(float x, float y) { float x0 = x - this.center.X; float y0 = y - this.center.Y; float xR = (x0 * this.cosRotation) - (y0 * this.sinRotation); float yR = (x0 * this.sinRotation) + (y0 * this.cosRotation); float xSquared = xR * xR; float ySquared = yR * yR; return MathF.Sqrt((xSquared / this.referenceRadiusSquared) + (ySquared / this.secondRadiusSquared)); } } } }