// Copyright (c) Six Labors. // Licensed under the Six Labors Split License. using SixLabors.ImageSharp.PixelFormats; using System; using System.Numerics; namespace SixLabors.ImageSharp.Drawing.Processing { /// /// Provides a brush that paints linear gradients within an area. /// Supports both classic two-point gradients and three-point (rotated) gradients. /// public sealed class LinearGradientBrush : GradientBrush { /// /// Initializes a new instance of the class using /// a start and end point. /// /// The start point of the gradient. /// The end point of the gradient. /// Defines how the colors are repeated. /// The ordered color stops of the gradient. public LinearGradientBrush( PointF p0, PointF p1, GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) : base(repetitionMode, colorStops) { this.StartPoint = p0; this.EndPoint = p1; } /// /// Initializes a new instance of the class using /// three points to define a rotated gradient axis. /// /// The first point (start of the gradient). /// The second point (gradient vector endpoint). /// /// The rotation reference point. This defines the rotation of the gradient axis. /// /// Defines how the colors are repeated. /// The ordered color stops of the gradient. public LinearGradientBrush( PointF p0, PointF p1, PointF rotationPoint, GradientRepetitionMode repetitionMode, params ColorStop[] colorStops) : base(repetitionMode, colorStops) { ResolveAxis(p0, p1, rotationPoint, out PointF start, out PointF end); this.StartPoint = start; this.EndPoint = end; } /// /// Gets the start point of the gradient axis. /// public PointF StartPoint { get; } /// /// Gets the end point of the gradient axis. /// public PointF EndPoint { get; } /// public override Brush Transform(Matrix4x4 matrix) => new LinearGradientBrush( PointF.Transform(this.StartPoint, matrix), PointF.Transform(this.EndPoint, matrix), this.RepetitionMode, this.ColorStopsArray); /// public override bool Equals(Brush? other) { if (other is LinearGradientBrush brush) { return base.Equals(other) && this.StartPoint.Equals(brush.StartPoint) && this.EndPoint.Equals(brush.EndPoint); } return false; } /// public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), this.StartPoint, this.EndPoint); /// /// Resolves a three-point gradient axis into a two-point axis by projecting /// the gradient vector (p0 to p1) onto the perpendicular of the rotation vector (p0 to rotationPoint). /// This follows the COLRv1 font specification for rotated linear gradients. /// /// The gradient start point. /// The gradient vector endpoint. /// The rotation reference point. /// The resolved start point of the gradient axis. /// The resolved end point of the gradient axis. private static void ResolveAxis(PointF p0, PointF p1, PointF rotationPoint, out PointF start, out PointF end) { // Gradient vector from p0 to p1. float vx = p1.X - p0.X; float vy = p1.Y - p0.Y; // Rotation vector from p0 to rotation point. float rx = rotationPoint.X - p0.X; float ry = rotationPoint.Y - p0.Y; // Perpendicular to the rotation vector. float nx = ry; float ny = -rx; float ndotn = (nx * nx) + (ny * ny); if (ndotn == 0f) { // Degenerate: p0 == rotationPoint, fall back to original axis. start = p0; end = p1; } else { // Project the gradient vector onto the perpendicular direction. float vdotn = (vx * nx) + (vy * ny); float scale = vdotn / ndotn; start = p0; end = new PointF(p0.X + (scale * nx), p0.Y + (scale * ny)); } } /// public override BrushRenderer CreateRenderer( Configuration configuration, GraphicsOptions options, int canvasWidth, RectangleF region) => new LinearGradientBrushRenderer( configuration, options, canvasWidth, this, this.ColorStopsArray, this.RepetitionMode); /// /// Implements the gradient application logic for . /// /// The pixel format. private sealed class LinearGradientBrushRenderer : GradientBrushRenderer where TPixel : unmanaged, IPixel { private readonly PointF start; private readonly float alongX; private readonly float alongY; private readonly float alongsSquared; /// /// Initializes a new instance of the class. /// /// The ImageSharp configuration. /// The graphics options. /// The canvas width for the current render pass. /// The linear gradient brush. /// The gradient color stops. /// Defines how the gradient repeats. public LinearGradientBrushRenderer( Configuration configuration, GraphicsOptions options, int canvasWidth, LinearGradientBrush brush, ColorStop[] colorStops, GradientRepetitionMode repetitionMode) : base(configuration, options, canvasWidth, colorStops, repetitionMode) { this.start = brush.StartPoint; this.alongX = brush.EndPoint.X - this.start.X; this.alongY = brush.EndPoint.Y - this.start.Y; this.alongsSquared = (this.alongX * this.alongX) + (this.alongY * this.alongY); } /// protected override float PositionOnGradient(float x, float y) { if (this.alongsSquared == 0f) { return 1f; } float deltaX = x - this.start.X; float deltaY = y - this.start.Y; return ((deltaX * this.alongX) + (deltaY * this.alongY)) / this.alongsSquared; } } } }