mathandy / svgpathtools

A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
MIT License
548 stars 138 forks source link

Matrix transforming a path changes its stroke width #160

Closed ralcant closed 2 years ago

ralcant commented 2 years ago

Problem

I'm working with Paths that each have a transform matrix defined in their attribute. I want to avoid having transforms because I want to be able to compare the start and endpoints from different Paths. For this purpose, I'm using the transform(curve, tf) method from svgpathools.path to go through every path and update them with their respective transform matrix, and deleting this transform from its attribute. The problem is that when I try to save the new paths and attributes to disk, the output has a different stroke width from the original. Original Changed

Unsure why this happens, mainly because I never change the stroke-width parameter in the original attributes

Expected behavior

The output saved to disk looks exactly the same as the original

Reproducible code

Minimal code can be found in this colab notebook

The most important part is this for loop:

from svgpathtools.parser import parse_transform
from svgpathtools.path import transform as path_transform

paths, attributes, svg_attributes = # hardcoded values or result from calling svg2paths2

new_paths = []
new_attributes = []

for i, (path, attribute) in enumerate(zip(paths, attributes)):
  transform_string = attributes[i].pop('transform', '')
  new_path = path_transform(path, parse_transform(transform_string))
  attributes[i]['d'] = new_path.d() #to make it consistent
  new_paths.append(new_path)
  new_attributes.append(attributes[i])  

# Looks the same as original but with different stroke width
wsvg(new_paths, attributes=new_attributes, svg_attributes=svg_attributes, filename="./changed.svg")

Use case where this is annoying

My use case is that I'm analyzing floorplans and when I try to do the method I described above, the stroke widths of everything gets incredibly large and the resulting SVG becomes nonsensical. It'd be great if I could transform all of the Paths while maintaining the original shape. The only solution I came up with so far was to divide all current stroke widths by a common factor. Unfortunately, I don't know a way to find a good common factor without just trial and error. This makes the solution unfeasible because I need to analyze hundreds of SVGs.

Any suggestions are welcome. Thank you in advance!

mathandy commented 2 years ago

Hi @ralcant, thanks for the nice report about this. Let me explain what's happening. The transform attribute in the original thick black curve is (rewritten in matrix notation) is

1.5  0  0
 0  1.5 0

Including a transform in an svg-path means that the path is drawn using a different coordinate system, including the thickness. This transform is a scalar transform that simply makes everything 50% bigger. Because it's a scalar transform, you can reproduce the same look (in the transformed "changed.svg" by using a stroke_width of 3 instead of 2.

For a non-scalar transform it's unfortunately not possibly to include this information, the reason being the x- and y-axes are scaled by different amounts. E.g. if I transform the rectangle in your svg by the transform

0.5  0  0
 0  2.0 0

It looks like image

Notice how everything is stretched in the vertical direction (including the stroke_width) and smooshed in the x-direction. Moreover, notice what happens when this same transform is used on a diagonal line (green is before transform, red is after). image

I.e. for any transform

a c e
b d f

If a == d, you can do what you want by changing the stroke width to be new_stroke_width = a*old_stroke_width, but for other cases, it's not always possible to achieve the same result without some sort of transform attribute.