Skip to content

Commit 81eafe8

Browse files
allisterbmonoman
authored andcommitted
Implement DrawPath and FillPath methods for drawing and filling GraphicsPath objects. (#10)
PR from Allister Beharry @allisterb * Added initial implementation of DrawPath and basic test. * Initial implementation of FillParh. * Implementation of FillPath closes all figures according to MSDN documentation. * Added some documentation to implemented methods. * Updated test with path fill from MSDN. * Added some more docs.
1 parent ee685bd commit 81eafe8

File tree

5 files changed

+185
-8
lines changed

5 files changed

+185
-8
lines changed

SvgGdiTest/SvgGdiTestForm.Designer.cs

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SvgGdiTest/SvgGdiTestForm.cs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,53 @@ private void Render(IGraphics ig)
479479
ig.EndContainer(cnt);
480480
//ig.DrawImageUnscaled(bmp, 270, 450, 20, 20);
481481
}
482+
else if (s == "Path")
483+
{
484+
/* The following example GraphicsPath code comes from the MSDN docs on the GraphicsPathIterator class
485+
* https://msdn.microsoft.com/en-us/library/79k451ts.aspx
486+
*
487+
*/
488+
// Create a graphics path.
489+
GraphicsPath myPath = new GraphicsPath();
490+
491+
// Set up primitives to add to myPath.
492+
Point[] myPoints = { new Point(20, 20), new Point(120, 120), new Point(20, 120), new Point(20, 20) };
493+
Rectangle myRect = new Rectangle(120, 120, 100, 100);
494+
495+
// Add 3 lines, a rectangle, an ellipse, and 2 markers.
496+
myPath.AddLines(myPoints);
497+
myPath.SetMarkers();
498+
myPath.AddRectangle(myRect);
499+
myPath.SetMarkers();
500+
myPath.AddEllipse(220, 220, 100, 100);
501+
ig.DrawPath(new Pen(Color.Black), myPath);
502+
LinearGradientBrush gbr2 = new LinearGradientBrush(new Point(0, 0), new Point(10, 20), Color.WhiteSmoke, Color.CornflowerBlue);
503+
gbr2.WrapMode = WrapMode.TileFlipXY;
504+
ig.FillPath(gbr2, myPath);
505+
}
506+
else if (s == "Path 2 (Slow)")
507+
{
508+
SolidBrush mySolidBrush = new SolidBrush(Color.Aqua);
509+
GraphicsPath myGraphicsPath = new GraphicsPath();
510+
511+
Point[] myPointArray = {
512+
new Point(15, 20),
513+
new Point(20, 40),
514+
new Point(50, 30)};
515+
516+
FontFamily myFontFamily = new FontFamily("Times New Roman");
517+
PointF myPointF = new PointF(50, 20);
518+
StringFormat myStringFormat = new StringFormat();
519+
520+
myGraphicsPath.AddArc(0, 0, 30, 20, -90, 180);
521+
myGraphicsPath.AddCurve(myPointArray);
522+
myGraphicsPath.AddString("a string in a path filled", myFontFamily,
523+
0, 24, myPointF, myStringFormat);
524+
myGraphicsPath.AddPie(230, 10, 40, 40, 40, 110);
525+
526+
ig.FillPath(mySolidBrush, myGraphicsPath);
527+
ig.DrawPath(new Pen(Color.Green), myGraphicsPath);
528+
}
482529
else
483530
{
484531
throw new NotImplementedException();

SvgNet/SVGGraphics.cs

Lines changed: 121 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
using System.Drawing.Text;
1717
using System.Globalization;
1818
using System.IO;
19+
using System.Collections.Generic;
20+
using System.Linq;
1921

2022
namespace SvgNet.SvgGdi
2123
{
@@ -1965,9 +1967,95 @@ public void DrawLines(Pen pen, Point[] points)
19651967
}
19661968

19671969
/// <summary>
1968-
/// Not implemented because GDI+ regions/paths are not emulated.
1970+
/// Implemented
19691971
/// </summary>
1970-
public void DrawPath(Pen pen, GraphicsPath path) { throw new SvgGdiNotImpl("DrawPath (Pen pen, GraphicsPath path)"); }
1972+
/// <remarks>
1973+
/// Mainly based on the libgdi+ implementation: https://github.com/mono/libgdiplus/blob/master/src/graphics-cairo.c
1974+
/// and this SO question reply: https://stackoverflow.com/questions/1790862/how-to-determine-endpoints-of-arcs-in-graphicspath-pathpoints-and-pathtypes-arra
1975+
/// from SiiliconMind.
1976+
/// </remarks>
1977+
public void DrawPath(Pen pen, GraphicsPath path)
1978+
{
1979+
//Save the original pen dash style in case we need to change it
1980+
DashStyle originalPenDashStyle = pen.DashStyle;
1981+
1982+
GraphicsPathIterator subpaths = new GraphicsPathIterator(path);
1983+
GraphicsPath subpath = new GraphicsPath(path.FillMode);
1984+
subpaths.Rewind();
1985+
1986+
//Iterate through all the subpaths in the path. Each subpath will contain either
1987+
//lines or Bezier curves
1988+
for (int s = 0; s < subpaths.SubpathCount; s++)
1989+
{
1990+
bool isClosed;
1991+
if (subpaths.NextSubpath(subpath, out isClosed) == 0)
1992+
{
1993+
continue; //go to next subpath if this one has zero points.
1994+
}
1995+
PointF start = new PointF(0, 0);
1996+
PointF origin = subpath.PathPoints[0];
1997+
PointF last = subpath.PathPoints[subpath.PathPoints.Length - 1];
1998+
int bezierCurvePointsIndex = 0;
1999+
PointF[] bezierCurvePoints = new PointF[4];
2000+
for (int i = 0; i < subpath.PathPoints.Length; i++)
2001+
{
2002+
/* Each subpath point has a corresponding path point type which can be:
2003+
*The point starts the subpath
2004+
*The point is a line point
2005+
*The point is Bezier curve point
2006+
* Another point type like dash-mode
2007+
*/
2008+
switch ((PathPointType)subpath.PathTypes[i] & PathPointType.PathTypeMask) //Mask off non path-type types
2009+
{
2010+
case PathPointType.Start:
2011+
start = subpath.PathPoints[i];
2012+
bezierCurvePoints[0] = subpath.PathPoints[i];
2013+
bezierCurvePointsIndex = 1;
2014+
continue;
2015+
case PathPointType.Line:
2016+
DrawLine(pen, start, subpath.PathPoints[i]); //Draw a line segment ftom start point
2017+
start = subpath.PathPoints[i]; //Move start point here
2018+
bezierCurvePoints[0] = subpath.PathPoints[i]; //A line point can also be the start of a Bezier curve
2019+
bezierCurvePointsIndex = 1;
2020+
continue;
2021+
case PathPointType.Bezier3:
2022+
bezierCurvePoints[bezierCurvePointsIndex++] = subpath.PathPoints[i];
2023+
if (bezierCurvePointsIndex == 4) //If 4 points including start have been found then draw the Bezier curve
2024+
{
2025+
DrawBezier(pen, bezierCurvePoints[0], bezierCurvePoints[1], bezierCurvePoints[2], bezierCurvePoints[3]);
2026+
bezierCurvePoints = new PointF[4];
2027+
bezierCurvePoints[0] = subpath.PathPoints[i];
2028+
bezierCurvePointsIndex = 1;
2029+
}
2030+
continue;
2031+
default:
2032+
2033+
switch ((PathPointType)subpath.PathTypes[i])
2034+
{
2035+
case PathPointType.DashMode:
2036+
pen.DashStyle = DashStyle.Dash;
2037+
continue;
2038+
default:
2039+
throw new SvgException("Unknown path type value: " + subpath.PathTypes[i]);
2040+
}
2041+
}
2042+
}
2043+
if (isClosed) //If the subpath is closed and it is a linear figure then draw the last connecting line segment
2044+
{
2045+
PathPointType originType = (PathPointType)subpath.PathTypes[0];
2046+
PathPointType lastType = (PathPointType) subpath.PathTypes[subpath.PathPoints.Length - 1];
2047+
2048+
if (((lastType & PathPointType.PathTypeMask) == PathPointType.Line) && ((originType & PathPointType.PathTypeMask) == PathPointType.Line))
2049+
{
2050+
DrawLine(pen, last, origin);
2051+
}
2052+
}
2053+
2054+
}
2055+
subpath.Dispose();
2056+
subpaths.Dispose();
2057+
pen.DashStyle = originalPenDashStyle;
2058+
}
19712059

19722060
/// <summary>
19732061
/// Implemented. <c>DrawPie</c> functions work correctly and thus produce different output from GDI+ if the ellipse is not circular.
@@ -2249,9 +2337,38 @@ public void FillEllipse(Brush brush, Int32 x, Int32 y, Int32 width, Int32 height
22492337
}
22502338

22512339
/// <summary>
2252-
/// Not implemented, because GDI+ regions/paths are not emulated.
2340+
/// Implemented
22532341
/// </summary>
2254-
public void FillPath(Brush brush, GraphicsPath path) { throw new SvgGdiNotImpl("FillPath (Brush brush, GraphicsPath path)"); }
2342+
public void FillPath(Brush brush, GraphicsPath path)
2343+
{
2344+
GraphicsPathIterator subpaths = new GraphicsPathIterator(path);
2345+
GraphicsPath subpath = new GraphicsPath(path.FillMode);
2346+
subpaths.Rewind();
2347+
for (int s = 0; s < subpaths.SubpathCount; s++)
2348+
{
2349+
bool isClosed;
2350+
if (subpaths.NextSubpath(subpath, out isClosed) < 2)
2351+
{
2352+
continue;
2353+
}
2354+
if (!isClosed)
2355+
{
2356+
subpath.CloseAllFigures();
2357+
}
2358+
PathPointType lastType = (PathPointType)subpath.PathTypes[subpath.PathPoints.Length - 1];
2359+
if (subpath.PathTypes.Any(pt => ((PathPointType) pt & PathPointType.PathTypeMask) == PathPointType.Line))
2360+
{
2361+
FillPolygon(brush, subpath.PathPoints, path.FillMode);
2362+
}
2363+
else
2364+
{
2365+
FillBeziers(brush, subpath.PathPoints, path.FillMode);
2366+
}
2367+
2368+
}
2369+
subpath.Dispose();
2370+
subpaths.Dispose();
2371+
}
22552372

22562373
/// <summary>
22572374
/// Implemented <c>FillPie</c> functions work correctly and thus produce different output from GDI+ if the ellipse is not circular.

SvgNet/SvgNet.csproj

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,13 @@
170170
<PropertyGroup>
171171
<PreBuildEvent />
172172
<PostBuildEvent>cd $(ProjectDir)
173-
nuget pack SvgNet.csproj</PostBuildEvent>
173+
nuget pack SvgNet.csproj
174+
IF NOT %25ERRORLEVEL%25==0 (
175+
echo Did not build NuGet package.
176+
exit /b 0
177+
)
178+
179+
</PostBuildEvent>
174180
</PropertyGroup>
175181
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
176182
<PropertyGroup>

SvgNet/svgnetdoc.xml

Lines changed: 7 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)