svglib icon indicating copy to clipboard operation
svglib copied to clipboard

Low resolution PNG output

Open fourjr opened this issue 4 years ago • 19 comments

This is the SVG file that I am trying to convert.

Is there a way to make the output png a higher resolution? The rendered SVG on Github has a way higher resolution than the one outputted by the lib.

72 DPI (default) image

1024 DPI image

Python code:

drawing = svg2rlg('dump/emoji.svg')
with open('dump/emoji.png', 'wb') as f:
    renderPM.drawToFile(drawing, f, fmt="PNG", dpi=1024)

fourjr avatar Jul 13 '19 11:07 fourjr

Good question. This is more a ReportLab question than a svglib one, but by reading RL code, looks like you should be able to play with the drawing.renderScale attribute which looks like a scale factor. Would be nice if you can report your experiment results, maybe we can add a line or two in our README.

claudep avatar Jul 13 '19 16:07 claudep

I managed to successfully resize properly with this answer. However, upon resizing, I realised that the edges were extremely jagged. emoji

fourjr avatar Jul 14 '19 05:07 fourjr

This is the SVG file that I am trying to convert.

Is there a way to make the output png a higher resolution? The rendered SVG on Github has a way higher resolution than the one outputted by the lib.

72 DPI (default) image

1024 DPI image

Python code:

drawing = svg2rlg('dump/emoji.svg')
with open('dump/emoji.png', 'wb') as f:
    renderPM.drawToFile(drawing, f, fmt="PNG", dpi=1024)

Having the same issue with a different svg image. drawToFile's dpi argument does not work, it just modifies the size of the output file but not its resolution. Adding this comment just in case a solution or follow up is added.

tonioluna avatar Jul 12 '20 00:07 tonioluna

I would also first scale it like you did. If the result looks jagged that could be because of its path definition, but I see it does contain cubic Bézier curves like in this pretty similar example here: https://www.sitepoint.com/closer-look-svg-path-data/ Maybe you want to compare with that? The test suite perfectly runs most flags from Wikipedia and they should be full of such curves.

Else I hope @replabrobin can shere any insight about any renderPM limitations?

deeplook avatar Jul 14 '20 05:07 deeplook

without the actual svg it's hard to answer any question like this. If I squash the large image the flats disappear, so are the curves bezier? I think the assertion in renderPM is 1pixel == 1pt for PDF compatibility. The dpi value is not used internally in the drawing code, but is passed to PIL via im.save.

replabrobin avatar Jul 14 '20 07:07 replabrobin

@replabrobin The SVG file I used is liked above in a gist.

fourjr avatar Jul 14 '20 07:07 fourjr

It's not working for me; I just see the loading gif for a few seconds and then "Sorry, something went wrong. Reload?"

replabrobin avatar Jul 14 '20 08:07 replabrobin

Does this work? @replabrobin https://gist.github.com/fourjr/ab90768916faa5d92cb26c106d598821

fourjr avatar Jul 14 '20 09:07 fourjr

I'm afraid not, as before just the placeholder circling and then something went wrong. I am using chromium 83 on arch linux latest

replabrobin avatar Jul 14 '20 10:07 replabrobin

<?xml version="1.0" encoding="UTF-8" standalone="no"?><svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 47.5 47.5" style="enable-background:new 0 0 47.5 47.5;" xml:space="preserve" version="1.1" id="svg2"><metadata id="metadata8"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/></cc:Work></rdf:RDF></metadata><defs id="defs6"><clipPath id="clipPath16" clipPathUnits="userSpaceOnUse"><path id="path18" d="M 0,38 38,38 38,0 0,0 0,38 Z"/></clipPath></defs><g transform="matrix(1.25,0,0,-1.25,0,47.5)" id="g10"><g id="g12"><g clip-path="url(#clipPath16)" id="g14"><g transform="translate(23.8555,36.2422)" id="g20"><path id="path22" style="fill:#5dadec;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 -14.98,-6.266 12.537,-9.733 c 2.632,-2.224 6.377,-2.937 9.769,-1.518 4.826,2.018 7.096,7.577 5.072,12.413 C 10.377,-0.266 4.824,2.019 0,0"/></g><g transform="translate(13.8931,18.3184)" id="g24"><path id="path26" style="fill:#5dadec;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 -10.843,8.398 -1.913,-13.246 c -0.534,-2.855 0.502,-5.902 2.958,-7.802 3.493,-2.705 8.518,-2.067 11.224,1.425 C 4.131,-7.732 3.493,-2.707 0,0"/></g><g transform="translate(29.2324,11.3027)" id="g28"><path id="path30" style="fill:#5dadec;fill-opacity:1;fill-rule:nonzero;stroke:none" d="m 0,0 -8.156,4.689 -0.033,-9.222 c -0.088,-2 0.904,-3.981 2.75,-5.041 2.63,-1.512 5.976,-0.594 7.478,2.051 C 3.539,-4.879 2.629,-1.51 0,0"/></g></g></g></g></svg>

deeplook avatar Jul 14 '20 11:07 deeplook

Thanks I'll take a look later

replabrobin avatar Jul 14 '20 11:07 replabrobin

Hi I had a look at this problem and it seems to me that the internal libart code is scaling after discretization instead of scaling before. I'll see if there's a better way to do curves in _renderPM.

replabrobin avatar Jul 14 '20 15:07 replabrobin

OK I looked in the _renderPM code and it seems that our smoothness is dictated by the parameter 0.25 passed into

vpath = art_bez_path_to_vec(self->path, 0.25)

which converts beziers into vector paths. Everywhere that is used it is followed eventually in _renderPM.c by a call to art_vpath_affine_transform(vpath, self->ctm) to scale the vpath by the current transformation matrix.

I wonder if that is the source of the problem we are scaling after vectorizing; we should be able to scale all the control points before scaling. It seems to make sense for scales > 1, but perhaps not when scaling < 1.

replabrobin avatar Jul 14 '20 16:07 replabrobin

@replabrobin What is the state of this? I find the very same effect as shown below (recorded inside a Jupyter notebook).

import io

import requests
from ipywidgets import interact
from PIL import Image
from reportlab.graphics import renderPM, renderPDF
from svglib.svglib import svg2rlg

svg = requests.get("https://simpleicons.org/icons/apple.svg").text
svg_file = io.StringIO(svg)

@interact(scale=(1, 20, 1))
def render(scale=1):
    drawing = svg2rlg(svg_file)
    drawing.scale(scale, scale)
    drawing.width *= scale
    drawing.height *= scale

    f = io.BytesIO()
    renderPM.drawToFile(drawing, f, fmt="PNG")
    f.seek(0)
    im = Image.open(f)
    return im

im = render()

apple_renderpm

import base64
import io

import requests
from IPython.display import Markdown
from ipywidgets import interact
from reportlab.graphics import renderPDF
from svglib.svglib import svg2rlg

svg = requests.get("https://simpleicons.org/icons/apple.svg").text
svg_file = io.StringIO(svg)

@interact(scale=(1, 20, 1))
def render(scale=1):
    drawing = svg2rlg(svg_file)
    drawing.scale(scale, scale)
    drawing.width *= scale
    drawing.height *= scale
    pdf_content = renderPDF.drawToString(drawing)
    base64_pdf = base64.b64encode(pdf_content).decode("utf-8")
    pdf_display = (
        f'<embed src="data:application/pdf;base64,{base64_pdf}" '
        'width="400" height="400" type="application/pdf">'
    )
    return Markdown(pdf_display)

md = render()

apple_renderpdf

deeplook avatar Jul 02 '21 15:07 deeplook

The fix above doesn't seem to apply to stroke

drawing.scale(scale, scale)

justingolden21 avatar Apr 06 '22 01:04 justingolden21

Seems to just be a limitation of svglib: https://stackoverflow.com/questions/68242812/in-python-convert-svg-to-png-while-resizing-and-increasing-quality

justingolden21 avatar Apr 06 '22 01:04 justingolden21

GOT IT FIGURED OUT

You can just convert from SVG to PDF then from PDF to PNG:

from svglib.svglib import svg2rlg
from reportlab.graphics import renderPDF
from pdf2image import convert_from_path

drawing = svg2rlg('card.svg')
renderPDF.drawToFile(drawing, 'card.pdf')

pages = convert_from_path('card.pdf', 600)
pages[0].save('card.png', 'PNG')

All other implementations online either require Pillow (which is a massive pain, struggled with that for several hours during several days a while ago) or CairoSVG (which is near impossible to get working on Windows)

justingolden21 avatar Apr 06 '22 01:04 justingolden21

I found this issue when trying to convert SVG inputs to PNG/PIL, without relying on cairosvg, pyvips or pdf2image as those need python-external dependencies like cairo, poppler or libvips that can be a pain to install in windows. I found pymupdf that at least comes with prebuild wheels for windows/linux/mac so nothing more than

pip install pymupdf svglib

is needed to get this running

import fitz
from svglib import svglib
from reportlab.graphics import renderPDF

# Convert svg to pdf in memory with svglib+reportlab
# directly rendering to png does not support transparency nor scaling
drawing = svglib.svg2rlg(path="input.svg")
pdf = renderPDF.drawToString(drawing)

# Open pdf with fitz (pyMuPdf) to convert to PNG
doc = fitz.Document(stream=pdf)
pix = doc.load_page(0).get_pixmap(alpha=True, dpi=300)
pix.save("output.png")

This supports transparent backgrounds without corners and does not have the issue pointed out above. I hope this helps someone who stumbles by.

Cheers!

M4a1x avatar Oct 22 '22 15:10 M4a1x

To add a bit to the response by @M4a1x, if you don't want to save to a file, you can just do:

import fitz
from svglib import svglib
from reportlab.graphics import renderPDF

# Convert svg to pdf in memory with svglib+reportlab
# directly rendering to png does not support transparency nor scaling
drawing = svglib.svg2rlg(path="input.svg")
pdf = renderPDF.drawToString(drawing)

# Open pdf with fitz (pyMuPdf) to convert to PNG
doc = fitz.Document(stream=pdf)
pix = doc.load_page(0).get_pixmap(alpha=True, dpi=300)
png_bytes = pix.tobytes() # defaults to PNG <======= return bytes that you can do something with like upload to external object storage

Nav31 avatar Feb 23 '23 20:02 Nav31