VectSharp  2.2.1
A light library for C# vector graphics
GradientBrushApplicator.cs
1 /*
2  VectSharp - A light library for C# vector graphics.
3  Copyright (C) 2020-2022 Giorgio Bianchini
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU Lesser General Public License as published by
7  the Free Software Foundation, version 3.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU Lesser General Public License for more details.
13 
14  You should have received a copy of the GNU Lesser General Public License
15  along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17 
18 using System;
19 using System.Buffers;
20 using System.Numerics;
21 using SixLabors.ImageSharp.Memory;
22 using SixLabors.ImageSharp.PixelFormats;
23 using SixLabors.ImageSharp.Drawing.Processing;
24 using SixLabors.ImageSharp;
25 using System.Reflection;
26 
28 {
29  // Adapted from https://github.com/SixLabors/ImageSharp.Drawing/blob/master/src/ImageSharp.Drawing/Processing/GradientBrush.cs - Why isn't this public? D:
30  internal abstract class GradientBrushApplicator<TPixel> : BrushApplicator<TPixel>
31  where TPixel : unmanaged, IPixel<TPixel>
32  {
33  private static readonly TPixel Transparent = Color.Transparent.ToPixel<TPixel>();
34 
35  private readonly ColorStop[] colorStops;
36 
37  private readonly GradientRepetitionMode repetitionMode;
38 
39  private readonly MemoryAllocator allocator;
40 
41  private readonly int scalineWidth;
42 
43  private readonly object blenderBuffers;
44 
45  private bool isDisposed;
46 
47  private PixelBlender<TPixel> _blender;
48 
49  IMemoryOwner<float> amountBuffer;
50  IMemoryOwner<TPixel> overlayBuffer;
51  static Type constructedLocalBlenderBuffersType;
52 
53  static GradientBrushApplicator()
54  {
55  Assembly utilities = Assembly.Load("SixLabors.ImageSharp.Drawing");
56  Type genericThreadLocalBlenderBuffersType = utilities.GetType("SixLabors.ImageSharp.Drawing.Utilities.ThreadLocalBlenderBuffers`1", true);
57 
58  Type[] typeArgs = new Type[] { typeof(TPixel) };
59  constructedLocalBlenderBuffersType = genericThreadLocalBlenderBuffersType.MakeGenericType(typeArgs);
60  }
61 
62 
63  /// <summary>
64  /// Initializes a new instance of the <see cref="GradientBrushApplicator{TPixel}"/> class.
65  /// </summary>
66  /// <param name="configuration">The configuration instance to use when performing operations.</param>
67  /// <param name="options">The graphics options.</param>
68  /// <param name="target">The target image.</param>
69  /// <param name="colorStops">An array of color stops sorted by their position.</param>
70  /// <param name="repetitionMode">Defines if and how the gradient should be repeated.</param>
71  protected GradientBrushApplicator(
72  Configuration configuration,
73  GraphicsOptions options,
74  ImageFrame<TPixel> target,
75  ColorStop[] colorStops,
76  GradientRepetitionMode repetitionMode)
77  : base(configuration, options, target)
78  {
79  // TODO: requires colorStops to be sorted by position.
80  // Use Array.Sort with a custom comparer.
81  this.colorStops = colorStops;
82  this.repetitionMode = repetitionMode;
83  this.scalineWidth = target.Width;
84  this.allocator = configuration.MemoryAllocator;
85 
86  this.blenderBuffers = Activator.CreateInstance(constructedLocalBlenderBuffersType, new object[] { this.allocator, this.scalineWidth, false });
87 
88  this._blender = (PixelBlender<TPixel>)this.GetType().GetProperty("Blender", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetProperty).GetValue(this);
89 
90  object data = this.blenderBuffers.GetType().GetField("data", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField).GetValue(this.blenderBuffers);
91  object bufferOwner = data.GetType().GetProperty("Value", BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty).GetValue(data);
92  amountBuffer = (IMemoryOwner<float>)bufferOwner.GetType().GetField("amountBuffer", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField).GetValue(bufferOwner);
93  overlayBuffer = (IMemoryOwner<TPixel>)bufferOwner.GetType().GetField("overlayBuffer", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.GetField).GetValue(bufferOwner);
94  }
95 
96  internal TPixel this[int x, int y]
97  {
98  get
99  {
100  float positionOnCompleteGradient = this.PositionOnGradient(x + 0.5f, y + 0.5f);
101 
102  switch (this.repetitionMode)
103  {
104  case GradientRepetitionMode.None:
105  // do nothing. The following could be done, but is not necessary:
106  // onLocalGradient = Math.Min(0, Math.Max(1, onLocalGradient));
107  break;
108  case GradientRepetitionMode.Repeat:
109  positionOnCompleteGradient %= 1;
110  break;
111  case GradientRepetitionMode.Reflect:
112  positionOnCompleteGradient %= 2;
113  if (positionOnCompleteGradient > 1)
114  {
115  positionOnCompleteGradient = 2 - positionOnCompleteGradient;
116  }
117 
118  break;
119  case GradientRepetitionMode.DontFill:
120  if (positionOnCompleteGradient > 1 || positionOnCompleteGradient < 0)
121  {
122  return Transparent;
123  }
124 
125  break;
126  default:
127  throw new ArgumentOutOfRangeException();
128  }
129 
130  (ColorStop from, ColorStop to) = this.GetGradientSegment(positionOnCompleteGradient);
131 
132  if (from.Color.Equals(to.Color))
133  {
134  return from.Color.ToPixel<TPixel>();
135  }
136 
137  float onLocalGradient = (positionOnCompleteGradient - from.Ratio) / (to.Ratio - from.Ratio);
138 
139  return new Color(Vector4.Lerp((Vector4)from.Color, (Vector4)to.Color, onLocalGradient)).ToPixel<TPixel>();
140  }
141  }
142 
143  /// <inheritdoc />
144  public override void Apply(Span<float> scanline, int x, int y)
145  {
146  Span<float> amounts = amountBuffer.Memory.Span.Slice(0, scanline.Length);
147  Span<TPixel> overlays = overlayBuffer.Memory.Span.Slice(0, scanline.Length);
148 
149  float blendPercentage = this.Options.BlendPercentage;
150 
151  // TODO: Remove bounds checks.
152  if (blendPercentage < 1)
153  {
154  for (int i = 0; i < scanline.Length; i++)
155  {
156  amounts[i] = scanline[i] * blendPercentage;
157  overlays[i] = this[x + i, y];
158  }
159  }
160  else
161  {
162  for (int i = 0; i < scanline.Length; i++)
163  {
164  amounts[i] = scanline[i];
165  overlays[i] = this[x + i, y];
166  }
167  }
168 
169  Span<TPixel> destinationRow = this.Target.PixelBuffer.DangerousGetRowSpan(y).Slice(x, scanline.Length);
170 
171  this._blender.Blend(this.Configuration, destinationRow, destinationRow, overlays, amounts);
172  }
173 
174  /// <summary>
175  /// Calculates the position on the gradient for a given point.
176  /// This method is abstract as it's content depends on the shape of the gradient.
177  /// </summary>
178  /// <param name="x">The x-coordinate of the point.</param>
179  /// <param name="y">The y-coordinate of the point.</param>
180  /// <returns>
181  /// The position the given point has on the gradient.
182  /// The position is not bound to the [0..1] interval.
183  /// Values outside of that interval may be treated differently,
184  /// e.g. for the <see cref="GradientRepetitionMode" /> enum.
185  /// </returns>
186  protected abstract float PositionOnGradient(float x, float y);
187 
188  /// <inheritdoc/>
189  protected override void Dispose(bool disposing)
190  {
191  if (this.isDisposed)
192  {
193  return;
194  }
195 
196  base.Dispose(disposing);
197 
198  if (disposing)
199  {
200  ((IDisposable)this.blenderBuffers).Dispose();
201  }
202 
203  this.isDisposed = true;
204  }
205 
206  private (ColorStop From, ColorStop To) GetGradientSegment(float positionOnCompleteGradient)
207  {
208  ColorStop localGradientFrom = this.colorStops[0];
209  ColorStop localGradientTo = default;
210 
211  // TODO: ensure colorStops has at least 2 items (technically 1 would be okay, but that's no gradient)
212  foreach (ColorStop colorStop in this.colorStops)
213  {
214  localGradientTo = colorStop;
215 
216  if (colorStop.Ratio > positionOnCompleteGradient)
217  {
218  // we're done here, so break it!
219  break;
220  }
221 
222  localGradientFrom = localGradientTo;
223  }
224 
225  return (localGradientFrom, localGradientTo);
226  }
227  }
228 }
VectSharp.Raster.ImageSharp
Definition: GradientBrushApplicator.cs:28