Skip to content

Commit 503b167

Browse files
Basic first pass functionality (pre-cleanup)
1 parent 5132451 commit 503b167

File tree

7 files changed

+641
-16
lines changed

7 files changed

+641
-16
lines changed

ImageSharp.Drawing.sln

Lines changed: 10 additions & 2 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 17
4-
VisualStudioVersion = 17.0.31903.59
3+
# Visual Studio Version 18
4+
VisualStudioVersion = 18.0.11012.119 d18.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_root", "_root", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
77
ProjectSection(SolutionItems) = preProject
@@ -337,6 +337,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
337337
.github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
338338
EndProjectSection
339339
EndProject
340+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SixLabors.Fonts", "..\Fonts\src\SixLabors.Fonts\SixLabors.Fonts.csproj", "{4A922B77-34EC-EA6A-8E96-8353C8FA0640}"
341+
EndProject
340342
Global
341343
GlobalSection(SolutionConfigurationPlatforms) = preSolution
342344
Debug|Any CPU = Debug|Any CPU
@@ -359,6 +361,10 @@ Global
359361
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|Any CPU.Build.0 = Debug|Any CPU
360362
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.ActiveCfg = Release|Any CPU
361363
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.Build.0 = Release|Any CPU
364+
{4A922B77-34EC-EA6A-8E96-8353C8FA0640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
365+
{4A922B77-34EC-EA6A-8E96-8353C8FA0640}.Debug|Any CPU.Build.0 = Debug|Any CPU
366+
{4A922B77-34EC-EA6A-8E96-8353C8FA0640}.Release|Any CPU.ActiveCfg = Release|Any CPU
367+
{4A922B77-34EC-EA6A-8E96-8353C8FA0640}.Release|Any CPU.Build.0 = Release|Any CPU
362368
EndGlobalSection
363369
GlobalSection(SolutionProperties) = preSolution
364370
HideSolutionNode = FALSE
@@ -386,12 +392,14 @@ Global
386392
{68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
387393
{5493F024-0A3F-420C-AC2D-05B77A36025B} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29}
388394
{23859314-5693-4E6C-BE5C-80A433439D2A} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
395+
{4A922B77-34EC-EA6A-8E96-8353C8FA0640} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
389396
EndGlobalSection
390397
GlobalSection(ExtensibilityGlobals) = postSolution
391398
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
392399
EndGlobalSection
393400
GlobalSection(SharedMSBuildProjectFiles) = preSolution
394401
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{2e33181e-6e28-4662-a801-e2e7dc206029}*SharedItemsImports = 5
402+
..\Fonts\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{4a922b77-34ec-ea6a-8e96-8353c8fa0640}*SharedItemsImports = 5
395403
shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems*{68a8cc40-6aed-4e96-b524-31b1158fdeea}*SharedItemsImports = 13
396404
EndGlobalSection
397405
GlobalSection(Performance) = preSolution

src/ImageSharp.Drawing/ImageSharp.Drawing.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@
4545
<None Include="..\..\shared-infrastructure\branding\icons\imagesharp.drawing\sixlabors.imagesharp.drawing.128.png" Pack="true" PackagePath="" />
4646
</ItemGroup>
4747
<ItemGroup>
48-
<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.1" />
48+
<!--<PackageReference Include="SixLabors.Fonts" Version="3.0.0-alpha.0.1" />-->
4949
<PackageReference Include="SixLabors.ImageSharp" Version="4.0.0-alpha.0.44" />
5050
</ItemGroup>
51+
<ItemGroup>
52+
<ProjectReference Include="..\..\..\Fonts\src\SixLabors.Fonts\SixLabors.Fonts.csproj" />
53+
</ItemGroup>
5154
<Import Project="..\..\shared-infrastructure\src\SharedInfrastructure\SharedInfrastructure.projitems" Label="Shared" />
5255
</Project>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
// Copyright (c) Six Labors.
2+
// Licensed under the Six Labors Split License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using SixLabors.Fonts;
6+
using SixLabors.Fonts.Rendering;
7+
8+
namespace SixLabors.ImageSharp.Drawing.Processing.Processors.Text;
9+
10+
/// <content>
11+
/// Utilities to translate format-agnostic paints (from Fonts) into ImageSharp.Drawing brushes.
12+
/// </content>
13+
internal sealed partial class RichTextGlyphRenderer
14+
{
15+
/// <summary>
16+
/// Attempts to create an ImageSharp.Drawing <see cref="Brush"/> from a <see cref="Paint"/>.
17+
/// </summary>
18+
/// <param name="paint">The paint definition coming from the interpreter.</param>
19+
/// <param name="brush">The resulting brush, or <see langword="null"/> if the paint is unsupported.</param>
20+
/// <returns><see langword="true"/> if a brush could be created; otherwise, <see langword="false"/>.</returns>
21+
public static bool TryCreateBrush(Paint? paint, [NotNullWhen(true)] out Brush? brush)
22+
{
23+
brush = null;
24+
25+
if (paint is null)
26+
{
27+
return false;
28+
}
29+
30+
// TODO: Do we need to apply the transform assigned to th underlying builder here?
31+
switch (paint)
32+
{
33+
case SolidPaint sp:
34+
brush = new SolidBrush(ToColor(sp.Color, sp.Opacity));
35+
return true;
36+
37+
case LinearGradientPaint lg:
38+
return TryCreateLinearGradientBrush(lg, out brush);
39+
case RadialGradientPaint rg:
40+
return TryCreateRadialGradientBrush(rg, out brush);
41+
case SweepGradientPaint sg:
42+
return TryCreateSweepGradientBrush(sg, out brush);
43+
default:
44+
return false;
45+
}
46+
}
47+
48+
/// <summary>
49+
/// Creates a <see cref="LinearGradientBrush"/> from a <see cref="LinearGradientPaint"/>.
50+
/// </summary>
51+
/// <param name="lg">The linear gradient paint.</param>
52+
/// <param name="brush">The resulting brush.</param>
53+
/// <returns><see langword="true"/> if created; otherwise, <see langword="false"/>.</returns>
54+
private static bool TryCreateLinearGradientBrush(LinearGradientPaint lg, out Brush? brush)
55+
{
56+
// Map gradient stops (apply paint opacity multiplier to each stop’s alpha).
57+
ColorStop[] stops = ToColorStops(lg.Stops, lg.Opacity);
58+
59+
// Map spread method.
60+
GradientRepetitionMode mode = MapSpread(lg.Spread);
61+
62+
PointF p0 = lg.P0;
63+
PointF p1 = lg.P1;
64+
65+
// Degenerate gradient, fall back to solid using last stop.
66+
if (ApproximatelyEqual(p0, p1))
67+
{
68+
// TODO: Consider using this.currentColor instead?
69+
Color fallback = stops.Length > 0 ? stops[^1].Color : Color.Black;
70+
brush = new SolidBrush(fallback);
71+
return true;
72+
}
73+
74+
brush = new LinearGradientBrush(p0, p1, mode, stops);
75+
return true;
76+
}
77+
78+
/// <summary>
79+
/// Creates a <see cref="RadialGradientBrush"/> from a <see cref="RadialGradientPaint"/>.
80+
/// </summary>
81+
/// <param name="rg">The radial gradient paint.</param>
82+
/// <param name="brush">The resulting brush.</param>
83+
/// <returns><see langword="true"/> if created; otherwise, <see langword="false"/>.</returns>
84+
private static bool TryCreateRadialGradientBrush(RadialGradientPaint rg, out Brush? brush)
85+
{
86+
// Map gradient stops (apply paint opacity multiplier to each stop’s alpha).
87+
ColorStop[] stops = ToColorStops(rg.Stops, rg.Opacity);
88+
89+
// Map spread method.
90+
GradientRepetitionMode mode = MapSpread(rg.Spread);
91+
92+
brush = new RadialGradientBrush(rg.Center, rg.Radius, mode, stops);
93+
return true;
94+
}
95+
96+
/// <summary>
97+
/// Creates a <see cref="SweepGradientBrush"/> from a <see cref="SweepGradientPaint"/>.
98+
/// </summary>
99+
/// <param name="sg">The sweep gradient paint.</param>
100+
/// <param name="brush">The resulting brush.</param>
101+
/// <returns><see langword="true"/> if created; otherwise, <see langword="false"/>.</returns>
102+
private static bool TryCreateSweepGradientBrush(SweepGradientPaint sg, out Brush? brush)
103+
{
104+
// Map gradient stops (apply paint opacity multiplier to each stop’s alpha).
105+
ColorStop[] stops = ToColorStops(sg.Stops, sg.Opacity);
106+
107+
// Map spread method.
108+
GradientRepetitionMode mode = MapSpread(sg.Spread);
109+
110+
brush = new SweepGradientBrush(sg.Center, sg.StartAngle, sg.EndAngle, mode, stops);
111+
return true;
112+
}
113+
114+
/// <summary>
115+
/// Maps an <see cref="SpreadMethod"/> to <see cref="GradientRepetitionMode"/>.
116+
/// </summary>
117+
/// <param name="spread">The spread method.</param>
118+
/// <returns>The repetition mode.</returns>
119+
private static GradientRepetitionMode MapSpread(SpreadMethod spread)
120+
=> spread switch
121+
{
122+
SpreadMethod.Reflect => GradientRepetitionMode.Reflect,
123+
SpreadMethod.Repeat => GradientRepetitionMode.Repeat,
124+
125+
// Pad extends edge colors, which matches 'None' (not 'DontFill').
126+
_ => GradientRepetitionMode.None,
127+
};
128+
129+
/// <summary>
130+
/// Converts gradient stops and applies a paint opacity multiplier.
131+
/// </summary>
132+
/// <param name="stops">The source stops.</param>
133+
/// <param name="paintOpacity">The paint opacity in range [0,1].</param>
134+
/// <returns>An array of <see cref="ColorStop"/>.</returns>
135+
private static ColorStop[] ToColorStops(ReadOnlySpan<GradientStop> stops, float paintOpacity)
136+
{
137+
if (stops.Length == 0)
138+
{
139+
return [];
140+
}
141+
142+
ColorStop[] result = new ColorStop[stops.Length];
143+
144+
for (int i = 0; i < stops.Length; i++)
145+
{
146+
GradientStop s = stops[i];
147+
Color c = ToColor(s.Color, paintOpacity);
148+
result[i] = new ColorStop(s.Offset, c);
149+
}
150+
151+
return result;
152+
}
153+
154+
/// <summary>
155+
/// Converts a <see cref="GlyphColor"/> with an additional opacity multiplier to ImageSharp <see cref="Color"/>.
156+
/// </summary>
157+
/// <param name="c">The glyph color.</param>
158+
/// <param name="opacity">The opacity multiplier in range [0,1].</param>
159+
/// <returns>The ImageSharp color.</returns>
160+
private static Color ToColor(in GlyphColor c, float opacity)
161+
{
162+
float a = Math.Clamp(c.Alpha / 255f * Math.Clamp(opacity, 0f, 1f), 0f, 1f);
163+
byte aa = (byte)MathF.Round(a * 255f);
164+
return Color.FromPixel(new Rgba32(c.Red, c.Green, c.Blue, aa));
165+
}
166+
167+
/// <summary>
168+
/// Compares two points for near-equality.
169+
/// </summary>
170+
/// <param name="a">The first point.</param>
171+
/// <param name="b">The second point.</param>
172+
/// <param name="eps">Tolerance.</param>
173+
/// <returns><see langword="true"/> if near-equal; otherwise <see langword="false"/>.</returns>
174+
private static bool ApproximatelyEqual(in PointF a, in PointF b, float eps = 1e-4f)
175+
=> MathF.Abs(a.X - b.X) <= eps && MathF.Abs(a.Y - b.Y) <= eps;
176+
}

0 commit comments

Comments
 (0)