meerk40t / svgelements

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

PPI option in parser not working #149

Closed itsAlexNoir closed 2 years ago

itsAlexNoir commented 2 years ago

Hi everyone,

I have just realised that when parsing a SVG file of the form: <?xml version="1.0" encoding="UTF-8"?>

I understand that parse function automatically converts pt units to px (pixel units) with ppi=96, so a scaling of 96 / 72 (1 inch is 72 pt)=1.333 is applied. But when I am trying to change ppi to other value, say ppi=300, but i still get the same scaling. This is normal behaviour, or it is not working properly?

Thanks, Alex

tatarize commented 2 years ago

It actually depends on the internal units. PPI is the reflection pixels to inches. It's a translation of the px units to inches which is an distance unit. In this case your width is being given in pt which is directly 1.3333 exact to a px. And the only object you have it in is a path which is natively in units px. This gives no relationship to discrete units at all. Your pixels are your native units and your height and width are in points (1.3333 pixels). Whether these pixels are 300 to an inch or each one is a mile across has no bearing on your results. The objects are still exactly that many pixels across.

The only time this can have an effect is if our units are given in physical lengths and thus the ratio of physical units to pixels becomes paramount. In the example, they are free floating and not relevant. Thus the value of PPI does nothing to the result. If you want to scale it up or down you can pass in a transformation to the parser, but the pixels-per-inch isn't a translation but information that allows for the conversion of units.

tatarize commented 2 years ago

Concrete Example. The MeerK40t parsing for svg files needs to translate the files into actual sizes and uses the following parse:

        ppi = 96.0
        scale_factor = 1000.0 / ppi
        source = pathname
        if pathname.lower().endswith("svgz"):
            source = gzip.open(pathname, "rb")
        svg = SVG.parse(
            source=source,
            reify=True,
            width="%fmm" % bed_dim.bed_width,
            height="%fmm" % bed_dim.bed_height,
            ppi=ppi,
            color="none",
            transform="scale(%f)" % scale_factor,
        )

Here we see that the parse parses the source, gives the length and height as the actual size of the laser bed in mm since this is needed for elements where % values are used. As percent values are percent of the actual view we have, usually it's the box the svg is stored within but it could be anything and you can use any valid length type object to do that. It gives it the PPI incase the document uses physical length values and needs to convert that into pixels. And then it does a transform which converts the pixels from px to inches, which in the native unit of the lasercutter's 1 mil steppers is 1000 / 96.0 (or 1000 / ppi) for other ppi values. That's at least covers most of the moving parts. (The "none" value for color is for defaultColor which is a weird thing for a color inherited by the SVG file, from outside the svg file).

Sometimes getting things the correct size doesn't just involve giving it PPI since that gives the ratio of lengths to pixels, sometimes you need the reverse and to move from pixels -> lengths which involves scaling the entire document by the ratio of your intended native units by the SVG native units (aka pixels).

itsAlexNoir commented 2 years ago

ok. Thanks for the anser and the clarification. So I guess the best option is to use a transform matrix as input parameter.

tatarize commented 2 years ago

If you want it to always scale up, then yes. PPI is just information about how real length units relate to pixels. So PPI does not cause any scaling in files that are in pixels. And you'd want to scale the image up to native units you'd need to use that pixel ratio (PPI) to get them into inches and scale that to your desired native units.