diff --git a/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs b/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs index c76b7811..21e499a3 100644 --- a/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs +++ b/src/ImageSharp.Drawing/Shapes/OutlinePathExtensions.cs @@ -15,6 +15,31 @@ public static class OutlinePathExtensions private const JointStyle DefaultJointStyle = JointStyle.Square; private const EndCapStyle DefaultEndCapStyle = EndCapStyle.Butt; + /// + /// Calculates the scaling matrixes tha tmust be applied to the inout and output paths of for successful clipping. + /// + /// the requested width + /// The matrix to apply to the input path + /// The matrix to apply to the output path + /// The final width to use internally to outlining + private static float CalculateScalingMatrix(float width, out Matrix3x2 scaleUpMartrix, out Matrix3x2 scaleDownMartrix) + { + // when the thickness is below a 0.5 threshold we need to scale + // the source path (up) and result path (down) by a factor to ensure + // the offest is greater than 0.5 to ensure offsetting isn't skipped. + scaleUpMartrix = Matrix3x2.Identity; + scaleDownMartrix = Matrix3x2.Identity; + if (width < 0.5) + { + float scale = 1 / width; + scaleUpMartrix = Matrix3x2.CreateScale(scale); + scaleDownMartrix = Matrix3x2.CreateScale(width); + width = 1; + } + + return width; + } + /// /// Generates an outline of the path. /// @@ -41,10 +66,14 @@ public static IPath GenerateOutline(this IPath path, float width, JointStyle joi return Path.Empty; } + width = CalculateScalingMatrix(width, out Matrix3x2 scaleUpMartrix, out Matrix3x2 scaleDownMartrix); + ClipperOffset offset = new(MiterOffsetDelta); - offset.AddPath(path, jointStyle, endCapStyle); - return offset.Execute(width); + // transform is noop for Matrix3x2.Identity + offset.AddPath(path.Transform(scaleUpMartrix), jointStyle, endCapStyle); + + return offset.Execute(width).Transform(scaleDownMartrix); } /// @@ -106,7 +135,9 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan paths = path.Flatten(); + width = CalculateScalingMatrix(width, out Matrix3x2 scaleUpMartrix, out Matrix3x2 scaleDownMartrix); + + IEnumerable paths = path.Transform(scaleUpMartrix).Flatten(); ClipperOffset offset = new(MiterOffsetDelta); List buffer = new(); @@ -186,6 +217,6 @@ public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan(TestImageProvider provider, float scale) + where TPixel : unmanaged, IPixel + { + Color color = Color.RebeccaPurple; + provider.RunValidatingProcessorTest( + x => x.DrawPolygon( + color, + scale, + new PointF[] { + new(5, 5), + new(5, 150), + new(190, 150), + }), + new { scale }); + } + + [Theory] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 3f)] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 1f)] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 0.3f)] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 0.7f)] + [WithSolidFilledImages(300, 300, "White", PixelTypes.Rgba32, 0.003f)] + public void DrawPolygonMustDrawoutlineOnly_Pattern(TestImageProvider provider, float scale) + where TPixel : unmanaged, IPixel + { + Color color = Color.RebeccaPurple; + var pen = Pens.DashDot(color, scale); + provider.RunValidatingProcessorTest( + x => x.DrawPolygon( + pen, + new PointF[] { + new(5, 5), + new(5, 150), + new(190, 150), + }), + new { scale }); + } +} diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png new file mode 100644 index 00000000..1770f151 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc86c51ad4946fb8a314c8d869a83cc2496d30468036729c3827c2c121cae69c +size 1068 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png new file mode 100644 index 00000000..a60e0771 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:62f86685f6f2326e629b8b84340bb1b3cbcf6ffe75facff0931b613337772345 +size 3296 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png new file mode 100644 index 00000000..5bf6378e --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:534d3fece38b94386b6ba20aa121addda1beea61d38cb34b9d2ae09b662fd38b +size 3585 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png new file mode 100644 index 00000000..61df79c3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4fff4ab1fec04c432529fd67d9c50934ce083fa6e7c0c4432fd6520eac2e53ac +size 3625 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png new file mode 100644 index 00000000..d5630da2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Pattern_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92a356847e8aa36b361a2208021a0c0e2a3287d615f0b948bdbcc0bc3b336bc4 +size 4300 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png new file mode 100644 index 00000000..924d09bf --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.003.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b02efd026d5ed83d1e0e747c8675d340bda7ba246d5a9385ecc6dabc81861e0 +size 3015 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png new file mode 100644 index 00000000..e6b9aee3 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f02d5a40abd8bbef39f2648f2d40d5da5a28adfbc5b015a56641eef5fb50de05 +size 3050 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png new file mode 100644 index 00000000..8f585eb2 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-0.7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:11b915b55e0005b52bf80e4891f293b6cb6477f3e0d83fead71768ac359fa610 +size 3382 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png new file mode 100644 index 00000000..79f87092 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8a48c33c58c36ba7f581fe7d60eaf0bbc96948640b14ccbdd891adeae031a7ad +size 3691 diff --git a/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png new file mode 100644 index 00000000..7b5c2352 --- /dev/null +++ b/tests/Images/ReferenceOutput/Issue_323/DrawPolygonMustDrawoutlineOnly_Rgba32_Solid300x300_(255,255,255,255)_scale-3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e80954c53985fc65b0846778861e3c023a8c798d466be887545fe2438ab5e3a3 +size 4353