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