// 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));
}
}
}
}