ImageSharp/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs
2026-08-03 22:31:27 +02:00

160 lines
6.6 KiB
C#

// 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 {
/// <summary>
/// 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.
/// </summary>
public sealed class EllipticGradientBrush : GradientBrush
{
/// <inheritdoc cref="GradientBrush" />
/// <param name="center">The center of the elliptical gradient and 0 for the color stops.</param>
/// <param name="referenceAxisEnd">The end point of the reference axis of the ellipse.</param>
/// <param name="axisRatio">
/// 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.
/// </param>
/// <param name="repetitionMode">Defines how the colors of the gradients are repeated.</param>
/// <param name="colorStops">The color stops.</param>
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;
}
/// <summary>
/// Gets the center of the ellipse.
/// </summary>
public PointF Center { get; }
/// <summary>
/// Gets the end point of the reference axis.
/// </summary>
public PointF ReferenceAxisEnd { get; }
/// <summary>
/// Gets the ratio of the secondary axis to the primary axis.
/// </summary>
public float AxisRatio { get; }
/// <inheritdoc/>
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);
}
/// <inheritdoc />
public override BrushRenderer<TPixel> CreateRenderer<TPixel>(
Configuration configuration,
GraphicsOptions options,
int canvasWidth,
RectangleF region) =>
new EllipticGradientBrushRenderer<TPixel>(
configuration,
options,
canvasWidth,
this,
this.ColorStopsArray,
this.RepetitionMode);
/// <inheritdoc />
private sealed class EllipticGradientBrushRenderer<TPixel> : GradientBrushRenderer<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly PointF center;
private readonly float cosRotation;
private readonly float sinRotation;
private readonly float referenceRadiusSquared;
private readonly float secondRadiusSquared;
/// <summary>
/// Initializes a new instance of the <see cref="EllipticGradientBrushRenderer{TPixel}" /> class.
/// </summary>
/// <param name="configuration">The configuration instance to use when performing operations.</param>
/// <param name="options">The graphics options.</param>
/// <param name="canvasWidth">The canvas width for the current render pass.</param>
/// <param name="brush">The elliptic gradient brush.</param>
/// <param name="colorStops">Definition of colors.</param>
/// <param name="repetitionMode">Defines how the gradient colors are repeated.</param>
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);
}
/// <inheritdoc />
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));
}
}
}
}