meerk40t / svgelements

SVG Parsing for Elements, Paths, and other SVG Objects.
MIT License
133 stars 29 forks source link

Add in Approximate Arc with Bezier Functions. #66

Closed tatarize closed 3 years ago

tatarize commented 3 years ago

I don't want to edit the code too much during with the draft PR. Since it can get annoying. But, this code should be added to Path. I offered it up to svgpathtools and the code is actually pretty nice.

    def approximate_arcs_with_cubics(self, error=0.1):
        """
        Iterates through this path and replaces any Arcs with cubic bezier curves.
        """
        tau = pi * 2
        sweep_limit = degrees(tau * error)
        for s in range(len(self)-1, -1, -1):
            segment = self[s]
            if not isinstance(segment, Arc):
                continue
            arc_required = int(ceil(abs(segment.delta) / sweep_limit))
            self[s:s+1] = list(segment.as_cubic_curves(arc_required))

    def approximate_arcs_with_quads(self, error=0.1):
        """
        Iterates through this path and replaces any Arcs with quadratic bezier curves.
        """
        tau = pi * 2
        sweep_limit = degrees(tau * error)
        for s in range(len(self)-1, -1, -1):
            segment = self[s]
            if not isinstance(segment, Arc):
                continue
            arc_required = int(ceil(abs(segment.delta) / sweep_limit))
            self[s:s+1] = list(segment.as_quad_curves(arc_required))

And the test coverage added to TestPath, though maybe modify svgelements proper.

    def test_approx_quad(self):
        n = 100
        for i in range(n):
            arc = random_arc()
            if arc.radius.real > 2000 or arc.radius.imag > 2000:
                continue  # Random Arc too large, by autoscale.
            path1 = Path(arc)
            path2 = Path(*path1)
            path2.approximate_arcs_with_quads(error=0.05)
            d = abs(path1.length() - path2.length())
            # Error less than 1% typically less than 0.5%
            self.assertAlmostEqual(d, 0.0, delta=20)

    def test_approx_cubic(self):
        n = 100
        for i in range(n):
            arc = random_arc()
            if arc.radius.real > 2000 or arc.radius.imag > 2000:
                continue  # Random Arc too large, by autoscale.
            path1 = Path(arc)
            path2 = Path(*path1)
            path2.approximate_arcs_with_cubics(error=0.1)
            d = abs(path1.length() - path2.length())
            # Error less than 0.1% typically less than 0.001%
            self.assertAlmostEqual(d,0.0, delta=2)
tatarize commented 3 years ago

Is fixed in #69

Oddly that slice replacing line took a lot of work, since I needed the slice corrections giving that I have to validate the objects.