/// <summary>
        /// Generates a solid outline of the path.
        /// </summary>
        /// <param name="path">the path to outline</param>
        /// <param name="width">The final width outline</param>
        /// <param name="jointStyle">The style to render the joints.</param>
        /// <param name="endCapStyle">The style to render the end caps of open paths (ignored on closed paths).</param>
        /// <returns>A new path representing the outline.</returns>
        public static IPath GenerateOutline(this IPath path, float width, JointStyle jointStyle = JointStyle.Square, EndCapStyle endCapStyle = EndCapStyle.Square)
        {
            var offset = new ClipperOffset()
            {
                MiterLimit = MiterOffsetDelta
            };

            JoinType style           = Convert(jointStyle);
            EndType  openEndCapStyle = Convert(endCapStyle);

            // Pattern can be applied to the path by cutting it into segments
            IEnumerable <ISimplePath> paths = path.Flatten();

            foreach (ISimplePath p in paths)
            {
                IReadOnlyList <PointF> vectors = p.Points;
                var points = new List <IntPoint>(vectors.Count);
                foreach (Vector2 v in vectors)
                {
                    points.Add(new IntPoint(v.X * ScalingFactor, v.Y * ScalingFactor));
                }

                EndType type = p.IsClosed ? EndType.etClosedLine : openEndCapStyle;

                offset.AddPath(points, style, type);
            }

            return(ExecuteOutliner(width, offset));
        }
Beispiel #2
0
        private void VerifyPoints(PointF[] expectedPoints, IPath path)
        {
            var simplePath = Assert.Single(path.Flatten());

            Assert.True(simplePath.IsClosed);
            Assert.Equal(expectedPoints, simplePath.Points.ToArray());
        }
 public override void DrawLine(IImageProcessingContext context, float scale, IPath path)
 {
     if (Marker == null)
     {
         base.DrawLine(context, scale, path);
     }
     else
     {
         var             simplePaths     = path.Flatten();
         PointF[]        points          = new PointF[2];
         GraphicsOptions graphicsOptions = GraphicsOptions.Default;
         foreach (var simplePath in simplePaths)
         {
             for (int i = 0; i < simplePath.Points.Count - 1; i++)
             {
                 PointF point0 = simplePath.Points[i];
                 PointF point1 = simplePath.Points[i + 1];
                 IPen   pen    = ToPen(scale, point0, point1);
                 points[0] = point0;
                 points[1] = point1;
                 context.Draw(graphicsOptions, pen, points.ToPath());
             }
         }
     }
 }
Beispiel #4
0
        /// <summary>
        /// Calculates the approximate length of the path as though each segment were unrolled into a line.
        /// </summary>
        /// <param name="path">The path to compute the length for.</param>
        /// <returns>
        /// The <see cref="float"/> representing the unrolled length.
        /// For closed paths, the length includes an implicit closing segment.
        /// </returns>
        public static float ComputeLength(this IPath path)
        {
            float dist = 0;

            foreach (ISimplePath s in path.Flatten())
            {
                ReadOnlySpan <PointF> points = s.Points.Span;
                if (points.Length < 2)
                {
                    // Only a single point
                    continue;
                }

                for (int i = 1; i < points.Length; i++)
                {
                    dist += Vector2.Distance(points[i - 1], points[i]);
                }

                if (s.IsClosed)
                {
                    dist += Vector2.Distance(points[0], points[points.Length - 1]);
                }
            }

            return(dist);
        }
Beispiel #5
0
 /// <summary>
 /// Adds the path.
 /// </summary>
 /// <param name="path">The path.</param>
 /// <param name="clippingType">The clipping type.</param>
 public void AddPath(IPath path, ClippingType clippingType)
 {
     Guard.NotNull(path, nameof(path));
     foreach (ISimplePath p in path.Flatten())
     {
         this.AddPath(p, clippingType);
     }
 }
Beispiel #6
0
        private void VerifyPoints(IPath expectedPath, IPath path)
        {
            var simplePathExpected = Assert.Single(expectedPath.Flatten());
            var expectedPoints     = simplePathExpected.Points.ToArray();

            var simplePath = Assert.Single(path.Flatten());

            Assert.True(simplePath.IsClosed);
            Assert.Equal(expectedPoints, simplePath.Points.ToArray());
        }
        private void VerifyPoints(PointF[] expectedPoints, IPath path)
        {
            Path                   innerPath     = Assert.IsType <Path>(path);
            ILineSegment           segment       = Assert.Single(innerPath.LineSegments);
            CubicBezierLineSegment bezierSegment = Assert.IsType <CubicBezierLineSegment>(segment);

            Assert.Equal(expectedPoints, bezierSegment.ControlPoints.ToArray());

            ISimplePath simplePath = Assert.Single(path.Flatten());

            Assert.False(simplePath.IsClosed);
        }
        /// <summary>
        /// Create a path with the segment order reversed.
        /// </summary>
        /// <param name="path">The path to reverse.</param>
        /// <returns>The reversed <see cref="IPath"/>.</returns>
        internal static IPath Reverse(this IPath path)
        {
            IEnumerable <LinearLineSegment> segments = path.Flatten().Select(p => new LinearLineSegment(p.Points.ToArray().Reverse().ToArray()));
            bool closed = false;

            if (path is ISimplePath sp)
            {
                closed = sp.IsClosed;
            }

            return(closed ? new Polygon(segments) : new Path(segments));
        }
        /// <summary>
        /// Adds the path.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="clippingType">The clipping type.</param>
        public void AddPath(IPath path, ClippingType clippingType)
        {
            if (path is null)
            {
                throw new ArgumentNullException(nameof(path));
            }

            foreach (ISimplePath p in path.Flatten())
            {
                this.AddPath(p, clippingType);
            }
        }
Beispiel #10
0
        public static TessellatedMultipolygon Create(IPath path, MemoryAllocator memoryAllocator)
        {
            if (path is IInternalPathOwner ipo)
            {
                IReadOnlyList <InternalPath> internalPaths = ipo.GetRingsAsInternalPath();

                // If we have only one ring, we can change it's orientation without negative side-effects.
                // Since the algorithm works best with positively-oriented polygons,
                // we enforce the orientation for best output quality.
                bool enforcePositiveOrientationOnFirstRing = internalPaths.Count == 1;

                var rings = new Ring[internalPaths.Count];
                IMemoryOwner <PointF> pointBuffer = internalPaths[0].ExtractVertices(memoryAllocator);
                RepeateFirstVertexAndEnsureOrientation(pointBuffer.Memory.Span, enforcePositiveOrientationOnFirstRing);
                rings[0] = new Ring(pointBuffer);

                for (int i = 1; i < internalPaths.Count; i++)
                {
                    pointBuffer = internalPaths[i].ExtractVertices(memoryAllocator);
                    RepeateFirstVertexAndEnsureOrientation(pointBuffer.Memory.Span, false);
                    rings[i] = new Ring(pointBuffer);
                }

                return(new TessellatedMultipolygon(rings));
            }
            else
            {
                ReadOnlyMemory <PointF>[] points = path.Flatten().Select(sp => sp.Points).ToArray();

                // If we have only one ring, we can change it's orientation without negative side-effects.
                // Since the algorithm works best with positively-oriented polygons,
                // we enforce the orientation for best output quality.
                bool enforcePositiveOrientationOnFirstRing = points.Length == 1;

                var rings = new Ring[points.Length];
                rings[0] = MakeRing(points[0], enforcePositiveOrientationOnFirstRing, memoryAllocator);
                for (int i = 1; i < points.Length; i++)
                {
                    rings[i] = MakeRing(points[i], false, memoryAllocator);
                }

                return(new TessellatedMultipolygon(rings));
            }
        private static void CompareToSkiaResultsImpl(TestImageProvider <Rgba32> provider, IPath shape)
        {
            using Image <Rgba32> image = provider.GetImage();
            image.Mutate(c => c.Fill(Color.White, shape));
            image.DebugSave(provider, "ImageSharp", appendPixelTypeToFileName: false, appendSourceFileOrDescription: false);

            using var bitmap = new SKBitmap(new SKImageInfo(image.Width, image.Height));

            using var skPath = new SKPath();

            foreach (ISimplePath loop in shape.Flatten())
            {
                ReadOnlySpan <SKPoint> points = MemoryMarshal.Cast <PointF, SKPoint>(loop.Points.Span);
                skPath.AddPoly(points.ToArray());
            }

            using var paint = new SKPaint
                  {
                      Style       = SKPaintStyle.Fill,
                      Color       = SKColors.White,
                      IsAntialias = true,
                  };

            using var canvas = new SKCanvas(bitmap);
            canvas.Clear(new SKColor(0, 0, 0));
            canvas.DrawPath(skPath, paint);

            using var skResultImage =
                      Image.LoadPixelData <Rgba32>(bitmap.GetPixelSpan(), image.Width, image.Height);
            skResultImage.DebugSave(
                provider,
                "SkiaSharp",
                appendPixelTypeToFileName: false,
                appendSourceFileOrDescription: false);

            ImageSimilarityReport <Rgba32, Rgba32> result = ImageComparer.Exact.CompareImagesOrFrames(image, skResultImage);

            throw new Exception(result.DifferencePercentageString);
        }
Beispiel #12
0
        /// <summary>
        /// Generates a solid outline of the path.
        /// </summary>
        /// <param name="path">the path to outline</param>
        /// <param name="width">The final width outline</param>
        /// <returns>A new path representing the outline.</returns>
        public static IPath GenerateOutline(this IPath path, float width)
        {
            ClipperOffset offset = new ClipperLib.ClipperOffset();

            // Pattern can be applied to the path by cutting it into segments
            IEnumerable <ISimplePath> paths = path.Flatten();

            foreach (ISimplePath p in paths)
            {
                IReadOnlyList <PointF> vectors = p.Points;
                List <IntPoint>        points  = new List <ClipperLib.IntPoint>(vectors.Count);
                foreach (Vector2 v in vectors)
                {
                    points.Add(new IntPoint(v.X * ScalingFactor, v.Y * ScalingFactor));
                }

                EndType type = p.IsClosed ? EndType.etClosedLine : EndType.etOpenButt;

                offset.AddPath(points, JoinType.jtSquare, type);
            }

            return(ExecuteOutliner(width, offset));
        }
Beispiel #13
0
        /// <summary>
        /// Generates a outline of the path with alternating on and off segments based on the pattern.
        /// </summary>
        /// <param name="path">the path to outline</param>
        /// <param name="width">The final width outline</param>
        /// <param name="pattern">The pattern made of multiples of the width.</param>
        /// <param name="startOff">Weather the first item in the pattern is on or off.</param>
        /// <returns>A new path representing the outline.</returns>
        public static IPath GenerateOutline(this IPath path, float width, float[] pattern, bool startOff)
        {
            if (pattern == null || pattern.Length < 2)
            {
                return(path.GenerateOutline(width));
            }

            IEnumerable <ISimplePath> paths = path.Flatten();

            ClipperOffset offset = new ClipperOffset();

            List <IntPoint> buffer = new List <IntPoint>(3);

            foreach (ISimplePath p in paths)
            {
                bool  online       = !startOff;
                float targetLength = pattern[0] * width;
                int   patternPos   = 0;

                // Create a new list of points representing the new outline
                int pCount = p.Points.Count;
                if (!p.IsClosed)
                {
                    pCount--;
                }

                int     i            = 0;
                Vector2 currentPoint = p.Points[0];

                while (i < pCount)
                {
                    int     next        = (i + 1) % p.Points.Count;
                    Vector2 targetPoint = p.Points[next];
                    float   distToNext  = Vector2.Distance(currentPoint, targetPoint);
                    if (distToNext > targetLength)
                    {
                        // find a point between the 2
                        float t = targetLength / distToNext;

                        Vector2 point = (currentPoint * (1 - t)) + (targetPoint * t);
                        buffer.Add(currentPoint.ToPoint());
                        buffer.Add(point.ToPoint());

                        // we now inset a line joining
                        if (online)
                        {
                            offset.AddPath(buffer, JoinType.jtSquare, EndType.etOpenButt);
                        }

                        online = !online;

                        buffer.Clear();

                        currentPoint = point;

                        // next length
                        patternPos   = (patternPos + 1) % pattern.Length;
                        targetLength = pattern[patternPos] * width;
                    }
                    else if (distToNext <= targetLength)
                    {
                        buffer.Add(currentPoint.ToPoint());
                        currentPoint = targetPoint;
                        i++;
                        targetLength -= distToNext;
                    }
                }

                if (buffer.Count > 0)
                {
                    if (p.IsClosed)
                    {
                        buffer.Add(p.Points.First().ToPoint());
                    }
                    else
                    {
                        buffer.Add(p.Points.Last().ToPoint());
                    }

                    if (online)
                    {
                        offset.AddPath(buffer, JoinType.jtSquare, EndType.etOpenButt);
                    }

                    online = !online;

                    buffer.Clear();
                    patternPos   = (patternPos + 1) % pattern.Length;
                    targetLength = pattern[patternPos] * width;
                }
            }

            return(ExecuteOutliner(width, offset));
        }
        /// <summary>
        /// Generates a outline of the path with alternating on and off segments based on the pattern.
        /// </summary>
        /// <param name="path">the path to outline</param>
        /// <param name="width">The final width outline</param>
        /// <param name="pattern">The pattern made of multiples of the width.</param>
        /// <param name="startOff">Weather the first item in the pattern is on or off.</param>
        /// <param name="jointStyle">The style to render the joints.</param>
        /// <param name="patternSectionCapStyle">The style to render between sections of the specified pattern.</param>
        /// <returns>A new path representing the outline.</returns>
        public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan <float> pattern, bool startOff, JointStyle jointStyle = JointStyle.Square, EndCapStyle patternSectionCapStyle = EndCapStyle.Butt)
        {
            if (pattern.Length < 2)
            {
                return(path.GenerateOutline(width, jointStyle: jointStyle));
            }

            JoinType style             = Convert(jointStyle);
            EndType  patternSectionCap = Convert(patternSectionCapStyle);

            IEnumerable <ISimplePath> paths = path.Flatten();

            var offset = new ClipperOffset()
            {
                MiterLimit = MiterOffsetDelta
            };

            var buffer = new List <IntPoint>(3);

            foreach (ISimplePath p in paths)
            {
                bool  online       = !startOff;
                float targetLength = pattern[0] * width;
                int   patternPos   = 0;

                // Create a new list of points representing the new outline
                int pCount = p.Points.Count;
                if (!p.IsClosed)
                {
                    pCount--;
                }

                int     i            = 0;
                Vector2 currentPoint = p.Points[0];

                while (i < pCount)
                {
                    int     next        = (i + 1) % p.Points.Count;
                    Vector2 targetPoint = p.Points[next];
                    float   distToNext  = Vector2.Distance(currentPoint, targetPoint);
                    if (distToNext > targetLength)
                    {
                        // find a point between the 2
                        float t = targetLength / distToNext;

                        Vector2 point = (currentPoint * (1 - t)) + (targetPoint * t);
                        buffer.Add(currentPoint.ToPoint());
                        buffer.Add(point.ToPoint());

                        // we now inset a line joining
                        if (online)
                        {
                            offset.AddPath(buffer, style, patternSectionCap);
                        }

                        online = !online;

                        buffer.Clear();

                        currentPoint = point;

                        // next length
                        patternPos   = (patternPos + 1) % pattern.Length;
                        targetLength = pattern[patternPos] * width;
                    }
                    else if (distToNext <= targetLength)
                    {
                        buffer.Add(currentPoint.ToPoint());
                        currentPoint = targetPoint;
                        i++;
                        targetLength -= distToNext;
                    }
                }

                if (buffer.Count > 0)
                {
                    if (p.IsClosed)
                    {
                        buffer.Add(p.Points.First().ToPoint());
                    }
                    else
                    {
                        buffer.Add(p.Points.Last().ToPoint());
                    }

                    if (online)
                    {
                        offset.AddPath(buffer, style, patternSectionCap);
                    }

                    online = !online;

                    buffer.Clear();
                    patternPos   = (patternPos + 1) % pattern.Length;
                    targetLength = pattern[patternPos] * width;
                }
            }

            return(ExecuteOutliner(width, offset));
        }
        /// <summary>
        /// Generates an outline of the path with alternating on and off segments based on the pattern.
        /// </summary>
        /// <param name="path">The path to outline</param>
        /// <param name="width">The outline width.</param>
        /// <param name="pattern">The pattern made of multiples of the width.</param>
        /// <param name="startOff">Whether the first item in the pattern is on or off.</param>
        /// <param name="jointStyle">The style to apply to the joints.</param>
        /// <param name="endCapStyle">The style to apply to the end caps.</param>
        /// <returns>A new <see cref="IPath"/> representing the outline.</returns>
        /// <exception cref="ClipperException">Thrown when an offset cannot be calculated.</exception>
        public static IPath GenerateOutline(this IPath path, float width, ReadOnlySpan <float> pattern, bool startOff, JointStyle jointStyle, EndCapStyle endCapStyle)
        {
            if (pattern.Length < 2)
            {
                return(path.GenerateOutline(width, jointStyle, endCapStyle));
            }

            IEnumerable <ISimplePath> paths = path.Flatten();

            ClipperOffset offset = new(MiterOffsetDelta);
            List <PointF> buffer = new();

            foreach (ISimplePath p in paths)
            {
                bool  online                 = !startOff;
                float targetLength           = pattern[0] * width;
                int   patternPos             = 0;
                ReadOnlySpan <PointF> points = p.Points.Span;

                // Create a new list of points representing the new outline
                int pCount = points.Length;
                if (!p.IsClosed)
                {
                    pCount--;
                }

                int     i            = 0;
                Vector2 currentPoint = points[0];

                while (i < pCount)
                {
                    int     next        = (i + 1) % points.Length;
                    Vector2 targetPoint = points[next];
                    float   distToNext  = Vector2.Distance(currentPoint, targetPoint);
                    if (distToNext > targetLength)
                    {
                        // find a point between the 2
                        float t = targetLength / distToNext;

                        Vector2 point = (currentPoint * (1 - t)) + (targetPoint * t);
                        buffer.Add(currentPoint);
                        buffer.Add(point);

                        // we now inset a line joining
                        if (online)
                        {
                            offset.AddPath(new ReadOnlySpan <PointF>(buffer.ToArray()), jointStyle, endCapStyle);
                        }

                        online = !online;

                        buffer.Clear();

                        currentPoint = point;

                        // next length
                        patternPos   = (patternPos + 1) % pattern.Length;
                        targetLength = pattern[patternPos] * width;
                    }
                    else if (distToNext <= targetLength)
                    {
                        buffer.Add(currentPoint);
                        currentPoint = targetPoint;
                        i++;
                        targetLength -= distToNext;
                    }
                }

                if (buffer.Count > 0)
                {
                    if (p.IsClosed)
                    {
                        buffer.Add(points[0]);
                    }
                    else
                    {
                        buffer.Add(points[points.Length - 1]);
                    }

                    if (online)
                    {
                        offset.AddPath(new ReadOnlySpan <PointF>(buffer.ToArray()), jointStyle, endCapStyle);
                    }

                    online = !online;

                    buffer.Clear();
                    patternPos   = (patternPos + 1) % pattern.Length;
                    targetLength = pattern[patternPos] * width;
                }
            }

            return(offset.Execute(width));
        }