1+ using System ;
2+ using System . Numerics ;
3+
4+ using SixLabors . ImageSharp . Advanced ;
5+ using SixLabors . ImageSharp . Memory ;
6+ using SixLabors . ImageSharp . PixelFormats ;
7+ using SixLabors . ImageSharp . PixelFormats . PixelBlenders ;
8+ using SixLabors . Primitives ;
9+
10+ namespace SixLabors . ImageSharp . Processing . Drawing . Brushes
11+ {
12+ /// <summary>
13+ /// Provides an implementation of a brush for painting gradients within areas.
14+ /// Supported right now:
15+ /// - a set of colors in relative distances to each other.
16+ /// - two points to gradient along.
17+ /// </summary>
18+ /// <typeparam name="TPixel">The pixel format</typeparam>
19+ public class LinearGradientBrush < TPixel > : IBrush < TPixel >
20+ where TPixel : struct , IPixel < TPixel >
21+ {
22+ private readonly Point p1 ;
23+
24+ private readonly Point p2 ;
25+
26+ private readonly Tuple < float , TPixel > [ ] keyColors ;
27+
28+ /// <summary>
29+ /// Initializes a new instance of the <see cref="LinearGradientBrush{TPixel}"/> class.
30+ /// </summary>
31+ /// <param name="p1">Start point</param>
32+ /// <param name="p2">End point</param>
33+ /// <param name="keyColors">a set of color keys and where they are. The double must be in range [0..1] and is relative between p1 and p2.</param>
34+ public LinearGradientBrush ( Point p1 , Point p2 , params Tuple < float , TPixel > [ ] keyColors )
35+ {
36+ this . p1 = p1 ;
37+ this . p2 = p2 ;
38+ this . keyColors = keyColors ;
39+ }
40+
41+ /// <inheritdoc />
42+ public BrushApplicator < TPixel > CreateApplicator ( ImageFrame < TPixel > source , RectangleF region , GraphicsOptions options )
43+ => new LinearGradientBrushApplicator ( source , this . p1 , this . p2 , this . keyColors , region , options ) ;
44+
45+ /// <summary>
46+ /// The linear gradient brush applicator.
47+ /// </summary>
48+ private class LinearGradientBrushApplicator : BrushApplicator < TPixel >
49+ {
50+ private readonly Point start ;
51+
52+ private readonly Point end ;
53+
54+ private readonly Tuple < float , TPixel > [ ] colorStops ;
55+
56+ /// <summary>
57+ /// the vector along the gradient, x component
58+ /// </summary>
59+ private readonly float alongX ;
60+
61+ /// <summary>
62+ /// the vector along the gradient, y component
63+ /// </summary>
64+ private readonly float alongY ;
65+
66+ /// <summary>
67+ /// the vector perpendicular to the gradient, y component
68+ /// </summary>
69+ private readonly float acrossY ;
70+
71+ /// <summary>
72+ /// the vector perpendicular to the gradient, x component
73+ /// </summary>
74+ private readonly float acrossX ;
75+
76+ /// <summary>
77+ /// helper to speed up calculation as these dont't change
78+ /// </summary>
79+ private readonly float aYcX ;
80+
81+ /// <summary>
82+ /// helper to speed up calculation as these dont't change
83+ /// </summary>
84+ private readonly float aXcY ;
85+
86+ /// <summary>
87+ /// helper to speed up calculation as these dont't change
88+ /// </summary>
89+ private readonly float aXcX ;
90+
91+ /// <summary>
92+ /// Initializes a new instance of the <see cref="LinearGradientBrushApplicator" /> class.
93+ /// </summary>
94+ /// <param name="source"></param>
95+ /// <param name="start">start point of the gradient</param>
96+ /// <param name="end">end point of the gradient</param>
97+ /// <param name="colorStops">tuple list of colors and their respective position between 0 and 1 on the line</param>
98+ /// <param name="region"></param>
99+ /// <param name="options">the graphics options</param>
100+ public LinearGradientBrushApplicator (
101+ ImageFrame < TPixel > source ,
102+ Point start ,
103+ Point end ,
104+ Tuple < float , TPixel > [ ] colorStops ,
105+ RectangleF region , // TODO: use region, compare with other Brushes for reference.
106+ GraphicsOptions options )
107+ : base ( source , options )
108+ {
109+ this . start = start ;
110+ this . end = end ;
111+ this . colorStops = colorStops ; // TODO: requires colorStops to be sorted by Item1!
112+
113+ // the along vector:
114+ this . alongX = this . start . X - this . end . X ;
115+ this . alongY = this . start . Y - this . end . Y ;
116+
117+ // the cross vector:
118+ this . acrossX = this . alongY ;
119+ this . acrossY = - this . alongX ;
120+
121+ // some helpers:
122+ this . aYcX = this . alongY * this . acrossX ;
123+ this . aXcY = this . alongX * this . acrossY ;
124+ this . aXcX = this . alongX * this . acrossX ;
125+ }
126+
127+ /// <summary>
128+ /// Gets the color for a single pixel
129+ /// </summary>
130+ /// <param name="x">The x.</param>
131+ /// <param name="y">The y.</param>
132+ internal override TPixel this [ int x , int y ]
133+ {
134+ get
135+ {
136+ // the following formula is the result of the linear equation system that forms the vector.
137+ // TODO: this formula should be abstracted as it's the only difference between linear and radial gradient!
138+ float onCompleteGradient = this . RatioOnGradient ( x , y ) ;
139+
140+ var localGradientFrom = this . colorStops [ 0 ] ;
141+ Tuple < float , TPixel > localGradientTo = null ;
142+
143+ // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
144+ foreach ( var colorStop in this . colorStops )
145+ {
146+ localGradientTo = colorStop ;
147+ if ( colorStop . Item1 >= onCompleteGradient )
148+ {
149+ // we're done here, so break it!
150+ break ;
151+ }
152+
153+ localGradientFrom = localGradientTo ;
154+ }
155+
156+ TPixel resultColor = default ;
157+ if ( localGradientFrom . Item2 . Equals ( localGradientTo . Item2 ) )
158+ {
159+ resultColor = localGradientFrom . Item2 ;
160+ }
161+ else
162+ {
163+ var fromAsVector = localGradientFrom . Item2 . ToVector4 ( ) ;
164+ var toAsVector = localGradientTo . Item2 . ToVector4 ( ) ;
165+ float onLocalGradient = ( onCompleteGradient - localGradientFrom . Item1 ) / localGradientTo . Item1 ; // TODO:
166+
167+ Vector4 result = PorterDuffFunctions . Normal (
168+ fromAsVector ,
169+ toAsVector ,
170+ onLocalGradient ) ;
171+
172+ // TODO: when resultColor is a struct, what does PackFromVector4 do here?
173+ resultColor . PackFromVector4 ( result ) ;
174+ }
175+
176+ return resultColor ;
177+ }
178+ }
179+
180+ private float RatioOnGradient ( int x , int y )
181+ {
182+ return ( ( x / this . acrossX ) - ( this . alongX * y / this . aYcX ) )
183+ / ( 1 - ( this . aXcY / this . aXcX ) ) ;
184+ }
185+
186+ internal override void Apply ( Span < float > scanline , int x , int y )
187+ {
188+ base . Apply ( scanline , x , y ) ;
189+
190+ // Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
191+ // MemoryManager memoryManager = this.Target.MemoryManager;
192+ // using (IBuffer<float> amountBuffer = memoryManager.Allocate<float>(scanline.Length))
193+ // {
194+ // Span<float> amountSpan = amountBuffer.Span;
195+ //
196+ // for (int i = 0; i < scanline.Length; i++)
197+ // {
198+ // amountSpan[i] = scanline[i] * this.Options.BlendPercentage;
199+ // }
200+ //
201+ // this.Blender.Blend(memoryManager, destinationRow, destinationRow, this.Colors.Span, amountSpan);
202+ // }
203+ }
204+
205+ /// <inheritdoc />
206+ public override void Dispose ( )
207+ {
208+ }
209+ }
210+ }
211+ }
0 commit comments