Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ImageSharp.Drawing.sln
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\build-and-test.yml = .github\workflows\build-and-test.yml
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Drawing.ManualBenchmarks", "tests\ImageSharp.Drawing.ManualBenchmarks\ImageSharp.Drawing.ManualBenchmarks.csproj", "{EB9C10E0-59B7-4620-B8EB-220E8FFC73E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -359,6 +361,10 @@ Global
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5493F024-0A3F-420C-AC2D-05B77A36025B}.Release|Any CPU.Build.0 = Release|Any CPU
{EB9C10E0-59B7-4620-B8EB-220E8FFC73E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EB9C10E0-59B7-4620-B8EB-220E8FFC73E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EB9C10E0-59B7-4620-B8EB-220E8FFC73E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EB9C10E0-59B7-4620-B8EB-220E8FFC73E6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -386,6 +392,7 @@ Global
{68A8CC40-6AED-4E96-B524-31B1158FDEEA} = {815C0625-CD3D-440F-9F80-2D83856AB7AE}
{5493F024-0A3F-420C-AC2D-05B77A36025B} = {528610AC-7C0C-46E8-9A2D-D46FD92FEE29}
{23859314-5693-4E6C-BE5C-80A433439D2A} = {1799C43E-5C54-4A8F-8D64-B1475241DB0D}
{EB9C10E0-59B7-4620-B8EB-220E8FFC73E6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5F8B9D1F-CD8B-4CC5-8216-D531E25BD795}
Expand Down
38 changes: 35 additions & 3 deletions src/ImageSharp.Drawing/Shapes/Path.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ SegmentInfo IPathInternals.PointAlongPath(float distance)
public static bool TryParseSvgPath(string svgPath, [NotNullWhen(true)] out IPath? value)
=> TryParseSvgPath(svgPath.AsSpan(), out value);

/// <summary>
/// <summary>
/// Converts an SVG path string into an <see cref="IPath"/>.
/// </summary>
/// <param name="svgPath">The string containing the SVG path data.</param>
Expand Down Expand Up @@ -381,10 +381,42 @@ private static ReadOnlySpan<char> FindScaler(ReadOnlySpan<char> str, out float s
str = TrimSeparator(str);
scaler = 0;

bool hasDot = false;
for (int i = 0; i < str.Length; i++)
{
if (IsSeparator(str[i]) || i == str.Length)
char ch = str[i];

if (IsSeparator(ch))
{
scaler = ParseFloat(str[..i]);
return str[i..];
}

if (ch == '.')
{
if (hasDot)
{
// Second decimal point starts a new number.
scaler = ParseFloat(str[..i]);
return str[i..];
}

hasDot = true;
}
else if ((ch is '-' or '+') && i > 0)
{
// A sign character mid-number starts a new number,
// unless it follows an exponent indicator.
char prev = str[i - 1];
if (prev is not 'e' and not 'E')
{
scaler = ParseFloat(str[..i]);
return str[i..];
}
}
else if (char.IsLetter(ch))
{
// Hit a command letter; end this number.
scaler = ParseFloat(str[..i]);
return str[i..];
}
Expand All @@ -395,7 +427,7 @@ private static ReadOnlySpan<char> FindScaler(ReadOnlySpan<char> str, out float s
scaler = ParseFloat(str);
}

return ReadOnlySpan<char>.Empty;
return Array.Empty<char>();
}

private static bool IsSeparator(char ch)
Expand Down
150 changes: 150 additions & 0 deletions tests/ImageSharp.Drawing.Benchmarks/Drawing/FillTiger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.

using System.Drawing;
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using SixLabors.ImageSharp.Drawing.Processing;
using SixLabors.ImageSharp.Drawing.Tests;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
using SkiaSharp;
using SDColor = System.Drawing.Color;
using SDPen = System.Drawing.Pen;
using SDSolidBrush = System.Drawing.SolidBrush;

namespace SixLabors.ImageSharp.Drawing.Benchmarks.Drawing;

/// <summary>
/// Benchmarks rendering the Ghostscript Tiger SVG (~240 path elements with fills and strokes)
/// across SkiaSharp, System.Drawing, ImageSharp (CPU), and ImageSharp (WebGPU).
/// </summary>
public class FillTiger
{
private static readonly string SvgFilePath =
TestFile.GetInputFileFullPath(TestImages.Svg.GhostscriptTiger);

private SKSurface skSurface;
private List<(SKPath Path, SKPaint FillPaint, SKPaint StrokePaint)> skElements;

private Bitmap sdBitmap;
private Graphics sdGraphics;
private List<(GraphicsPath Path, SDSolidBrush Fill, SDPen Stroke)> sdElements;

private Image<Rgba32> image;
private List<(IPath Path, Processing.SolidBrush Fill, SolidPen Stroke)> isElements;

[Params(1000, 100)]
public int Dimensions { get; set; }

[GlobalSetup]
public void Setup()
{
int width = this.Dimensions;
int height = this.Dimensions;
float scale = this.Dimensions / 200f;

ThreadPool.GetMinThreads(out int minWorkerThreads, out int minCompletionPortThreads);
int desiredWorkerThreads = Math.Max(minWorkerThreads, Environment.ProcessorCount);
ThreadPool.SetMinThreads(desiredWorkerThreads, minCompletionPortThreads);
Parallel.For(0, desiredWorkerThreads, static _ => { });

List<SvgBenchmarkHelper.SvgElement> elements = SvgBenchmarkHelper.ParseSvg(SvgFilePath);

this.skSurface = SKSurface.Create(new SKImageInfo(width, height));
this.skElements = SvgBenchmarkHelper.BuildSkiaElements(elements, scale);

this.sdBitmap = new Bitmap(width, height);
this.sdGraphics = Graphics.FromImage(this.sdBitmap);
this.sdGraphics.SmoothingMode = SmoothingMode.AntiAlias;
this.sdElements = SvgBenchmarkHelper.BuildSystemDrawingElements(elements, scale);

this.image = new Image<Rgba32>(width, height);
this.isElements = SvgBenchmarkHelper.BuildImageSharpElements(elements, scale);
}

[IterationSetup]
public void IterationSetup()
{
this.sdGraphics.Clear(SDColor.Transparent);
this.skSurface.Canvas.Clear(SKColors.Transparent);
}

[GlobalCleanup]
public void Cleanup()
{
foreach ((SKPath path, SKPaint fill, SKPaint stroke) in this.skElements)
{
path.Dispose();
fill?.Dispose();
stroke?.Dispose();
}

this.skSurface.Dispose();

foreach ((GraphicsPath path, SDSolidBrush fill, SDPen stroke) in this.sdElements)
{
path.Dispose();
fill?.Dispose();
stroke?.Dispose();
}

this.sdGraphics.Dispose();
this.sdBitmap.Dispose();

this.image.Dispose();
}

[Benchmark(Baseline = true)]
public void SkiaSharp()
{
SKCanvas canvas = this.skSurface.Canvas;
foreach ((SKPath path, SKPaint fillPaint, SKPaint strokePaint) in this.skElements)
{
if (fillPaint is not null)
{
canvas.DrawPath(path, fillPaint);
}

if (strokePaint is not null)
{
canvas.DrawPath(path, strokePaint);
}
}
}

[Benchmark]
public void SystemDrawing()
{
foreach ((GraphicsPath path, SDSolidBrush fill, SDPen stroke) in this.sdElements)
{
if (fill is not null)
{
this.sdGraphics.FillPath(fill, path);
}

if (stroke is not null)
{
this.sdGraphics.DrawPath(stroke, path);
}
}
}

[Benchmark]
public void ImageSharp()
=> this.image.Mutate(c =>
{
foreach ((IPath path, Processing.SolidBrush fill, SolidPen stroke) in this.isElements)
{
if (fill is not null)
{
c.Fill(fill, path);
}

if (stroke is not null)
{
c.Draw(stroke, path);
}
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<IsTestProject>false</IsTestProject>
<LangVersion>latest</LangVersion>
</PropertyGroup>

<Choose>
Expand All @@ -20,7 +21,7 @@
</PropertyGroup>
</Otherwise>
</Choose>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="System.Drawing.Common" />
Expand All @@ -45,6 +46,9 @@
<Compile Include="..\ImageSharp.Drawing.Tests\TestUtilities\TestEnvironment.cs">
<Link>TestEnvironment.cs</Link>
</Compile>
<Compile Include="..\ImageSharp.Drawing.Tests\TestUtilities\SvgBenchmarkHelper.cs">
<Link>SvgBenchmarkHelper.cs</Link>
</Compile>
</ItemGroup>

</Project>
Loading
Loading