meerk40t / svgelements

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

Wrong Path's bbox after rotation #104

Closed ghost closed 3 years ago

ghost commented 3 years ago

Calling the bbox method on the following path after a rotation returns a wrong bounding box (wider than the path boundaries). This happens specifically when the rotation is not on one of four axis (ie 0, 90, 135 & 180 return the correct path bbox):


import svgwrite as s
import svgelements as se

d="M0 36c0 128.082 96 214 251 214c153.639 0 293 -37 293 -264c0 -220 -158.05 -325.976 -254 -391c-121 -82 -200.248 -117.634 -339 -171c-5.93907 -2.28437 -11.3166 -3.27496 -15.9795 -3.27496c-11.9348 0 -19.1871 6.48977 -19.1871 14.3854 c0 7.04844 5.77949 15.2172 19.1666 20.8896c118 50 225 86 316 200c91 113 125 205.913 125 350c0 135 -33 224 -145 224c-46.615 0 -77.452 -12.593 -112 -44c-8.09234 -7.35667 -11.3121 -13.0296 -11.3121 -17.4022c0 -13.4585 30.5027 -14.5978 43.3121 -14.5978 c69 0 123 -64.8867 123 -136c0 -75 -48 -132 -139 -132c-79.1582 0 -135 74 -135 150zM572 128c0 35 29 64 64 64s63 -29 63 -64s-28 -63 -63 -63s-64 28 -64 63zM572 -131c0 35 29 64 64 64s63 -29 63 -64s-28 -63 -63 -63s-64 28 -64 63z"

def render():
    D = s.drawing.Drawing(filename="/tmp/asd.svg", size=(1000, 1000), debug=True)
    x=se.Path(d) * "scale(.1 -.1) rotate(45deg)"
    xmin,ymin,xmax,ymax=x.bbox()
    D.add(s.path.Path(d=x.d()))
    D.add(s.path.Path(d=se.Rect(xmin,ymin,xmax-xmin,ymax-ymin).d(), fill=s.utils.rgb(100,0,0,"%"),fill_opacity=.5))
    D.save(pretty=True)

render()

results in

Screenshot_2021-04-17_09-24-07

Also is it possible to assign rotation axis?

tatarize commented 3 years ago

The actual rotation of an object can actually change the bbox. If you have a normal square and you rotate it by 45°, you have a bounding box larger than the original square. The rotations you have are based on some point. So if you rotated it around a different corner by 90 degrees you would end up things differently with regard to position. Or rotating it around the center of the point.

tatarize commented 3 years ago

Yeah, I checked by loading it up in meerk40t and applying the rotation moving it around in a circle. And yes, the bbox is wrong in that case. You'd have to reify the path to get the correct bbox. abs(path).bbox() will give you the correct results. Which will work as a good work around while I look into why it's wrong.

tatarize commented 3 years ago

https://youtu.be/UbpBVRlz-Dw

You can see when I reify the image it gets the correct bounding box. I swear there's a reason it does this but I'm not sure. I think it calculates the bounding box and then rotates the four corners of that bounding box. I'll get a better answer fairly soon.

ghost commented 3 years ago

And the second question: how could I rotate about a certain point?

tatarize commented 3 years ago

There's two optional parameter of x an y in any translation event. "rotate(0.3turn)" will, for example, perform a turn of 30% around, however implied in that is that the turn is relative to 0,0. "rotate(108deg, 30, 30)" is a rotation around point 30,30. Mathematically this will be the same as translating by -30,-30, rotating around the origin, then translating back +30,+30. Which is how the affine transformation is performed. SVG allows that kind of shorthand.

tatarize commented 3 years ago

I ended up correcting 104 by simply performing the transformation universally before the bbox. The issue was conceptual since you cannot actually rotate a value and not change the underlying geometry. Rotating the corners of the original bounds is not sufficient. https://github.com/meerk40t/svgelements/pull/106/commits/1a68c1dd5d147b8511394dab2c9e7f4a24d43835