Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix arc handling for VMobjectFromSVGPath when a matrix transform is present in the SVG #2322

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

jkjkil4
Copy link
Contributor

@jkjkil4 jkjkil4 commented Mar 7, 2025

Motivation

Handling arcs in VMobjectFromSVGPath is currently flawed. It works well when there is no matrix transformation in the SVG, but when such transformations are present, the behavior becomes problematic. The following comparison illustrates the issue (see the raw SVG file in the Test section):

VS Code Manim
image image

When opening the SVG file in VS Code, the expected rendering is shown on the left, whereas the right image shows the distorted result rendered by Manim. The issue mainly affects the "wing" part of the SVG.

Based on my analysis, this problem is caused by the svgelements library. When handling arcs with transformations, the library only transforms a few key points of the arc and assumes that the vectors from arc.center to arc.prx (major axis endpoint) and arc.pry (minor axis endpoint) remain perpendicular. While this approach correctly handles translations and rotations, it fails for shear matrix transformations, leading to incorrect path calculations.

To address this, I devised a solution leveraging path.values['transform'], which stores the cumulative effect of all parent group transformations. The proposed approach follows these steps:

  1. Apply the inverse transformation to the arc, converting it to a coordinate space where the vectors from arc.center to arc.prx and arc.pry remain perpendicular.
  2. Compute the arc path in this transformed space.
  3. Transform the computed path back to the original coordinate space.

This method correctly handles arc paths and eliminates the need for path_obj.approximate_arcs_with_quads().

See the Test section for detailed comparisons.

Proposed changes

  • Fix the behaviour of VMobjectFromSVGPath when handling arcs

Test

Code:

from manimlib import *

class Test(Scene):
    def construct(self):
        svg1 = SVGMobject('to.svg')
        svg2 = SVGMobject('rings.svg', stroke_width=5)
        svg3 = SVGMobject('insect.svg')
        group = Group(svg1, svg2, svg3).arrange(DOWN)
        self.add(group)
to.svg

This is an example from jkjkil4/JAnim#12

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   width="335.62592"
   height="240.87685"
   viewBox="0 0 88.80102 63.731996"
   version="1.1"
   id="svg1"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <defs
     id="defs1" />
  <g
     id="layer1"
     transform="translate(-4.3573659,-20.663967)">
    <path
       id="rect1"
       style="fill:#ffffff;stroke-width:0.202172"
       d="m 30.66848,81.398293 h 62.489902 v 2.99767 H 30.66848 Z" />
    <path
       id="rect2"
       style="fill:#ffffff;stroke-width:0.472574"
       d="m -21.706642,36.676408 a 9.7250077,28.853517 61.995476 0 0 -1.872195,-2.785827 l -30.851001,24.749156 20.682945,-29.905015 a 7.7804066,25.784219 24.46654 0 0 -6.886647,-3.788618 7.7804066,25.784219 24.46654 0 0 -12.896399,18.646394 7.7804066,25.784219 24.46654 0 0 -5.478594,17.172777 l -34.46873,-3.42e-4 -3.6e-4,4.960387 34.134985,3.1e-5 a 7.7804066,25.784219 24.46654 0 0 -1.49e-4,0.01767 l 0.0121,-0.01745 17.501194,-3.77e-4 -2.2e-4,-4.960096 -8.036314,-4.03e-4 a 9.7250077,28.853517 61.995476 0 0 18.765557,-11.004922 9.7250077,28.853517 61.995476 0 0 9.393826,-13.083362 z"
       transform="matrix(0.50074669,-0.86559387,0.99868878,0.05119289,0,0)" />
    <path
       style="font-weight:300;font-size:45.8047px;font-family:'LXGW WenKai';-inkscape-font-specification:'LXGW WenKai, Light';fill:#ffffff;stroke-width:0.468752"
       d="m 56.33701,43.548826 -0.09161,4.076619 v 2.290235 l 1.374141,-0.04581 q 1.648969,-0.09161 3.481157,-0.274828 h 0.0458 q 0.549657,0 0.961899,0.595461 0.412242,0.549656 0.412242,1.099312 0,0.503852 -0.458047,0.503852 h -1.740578 q -1.282532,0 -2.198626,0.04581 l -1.877992,0.09161 v 14.932332 q 0,1.923797 0.366437,2.565063 0.366438,0.641266 1.374141,0.641266 1.832188,0 4.763689,-2.33604 0.549656,-0.366437 0.916094,-0.366437 0.366437,0 0.366437,0.412242 0,0.366438 -0.549656,1.145118 -0.549656,0.778679 -1.46575,1.603164 -2.152821,2.015407 -4.397251,2.015407 -2.198626,0 -3.023111,-1.419946 -0.824484,-1.46575 -0.824484,-4.351446 V 52.022696 h -0.183219 q -1.007703,0.0458 -1.969602,0.09161 l -0.961899,0.09161 q -0.595461,0 -1.007703,-0.595461 -0.412242,-0.641265 -0.412242,-1.099312 0,-0.458047 0.458047,-0.458047 h 4.076618 v -2.427649 l -0.09161,-4.901103 q 0,-0.412242 0.641266,-0.412242 0.641266,0 1.328337,0.366437 0.68707,0.320633 0.68707,0.870289 z m 17.268399,8.015823 q 2.565063,-1.923798 5.450759,-1.923798 3.8934,0 6.321049,2.885696 2.473454,2.885696 2.473454,7.786799 0,4.901103 -2.931501,8.565479 -2.885696,3.664376 -7.69519,3.664376 -4.809493,0 -7.328751,-2.748282 -2.473454,-2.748282 -2.473454,-7.466166 0,-3.755985 2.290235,-7.649385 0.916094,-1.648969 1.786383,-2.610868 0.916094,-0.961898 1.374141,-0.961898 0.503852,0 0.732875,0.458047 z m 3.389548,18.642512 q 2.24443,0 4.122423,-1.282531 1.923797,-1.328337 3.068915,-3.481158 1.145117,-2.198625 1.145117,-4.855298 0,-3.847594 -1.694774,-6.229439 -1.694774,-2.427649 -4.855298,-2.427649 -3.80179,0 -6.321048,3.389548 -2.519259,3.252134 -2.519259,7.008119 0,3.755985 1.877993,5.817197 1.877993,2.061211 5.175931,2.061211 z"
       id="text2"
       aria-label="to" />
    <path
       id="rect4"
       style="fill:#9c9c9c;fill-opacity:0.478178;stroke-width:0.291655"
       d="m 91.54425,24.044106 h 1.383541 V 80.706524 H 91.54425 Z" />
  </g>
</svg>
rings.svg

This is an example from SVG Element Reference

<svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
  <!-- The influence of the arc flags with which the arc is drawn -->
  <path
    fill="none"
    stroke="red"
    d="M 6,10
           A 6 4 10 1 0 14,10" />

  <path
    fill="none"
    stroke="lime"
    d="M 6,10
           A 6 4 10 1 1 14,10" />

  <path
    fill="none"
    stroke="purple"
    d="M 6,10
           A 6 4 10 0 1 14,10" />

  <path
    fill="none"
    stroke="pink"
    d="M 6,10
           A 6 4 10 0 0 14,10" />
</svg>
insect.svg
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1741309892494" class="icon" viewBox="0 0 1024 1024" version="1.1"
    xmlns="http://www.w3.org/2000/svg" p-id="1466"
    xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200">
    <path d="M940 512H792V412c76.8 0 139-62.2 139-139 0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 34.8-28.2 63-63 63H232c-34.8 0-63-28.2-63-63 0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 76.8 62.2 139 139 139v100H84c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h148v96c0 6.5 0.2 13 0.7 19.3C164.1 728.6 116 796.7 116 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-44.2 23.9-82.9 59.6-103.7 6 17.2 13.6 33.6 22.7 49 24.3 41.5 59 76.2 100.5 100.5S460.5 960 512 960s99.8-13.9 141.3-38.2c41.5-24.3 76.2-59 100.5-100.5 9.1-15.5 16.7-31.9 22.7-49C812.1 793.1 836 831.8 836 876c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-79.3-48.1-147.4-116.7-176.7 0.4-6.4 0.7-12.8 0.7-19.3v-96h148c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM716 680c0 36.8-9.7 72-27.8 102.9-17.7 30.3-43 55.6-73.3 73.3-20.1 11.8-42 20-64.9 24.3V484c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v396.5c-22.9-4.3-44.8-12.5-64.9-24.3-30.3-17.7-55.6-43-73.3-73.3C317.7 752 308 716.8 308 680V412h408v268z" p-id="1467"></path>
    <path d="M304 280h56c4.4 0 8-3.6 8-8 0-28.3 5.9-53.2 17.1-73.5 10.6-19.4 26-34.8 45.4-45.4C450.9 142 475.7 136 504 136h16c28.3 0 53.2 5.9 73.5 17.1 19.4 10.6 34.8 26 45.4 45.4C650 218.9 656 243.7 656 272c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8 0-40-8.8-76.7-25.9-108.1-17.2-31.5-42.5-56.8-74-74C596.7 72.8 560 64 520 64h-16c-40 0-76.7 8.8-108.1 25.9-31.5 17.2-56.8 42.5-74 74C304.8 195.3 296 232 296 272c0 4.4 3.6 8 8 8z" p-id="1468"></path>
</svg>

The rings.svg and insect.svg files serve as controls to ensure that no unintended side effects occur in unrelated cases.

Result:

Previous Now
image image

Performance Comparison:

I conducted a rough performance comparison on my device (not a strict benchmark):

Notes Previous Now Difference
to.svg path-1 No arc 414us 942ns 409us 583ns -1.3%
to.svg path-2 Fixed issue
to.svg path-3 No arc 1ms 989us 1ms 949us -2.0%
to.svg path-4 No arc 360us 278ns 312us 916ns -13.1%
rings.svg path-1 Arc only 433us 233ns 283us 125ns -34.6%
rings.svg path-2 Arc only 452us 732ns 322us 108ns -28.9%
rings.svg path-3 Arc only 255us 948ns 224us 750ns -12.2%
rings.svg path-4 Arc only 249us 32ns 230us 41ns -7.6%
insect.svg path-1 No arc 6ms 449us 6ms 370us -1.2%
insect.svg path-2 No arc 1ms 856us 1ms 887us +1.7%

@jkjkil4 jkjkil4 changed the title Fix path arc handling for SVGMobject when a matrix transform is present in the SVG Fix arc path handling for SVGMobject when a matrix transform is present in the SVG Mar 7, 2025
@jkjkil4 jkjkil4 changed the title Fix arc path handling for SVGMobject when a matrix transform is present in the SVG Fix arc handling for VMobjectFromSVGPath when a matrix transform is present in the SVG Mar 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant