Skip to content

Commit 0774da7

Browse files
Merge pull request #368 from SixLabors/js/lay-glyph-renderer
Support rendering painted layered glyphs.
2 parents dd8f167 + 4623721 commit 0774da7

File tree

57 files changed

+2251
-497
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2251
-497
lines changed

ImageSharp.Drawing.sln

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 18
4-
VisualStudioVersion = 18.0.11123.170 d18.0
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.14.36623.8 d17.14
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
77
ProjectSection(SolutionItems) = preProject
@@ -359,14 +359,6 @@ Global
359359
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|Any CPU.Build.0 = Debug|Any CPU
360360
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.ActiveCfg = Release|Any CPU
361361
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.Build.0 = Release|Any CPU
362-
{FCEDD229-22BC-4B82-87DE-786BBFC52EDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
363-
{FCEDD229-22BC-4B82-87DE-786BBFC52EDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
364-
{FCEDD229-22BC-4B82-87DE-786BBFC52EDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
365-
{FCEDD229-22BC-4B82-87DE-786BBFC52EDE}.Release|Any CPU.Build.0 = Release|Any CPU
366-
{5490DFAF-0891-535F-08B4-2BF03C2BB778}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
367-
{5490DFAF-0891-535F-08B4-2BF03C2BB778}.Debug|Any CPU.Build.0 = Debug|Any CPU
368-
{5490DFAF-0891-535F-08B4-2BF03C2BB778}.Release|Any CPU.ActiveCfg = Release|Any CPU
369-
{5490DFAF-0891-535F-08B4-2BF03C2BB778}.Release|Any CPU.Build.0 = Release|Any CPU
370362
EndGlobalSection
371363
GlobalSection(SolutionProperties) = preSolution
372364
HideSolutionNode = FALSE

samples/DrawShapesWithImageSharp/Program.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using SixLabors.ImageSharp;
77
using SixLabors.ImageSharp.Drawing;
88
using SixLabors.ImageSharp.Drawing.Processing;
9+
using SixLabors.ImageSharp.Drawing.Text;
910
using SixLabors.ImageSharp.PixelFormats;
1011
using SixLabors.ImageSharp.Processing;
1112
using IODirectory = System.IO.Directory;
@@ -61,7 +62,7 @@ private static void DrawText(string text)
6162
FontFamily fam = SystemFonts.Get("Arial");
6263
Font font = new(fam, 30);
6364
TextOptions textOptions = new(font);
64-
IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, textOptions);
65+
IPathCollection glyphs = TextBuilder.GeneratePaths(text, textOptions);
6566

6667
glyphs.SaveImage("Text", text + ".png");
6768
}
@@ -80,7 +81,7 @@ private static void DrawText(string text, IPath path)
8081
// LayoutMode = LayoutMode.VerticalLeftRight
8182
};
8283

83-
IPathCollection glyphs = TextBuilder.GenerateGlyphs(text, path, textOptions);
84+
IPathCollection glyphs = TextBuilder.GeneratePaths(text, path, textOptions);
8485

8586
glyphs.SaveImageWithPath(path, "Text-Path", text + ".png");
8687
}

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@
4444
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp.drawing\sixlabors.imagesharp.drawing.128.png" Pack="true" PackagePath="" />
4545
</ItemGroup>
4646
<ItemGroup>
47-
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.1" />
48-
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.44" />
47+
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.3" />
48+
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.49" />
4949
</ItemGroup>
5050
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
5151
</Project>

src/ImageSharp.Drawing/Processing/EllipticGradientBrush.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,11 +109,11 @@ public RadialGradientBrushApplicator(
109109
this.center = center;
110110
this.referenceAxisEnd = referenceAxisEnd;
111111
this.axisRatio = axisRatio;
112-
this.rotation = RadialGradientBrushApplicator<TPixel>.AngleBetween(
112+
this.rotation = AngleBetween(
113113
this.center,
114114
new PointF(this.center.X + 1, this.center.Y),
115115
this.referenceAxisEnd);
116-
this.referenceRadius = RadialGradientBrushApplicator<TPixel>.DistanceBetween(this.center, this.referenceAxisEnd);
116+
this.referenceRadius = DistanceBetween(this.center, this.referenceAxisEnd);
117117
this.secondRadius = this.referenceRadius * this.axisRatio;
118118

119119
this.referenceRadiusSquared = this.referenceRadius * this.referenceRadius;

src/ImageSharp.Drawing/Processing/Extensions/ClearExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ public static IImageProcessingContext Clear(this IImageProcessingContext source,
5454
internal static DrawingOptions CloneForClearOperation(this DrawingOptions drawingOptions)
5555
{
5656
GraphicsOptions options = drawingOptions.GraphicsOptions.DeepClone();
57-
options.ColorBlendingMode = PixelFormats.PixelColorBlendingMode.Normal;
58-
options.AlphaCompositionMode = PixelFormats.PixelAlphaCompositionMode.Src;
57+
options.ColorBlendingMode = PixelColorBlendingMode.Normal;
58+
options.AlphaCompositionMode = PixelAlphaCompositionMode.Src;
5959
options.BlendPercentage = 1F;
6060

6161
return new DrawingOptions(options, drawingOptions.ShapeOptions, drawingOptions.Transform);

src/ImageSharp.Drawing/Processing/Extensions/DrawPathCollectionExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,8 @@ public static IImageProcessingContext Draw(
3838
/// <param name="paths">The paths.</param>
3939
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
4040
public static IImageProcessingContext
41-
Draw(this IImageProcessingContext source, Pen pen, IPathCollection paths) =>
42-
source.Draw(source.GetDrawingOptions(), pen, paths);
41+
Draw(this IImageProcessingContext source, Pen pen, IPathCollection paths)
42+
=> source.Draw(source.GetDrawingOptions(), pen, paths);
4343

4444
/// <summary>
4545
/// Draws the outline of the polygon with the provided brush at the provided thickness.

src/ImageSharp.Drawing/Processing/Extensions/FillPathCollectionExtensions.cs

Lines changed: 145 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
33

4+
using SixLabors.ImageSharp.Drawing.Text;
5+
46
namespace SixLabors.ImageSharp.Drawing.Processing;
57

68
/// <summary>
@@ -14,7 +16,7 @@ public static class FillPathCollectionExtensions
1416
/// <param name="source">The source image processing context.</param>
1517
/// <param name="options">The graphics options.</param>
1618
/// <param name="brush">The brush.</param>
17-
/// <param name="paths">The shapes.</param>
19+
/// <param name="paths">The collection of paths.</param>
1820
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
1921
public static IImageProcessingContext Fill(
2022
this IImageProcessingContext source,
@@ -30,12 +32,120 @@ public static IImageProcessingContext Fill(
3032
return source;
3133
}
3234

35+
/// <summary>
36+
/// Flood fills the image in the shape of the provided glyphs with the specified brush and pen.
37+
/// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer.
38+
/// </summary>
39+
/// <param name="source">The source image processing context.</param>
40+
/// <param name="options">The graphics options.</param>
41+
/// <param name="brush">The brush.</param>
42+
/// <param name="pen">The pen.</param>
43+
/// <param name="paths">The collection of glyph paths.</param>
44+
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
45+
public static IImageProcessingContext Fill(
46+
this IImageProcessingContext source,
47+
DrawingOptions options,
48+
Brush brush,
49+
Pen pen,
50+
IReadOnlyList<GlyphPathCollection> paths)
51+
=> source.Fill(options, brush, pen, paths, static (gp, layer, path) =>
52+
{
53+
if (layer.Kind == GlyphLayerKind.Decoration)
54+
{
55+
// Decorations (underlines, strikethroughs, etc) are always filled.
56+
return true;
57+
}
58+
59+
if (layer.Kind == GlyphLayerKind.Glyph)
60+
{
61+
// Standard glyph layers are filled by default.
62+
return true;
63+
}
64+
65+
// Default heuristic: stroke "background-like" layers (large coverage), fill others.
66+
// Use the bounding box area as an approximation of the glyph area as it is cheaper to compute.
67+
float glyphArea = gp.Bounds.Width * gp.Bounds.Height;
68+
float layerArea = path.ComputeArea();
69+
70+
if (layerArea <= 0 || glyphArea <= 0)
71+
{
72+
return false; // degenerate glyph, don't fill
73+
}
74+
75+
float coverage = layerArea / glyphArea;
76+
77+
// <50% coverage, fill. Otherwise, stroke.
78+
return coverage < 0.50F;
79+
});
80+
81+
/// <summary>
82+
/// Flood fills the image in the shape of the provided glyphs with the specified brush and pen.
83+
/// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer.
84+
/// </summary>
85+
/// <param name="source">The source image processing context.</param>
86+
/// <param name="options">The graphics options.</param>
87+
/// <param name="brush">The brush.</param>
88+
/// <param name="pen">The pen.</param>
89+
/// <param name="paths">The collection of glyph paths.</param>
90+
/// <param name="shouldFillLayer">
91+
/// A function that decides whether to fill or stroke a given layer within a multi-layer (painted) glyph.
92+
/// </param>
93+
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
94+
public static IImageProcessingContext Fill(
95+
this IImageProcessingContext source,
96+
DrawingOptions options,
97+
Brush brush,
98+
Pen pen,
99+
IReadOnlyList<GlyphPathCollection> paths,
100+
Func<GlyphPathCollection, GlyphLayerInfo, IPath, bool> shouldFillLayer)
101+
{
102+
foreach (GlyphPathCollection gp in paths)
103+
{
104+
if (gp.LayerCount == 0)
105+
{
106+
continue;
107+
}
108+
109+
if (gp.LayerCount == 1)
110+
{
111+
// Single-layer glyph: just fill with the supplied brush.
112+
source.Fill(options, brush, gp.Paths);
113+
continue;
114+
}
115+
116+
// Multi-layer: decide per layer whether to fill or stroke.
117+
for (int i = 0; i < gp.Layers.Count; i++)
118+
{
119+
GlyphLayerInfo layer = gp.Layers[i];
120+
IPath path = gp.PathList[i];
121+
122+
if (shouldFillLayer(gp, layer, path))
123+
{
124+
// Respect the layer's fill rule if different to the drawing options.
125+
DrawingOptions o = options.CloneOrReturnForRules(
126+
layer.IntersectionRule,
127+
layer.PixelAlphaCompositionMode,
128+
layer.PixelColorBlendingMode);
129+
130+
source.Fill(o, brush, path);
131+
}
132+
else
133+
{
134+
// Outline only to preserve interior detail.
135+
source.Draw(options, pen, path);
136+
}
137+
}
138+
}
139+
140+
return source;
141+
}
142+
33143
/// <summary>
34144
/// Flood fills the image in the shape of the provided polygon with the specified brush.
35145
/// </summary>
36146
/// <param name="source">The source image processing context.</param>
37147
/// <param name="brush">The brush.</param>
38-
/// <param name="paths">The paths.</param>
148+
/// <param name="paths">The collection of paths.</param>
39149
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
40150
public static IImageProcessingContext Fill(
41151
this IImageProcessingContext source,
@@ -44,7 +154,23 @@ public static IImageProcessingContext Fill(
44154
source.Fill(source.GetDrawingOptions(), brush, paths);
45155

46156
/// <summary>
47-
/// Flood fills the image in the shape of the provided polygon with the specified brush.
157+
/// Flood fills the image in the shape of the provided glyphs with the specified brush and pen.
158+
/// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer.
159+
/// </summary>
160+
/// <param name="source">The source image processing context.</param>
161+
/// <param name="brush">The brush.</param>
162+
/// <param name="pen">The pen.</param>
163+
/// <param name="paths">The collection of glyph paths.</param>
164+
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
165+
public static IImageProcessingContext Fill(
166+
this IImageProcessingContext source,
167+
Brush brush,
168+
Pen pen,
169+
IReadOnlyList<GlyphPathCollection> paths) =>
170+
source.Fill(source.GetDrawingOptions(), brush, pen, paths);
171+
172+
/// <summary>
173+
/// Flood fills the image in the shape of the provided polygon with the specified color.
48174
/// </summary>
49175
/// <param name="source">The source image processing context.</param>
50176
/// <param name="options">The options.</param>
@@ -59,15 +185,29 @@ public static IImageProcessingContext Fill(
59185
source.Fill(options, new SolidBrush(color), paths);
60186

61187
/// <summary>
62-
/// Flood fills the image in the shape of the provided polygon with the specified brush.
188+
/// Flood fills the image in the shape of the provided polygon with the specified color.
63189
/// </summary>
64190
/// <param name="source">The source image processing context.</param>
65191
/// <param name="color">The color.</param>
66-
/// <param name="paths">The paths.</param>
192+
/// <param name="paths">The collection of paths.</param>
67193
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
68194
public static IImageProcessingContext Fill(
69195
this IImageProcessingContext source,
70196
Color color,
71197
IPathCollection paths) =>
72198
source.Fill(new SolidBrush(color), paths);
199+
200+
/// <summary>
201+
/// Flood fills the image in the shape of the provided glyphs with the specified color.
202+
/// For multi-layer glyphs, a heuristic is used to decide whether to fill or stroke each layer.
203+
/// </summary>
204+
/// <param name="source">The source image processing context.</param>
205+
/// <param name="color">The color.</param>
206+
/// <param name="paths">The collection of glyph paths.</param>
207+
/// <returns>The <see cref="IImageProcessingContext"/> to allow chaining of operations.</returns>
208+
public static IImageProcessingContext Fill(
209+
this IImageProcessingContext source,
210+
Color color,
211+
IReadOnlyList<GlyphPathCollection> paths) =>
212+
source.Fill(new SolidBrush(color), new SolidPen(color), paths);
73213
}

0 commit comments

Comments
 (0)