-
-
Notifications
You must be signed in to change notification settings - Fork 45
Expand file tree
/
Copy pathFillPathProcessor{TPixel}.cs
More file actions
157 lines (135 loc) · 5.61 KB
/
FillPathProcessor{TPixel}.cs
File metadata and controls
157 lines (135 loc) · 5.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using SixLabors.ImageSharp.Drawing.Shapes.Rasterization;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.Processing.Processors;
namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Drawing;
/// <summary>
/// Uses a brush and a shape to fill the shape with contents of the brush.
/// </summary>
/// <typeparam name="TPixel">The type of the color.</typeparam>
/// <seealso cref="ImageProcessor{TPixel}" />
internal class FillPathProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : unmanaged, IPixel<TPixel>
{
private readonly FillPathProcessor definition;
private readonly IPath path;
private readonly Rectangle bounds;
public FillPathProcessor(
Configuration configuration,
FillPathProcessor definition,
Image<TPixel> source,
Rectangle sourceRectangle)
: base(configuration, source, sourceRectangle)
{
IPath path = definition.Region;
int left = (int)MathF.Floor(path.Bounds.Left);
int top = (int)MathF.Floor(path.Bounds.Top);
int right = (int)MathF.Ceiling(path.Bounds.Right);
int bottom = (int)MathF.Ceiling(path.Bounds.Bottom);
this.bounds = Rectangle.FromLTRB(left, top, right, bottom);
this.path = path.AsClosedPath();
this.definition = definition;
}
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source)
{
Configuration configuration = this.Configuration;
ShapeOptions shapeOptions = this.definition.Options.ShapeOptions;
GraphicsOptions graphicsOptions = this.definition.Options.GraphicsOptions;
Brush brush = this.definition.Brush;
TPixel solidBrushColor = default;
bool isSolidBrushWithoutBlending = false;
if (IsSolidBrushWithoutBlending(graphicsOptions, brush, out SolidBrush? solidBrush))
{
isSolidBrushWithoutBlending = true;
solidBrushColor = solidBrush.Color.ToPixel<TPixel>();
}
// Align start/end positions.
Rectangle interest = Rectangle.Intersect(this.bounds, source.Bounds());
if (interest.Equals(Rectangle.Empty))
{
return; // No effect inside image;
}
int minX = interest.Left;
int subpixelCount = FillPathProcessor.MinimumSubpixelCount;
// We need to offset the pixel grid to account for when we outline a path.
// basically if the line is [1,2] => [3,2] then when outlining at 1 we end up with a region of [0.5,1.5],[1.5, 1.5],[3.5,2.5],[2.5,2.5]
// and this can cause missed fills when not using antialiasing.so we offset the pixel grid by 0.5 in the x & y direction thus causing the#
// region to align with the pixel grid.
if (graphicsOptions.Antialias)
{
subpixelCount = Math.Max(subpixelCount, graphicsOptions.AntialiasSubpixelDepth);
}
using BrushApplicator<TPixel> applicator = brush.CreateApplicator(configuration, graphicsOptions, source, this.bounds);
int scanlineWidth = interest.Width;
MemoryAllocator allocator = this.Configuration.MemoryAllocator;
bool scanlineDirty = true;
PolygonScanner scanner = PolygonScanner.Create(
this.path,
interest.Top,
interest.Bottom,
subpixelCount,
shapeOptions.IntersectionRule,
configuration.MemoryAllocator);
try
{
using IMemoryOwner<float> bScanline = allocator.Allocate<float>(scanlineWidth);
Span<float> scanline = bScanline.Memory.Span;
while (scanner.MoveToNextPixelLine())
{
if (scanlineDirty)
{
scanline.Clear();
}
scanlineDirty = scanner.ScanCurrentPixelLineInto(minX, 0F, scanline);
if (scanlineDirty)
{
int y = scanner.PixelLineY;
if (!graphicsOptions.Antialias)
{
bool hasOnes = false;
bool hasZeros = false;
for (int x = 0; x < scanline.Length; x++)
{
if (scanline[x] >= 0.5F)
{
scanline[x] = 1F;
hasOnes = true;
}
else
{
scanline[x] = 0F;
hasZeros = true;
}
}
if (isSolidBrushWithoutBlending && hasOnes != hasZeros)
{
if (hasOnes)
{
source.PixelBuffer.DangerousGetRowSpan(y).Slice(minX, scanlineWidth).Fill(solidBrushColor);
}
continue;
}
}
applicator.Apply(scanline, minX, y);
}
}
}
finally
{
scanner.Dispose();
}
}
private static bool IsSolidBrushWithoutBlending(GraphicsOptions options, Brush inputBrush, [NotNullWhen(true)] out SolidBrush? solidBrush)
{
solidBrush = inputBrush as SolidBrush;
if (solidBrush == null)
{
return false;
}
return options.IsOpaqueColorWithoutBlending(solidBrush.Color);
}
}