134

How do I convert an svg to png, in Python? I am storing the svg in an instance of StringIO. Should I use the pyCairo library? How do I write that code?

jsbueno
  • 86,446
  • 9
  • 131
  • 182
ram1
  • 5,917
  • 8
  • 39
  • 44
  • 3
    Possibly a duplicate of http://stackoverflow.com/questions/2932408/server-side-svg-to-png-or-some-other-image-format-in-python – Optimal Cynic Jul 05 '11 at 22:06
  • 4
    That thread left the problem unsolved. The accepted answer came from the asker who was sharing his failed code attempt. The other answer suggested ImageMagick but a commenter said ImageMagick does "a horrible job of interpreting SVG." I don't want my pngs to look horrible so I'm re-asking the question. – ram1 Jul 05 '11 at 22:21
  • 1
    Try http://cairographics.org/cookbook/librsvgpython/ – Optimal Cynic Jul 05 '11 at 22:26
  • The examples in that link are specific to Win32. I'm running linux. – ram1 Jul 05 '11 at 22:49
  • Take a look at [this](http://guillaume.segu.in/blog/code/43/svg-to-png/) blog post, it looks like it might be what you need. – giodamelio Jul 06 '11 at 09:24
  • Simple SVGs: https://github.com/aslpavel/svgrasterize.py – doublemax Mar 06 '21 at 19:48

14 Answers14

121

Here is what I did using cairosvg:

from cairosvg import svg2png

svg_code = """
    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
        <circle cx="12" cy="12" r="10"/>
        <line x1="12" y1="8" x2="12" y2="12"/>
        <line x1="12" y1="16" x2="12" y2="16"/>
    </svg>
"""

svg2png(bytestring=svg_code,write_to='output.png')

And it works like a charm!

See more: cairosvg document

duan
  • 7,791
  • 3
  • 45
  • 65
JWL
  • 12,589
  • 7
  • 54
  • 60
  • 8
    Hi. Do you know how can i do the same but without writing to a file? I need to push png content to the browser from a webserver, so that way the user can download the image. Saving the png file is not a valid option in our project, that's why I need it that way. Thanks – estemendoza Feb 25 '13 at 18:11
  • 5
    I've been doing this myself. It basically depends on what tools or frameworks u have at hand when handling your web requests, but no matter what it is, the basic idea is that `svg2png` takes in a `stream` object in the `write_to` parameter, and this can either be your HTTP Response object (which in most frameworks is a file-like object) or some other stream, which you then serve to the browser using the `Content-Disposition` header. see here: http://stackoverflow.com/questions/1012437/uses-of-content-disposition-in-an-http-response-header – JWL Feb 26 '13 at 06:59
  • 4
    For those who experiences the issues with that code, as I was: 1). `bytestring` accepts bytes, so convert string first with `bytestring=bytes(svg,'UTF-8')` 2). file mode should be binary, so `open('output.png','wb')` – Serj Zaharchenko Oct 19 '14 at 13:28
  • The equivalent of @SerjZaharchenko's answer for Python 2.x is `bytestring=svg.encode('utf-8')` – supervacuo Feb 26 '15 at 14:55
  • If you want to set the width and height with `cairosvg`, the SVG must have a [`viewbox`](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox). You can add one using [`scour`](https://github.com/scour-project/scour#usage). – z0r Aug 12 '16 at 00:11
  • 3
    cairosvg supports only Python 3.4+. They have dropped Python 2 support – Marlon Abeykoon Sep 09 '16 at 10:32
  • Note that for python 2.7 you need to `pip install cairosvg==1.0.22` , otherwise you'll have installation issues. – Amir Rosenfeld Aug 22 '17 at 14:01
  • 1
    There wasn't a `svg2png` for me, I had to use `cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png')`. – tobltobs Sep 12 '18 at 08:09
  • 1
    @estemendoza if you pass `write_to=None` it returns a bytestring. – mondaugen May 20 '20 at 19:34
  • 1
    @estemendoza Yes; here's how to do that: `PIL.Image.open(io.BytesIO(cairosvg.svg2png(url=filename, write_to=None)))` – JamesTheAwesomeDude May 23 '21 at 20:37
68

The answer is "pyrsvg" - a Python binding for librsvg.

There is an Ubuntu python-rsvg package providing it. Searching Google for its name is poor because its source code seems to be contained inside the "gnome-python-desktop" Gnome project GIT repository.

I made a minimalist "hello world" that renders SVG to a cairo surface and writes it to disk:

import cairo
import rsvg

img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 640,480)

ctx = cairo.Context(img)

## handle = rsvg.Handle(<svg filename>)
# or, for in memory SVG data:
handle= rsvg.Handle(None, str(<svg data>))

handle.render_cairo(ctx)

img.write_to_png("svg.png")

Update: as of 2014 the needed package for Fedora Linux distribution is: gnome-python2-rsvg. The above snippet listing still works as-is.

jsbueno
  • 86,446
  • 9
  • 131
  • 182
  • 1
    Great, works nicely. But is there a way to let `cairo` determine the HEIGHT and WIDTH of the picture on its own? I've looked into the `*.svg` file, to extract the HEIGHT and WIDTH from there, but it is both set to `100%`. Of course, I can look into the properties of the picture, but since this is only one step in image processing this is not what I want. – quapka Jun 09 '14 at 09:19
  • 1
    If the "width" and "height" of your files are set to 100%, there is no magic Cairo or rsvg can do to guess the size: such SVG files were left size independent by the creator software(/person). The surrounding HTML code to import the SVG file would supply the physical size. However, the "Handle" object of rsvg do have a `.get_dimension_data()` method that worked for my example file (a well behaved SVG) - give it a try. – jsbueno Jun 12 '14 at 05:50
  • 1
    as of 2014, for ubuntu, you can use: apt-get install python-rsvg – t1m0 Sep 23 '14 at 14:11
  • 1
    Is there a quick command for adding a white background to the image if its current background is transparent? – fiatjaf Jan 07 '15 at 03:23
  • @jsbueno I use Windows 8.1 and python 2.7.11 How can I install cairo and rsvg and make it work. I was struggling to make this work. BTW +1 for your detailed explanation. – Marlon Abeykoon Sep 09 '16 at 10:24
  • @MarlonAbeykoon: I think it is better you ask this as a separate question on another Stack Exchange forum - (I actually have no idea). Likely http://superuser.com is your best choice. Just put a link to this question on your question there to give some more context to whoever knows how to answer this. – jsbueno Sep 09 '16 at 13:50
  • I used bit old version of wand to do it. Since latest version had some issues – Marlon Abeykoon Sep 10 '16 at 10:33
  • The pyrsvg link is dead. – Matthias Urlichs Apr 30 '18 at 14:08
46

Install Inkscape and call it as command line:

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -e ${dest_png}

You can also snap specific rectangular area only using parameter -j, e.g. co-ordinate "0:125:451:217"

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -j -a ${coordinates} -e ${dest_png}

If you want to show only one object in the SVG file, you can specify the parameter -i with the object id that you have setup in the SVG. It hides everything else.

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
bignose
  • 27,414
  • 13
  • 72
  • 104
blj
  • 3,044
  • 2
  • 14
  • 6
  • 3
    +1 because this is also extremely handy for shell scripting. See http://inkscape.org/doc/inkscape-man.html for full docs on Inkscape's command line. – Prime Jul 06 '13 at 23:24
  • Thanks, this was the easiest way I found to do this. On Windows, to make it so that you don't have to type in the full path to Inkscape every time, you can [add it to your Path in Environmental Variables](http://www.computerhope.com/issues/ch000549.htm). – Alex S Nov 12 '16 at 00:40
  • 6
    This is not an answer. It's a workaround. OP asked for a Python solution. – lamino Oct 29 '18 at 23:02
35

I'm using Wand-py (an implementation of the Wand wrapper around ImageMagick) to import some pretty advanced SVGs and so far have seen great results! This is all the code it takes:

    with wand.image.Image( blob=svg_file.read(), format="svg" ) as image:
        png_image = image.make_blob("png")

I just discovered this today, and felt like it was worth sharing for anyone else who might straggle across this answer as it's been a while since most of these questions were answered.

NOTE: Technically in testing I discovered you don't even actually have to pass in the format parameter for ImageMagick, so with wand.image.Image( blob=svg_file.read() ) as image: was all that was really needed.

EDIT: From an attempted edit by qris, here's some helpful code that lets you use ImageMagick with an SVG that has a transparent background:

from wand.api import library
import wand.color
import wand.image

with wand.image.Image() as image:
    with wand.color.Color('transparent') as background_color:
        library.MagickSetBackgroundColor(image.wand, 
                                         background_color.resource) 
    image.read(blob=svg_file.read(), format="svg")
    png_image = image.make_blob("png32")

with open(output_filename, "wb") as out:
    out.write(png_image)
streetlogics
  • 4,531
  • 31
  • 30
  • 3
    Wand worked a **lot** better than Cairo for my PNGs. – qris May 09 '14 at 16:41
  • Thanks @qris - added your transparent background SVG rendering code to my answer as well. – streetlogics May 12 '14 at 14:40
  • this looks nice, but where do you put the svg's filename?? – adrienlucca.net Jul 15 '16 at 11:49
  • 4
    I get the error `image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined` – adrienlucca.net Jul 15 '16 at 11:56
  • 4
    `svg_file` is assumed to be a "file" object in this example, setting `svg_file` would look something like: `svg_file = File.open(file_name, "r")` – streetlogics Jul 15 '16 at 18:04
  • 2
    Thanks, the `cairo` and `rsvg` 'accepted' method didn't work for my PDF. `pip install wand` and your snippet did the trick ;) – nmz787 Dec 26 '16 at 06:27
  • 6
    If you have an svg `str` then you first need to encode into binary like this: `svg_blob = svg_str.encode('utf-8')`. Now you can use the method above by replacing `blob=svg_file.read()` with `blob=svg_blob`. – asherbret Sep 30 '18 at 10:18
  • This still didn't export correctly with fonts... I know that's not helpful... so specifically, I have an svg that utilizes the font-family property and loads all of the fonts from external stylesheets generated by font squirrel . com ... – Shmack Oct 25 '20 at 17:13
  • FYI, i had trouble directly running the above code. First thing is that i had to install `potrace` from bioconda. Second, i actually end up using this `svg_to_png` function from https://www.programcreek.com/python/?code=yaqwsx%2FPcbDraw%2FPcbDraw-master%2Fpcbdraw%2Fpcbdraw.py# -- i hope it will be helpful for someone else too – Forrest1988 Jun 02 '21 at 16:16
  • With Wand 0.6.6 in MacOs, png_image was always None, make it works by adding background in constructor too: `image.read(blob=svg_file.read(), background=background_color), format="svg")` – Marius Jun 22 '21 at 09:34
  • FYI, this solution requires both Imagemagick and Inkscape to be installed. – Brōtsyorfuzthrāx Oct 14 '21 at 03:14
21

I did not find any of the answers satisfactory. All the mentioned libraries have some problem or the other like Cairo dropping support for python 3.6 (they dropped Python 2 support some 3 years ago!). Also, installing the mentioned libraries on the Mac was a pain.

Finally, I found the best solution was svglib + reportlab. Both installed without a hitch using pip and first call to convert from svg to png worked beautifully! Very happy with the solution.

Just 2 commands do the trick:

from svglib.svglib import svg2rlg
from reportlab.graphics import renderPM
drawing = svg2rlg("my.svg")
renderPM.drawToFile(drawing, "my.png", fmt="PNG")

Are there any limitations with these I should be aware of?

Rainald62
  • 565
  • 9
  • 16
Sarang
  • 1,493
  • 15
  • 18
13

Try this: http://cairosvg.org/

The site says:

CairoSVG is written in pure python and only depends on Pycairo. It is known to work on Python 2.6 and 2.7.

Update November 25, 2016:

2.0.0 is a new major version, its changelog includes:

  • Drop Python 2 support
Daniel F
  • 12,968
  • 10
  • 84
  • 107
user732592
  • 237
  • 1
  • 6
  • There is two problem with this, unfortunately. First, it doesn't handle the ``. Second, it doesn't take the -d (DPI) option. – Ray Nov 20 '12 at 09:58
  • 2
    @Ray , please send bug reports / feature requests on [the CairoSVG tracker](https://github.com/Kozea/CairoSVG/issues)! – Simon Sapin Jan 22 '13 at 12:49
  • @Simon, Can you do it please? I'm too busy and I will be in the next 1-2 month. – Ray Jan 22 '13 at 16:35
  • 2
    @Ray, actually the -d / --dpi option has been there for a while now, and I’m told that support for was added a few weeks back in the git version. – Simon Sapin Jan 22 '13 at 22:13
  • @Simon, nice! Thank you! :) – Ray Jan 23 '13 at 12:52
  • I had some problems with SVGs that use opacity. In my case Wand-py showed to be a better solution. – ruhanbidart May 08 '15 at 02:35
7

Another solution I've just found here How to render a scaled SVG to a QImage?

from PySide.QtSvg import *
from PySide.QtGui import *


def convertSvgToPng(svgFilepath,pngFilepath,width):
    r=QSvgRenderer(svgFilepath)
    height=r.defaultSize().height()*width/r.defaultSize().width()
    i=QImage(width,height,QImage.Format_ARGB32)
    p=QPainter(i)
    r.render(p)
    i.save(pngFilepath)
    p.end()

PySide is easily installed from a binary package in Windows (and I use it for other things so is easy for me).

However, I noticed a few problems when converting country flags from Wikimedia, so perhaps not the most robust svg parser/renderer.

Community
  • 1
  • 1
Adam Kerz
  • 879
  • 1
  • 6
  • 13
5

A little extension on the answer of jsbueno:

#!/usr/bin/env python

import cairo
import rsvg
from xml.dom import minidom


def convert_svg_to_png(svg_file, output_file):
    # Get the svg files content
    with open(svg_file) as f:
        svg_data = f.read()

    # Get the width / height inside of the SVG
    doc = minidom.parse(svg_file)
    width = int([path.getAttribute('width') for path
                 in doc.getElementsByTagName('svg')][0])
    height = int([path.getAttribute('height') for path
                  in doc.getElementsByTagName('svg')][0])
    doc.unlink()

    # create the png
    img = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
    ctx = cairo.Context(img)
    handler = rsvg.Handle(None, str(svg_data))
    handler.render_cairo(ctx)
    img.write_to_png(output_file)

if __name__ == '__main__':
    from argparse import ArgumentParser

    parser = ArgumentParser()

    parser.add_argument("-f", "--file", dest="svg_file",
                        help="SVG input file", metavar="FILE")
    parser.add_argument("-o", "--output", dest="output", default="svg.png",
                        help="PNG output file", metavar="FILE")
    args = parser.parse_args()

    convert_svg_to_png(args.svg_file, args.output)
Martin Thoma
  • 108,021
  • 142
  • 552
  • 849
  • I used the svg width and height extraction. I'm not sure about the svg standard but in some of my svg files the width or height were followed by a non numeric string such as 'mm' or 'px' (ex: '250mm'). The int('250mm') throws an exception and I had to make some additional tweaks. – WigglyWorld Feb 15 '15 at 11:12
5

Here is a another solution without using rsvg(which is currently not available for windows).Only install cairosvg using pip install CairoSVG

svg2png.py

from cairosvg import svg2png
svg_code = open("input.svg", 'rt').read()
svg2png(bytestring=svg_code,write_to='output.png')
  • 2
    Another simpler way: `cairosvg.svg2png(url="/path/to/input.svg", write_to="/tmp/output.png")`. As shared in Cairosvg's official documentation: https://cairosvg.org/documentation/ – Amit Dash Jun 04 '21 at 07:05
3

Here is an approach where Inkscape is called by Python.

Note that it suppresses certain crufty output that Inkscape writes to the console (specifically, stderr and stdout) during normal error-free operation. The output is captured in two string variables, out and err.

import subprocess               # May want to use subprocess32 instead

cmd_list = [ '/full/path/to/inkscape', '-z', 
             '--export-png', '/path/to/output.png',
             '--export-width', 100,
             '--export-height', 100,
             '/path/to/input.svg' ]

# Invoke the command.  Divert output that normally goes to stdout or stderr.
p = subprocess.Popen( cmd_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE )

# Below, < out > and < err > are strings or < None >, derived from stdout and stderr.
out, err = p.communicate()      # Waits for process to terminate

# Maybe do something with stdout output that is in < out >
# Maybe do something with stderr output that is in < err >

if p.returncode:
    raise Exception( 'Inkscape error: ' + (err or '?')  )

For example, when running a particular job on my Mac OS system, out ended up being:

Background RRGGBBAA: ffffff00
Area 0:0:339:339 exported to 100 x 100 pixels (72.4584 dpi)
Bitmap saved as: /path/to/output.png

(The input svg file had a size of 339 by 339 pixels.)

Iron Pillow
  • 2,082
  • 4
  • 17
  • 29
  • It's not end-to-end Python if you rely on Inkscape. – Joel Jul 31 '19 at 14:51
  • 2
    @Joel: First line has been modified to overcome your objection. But of course, even a "pure" Python solution relies on elements outside the core language, and is ultimately run with machine language, so perhaps there is no such thing as end-to-end anything! – Iron Pillow Aug 01 '19 at 17:54
3

SVG scaling and PNG rendering

Using pycairo and librsvg I was able to achieve SVG scaling and rendering to a bitmap. Assuming your SVG is not exactly 256x256 pixels, the desired output, you can read in the SVG to a Cairo context using rsvg and then scale it and write to a PNG.

main.py

import cairo
import rsvg

width = 256
height = 256

svg = rsvg.Handle('cool.svg')
unscaled_width = svg.props.width
unscaled_height = svg.props.height

svg_surface = cairo.SVGSurface(None, width, height)
svg_context = cairo.Context(svg_surface)
svg_context.save()
svg_context.scale(width/unscaled_width, height/unscaled_height)
svg.render_cairo(svg_context)
svg_context.restore()

svg_surface.write_to_png('cool.png')

RSVG C binding

From the Cario website with some minor modification. Also a good example of how to call a C-library from Python

from ctypes import CDLL, POINTER, Structure, byref, util
from ctypes import c_bool, c_byte, c_void_p, c_int, c_double, c_uint32, c_char_p


class _PycairoContext(Structure):
    _fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__),
                ("ctx", c_void_p),
                ("base", c_void_p)]


class _RsvgProps(Structure):
    _fields_ = [("width", c_int), ("height", c_int),
                ("em", c_double), ("ex", c_double)]


class _GError(Structure):
    _fields_ = [("domain", c_uint32), ("code", c_int), ("message", c_char_p)]


def _load_rsvg(rsvg_lib_path=None, gobject_lib_path=None):
    if rsvg_lib_path is None:
        rsvg_lib_path = util.find_library('rsvg-2')
    if gobject_lib_path is None:
        gobject_lib_path = util.find_library('gobject-2.0')
    l = CDLL(rsvg_lib_path)
    g = CDLL(gobject_lib_path)
    g.g_type_init()

    l.rsvg_handle_new_from_file.argtypes = [c_char_p, POINTER(POINTER(_GError))]
    l.rsvg_handle_new_from_file.restype = c_void_p
    l.rsvg_handle_render_cairo.argtypes = [c_void_p, c_void_p]
    l.rsvg_handle_render_cairo.restype = c_bool
    l.rsvg_handle_get_dimensions.argtypes = [c_void_p, POINTER(_RsvgProps)]

    return l


_librsvg = _load_rsvg()


class Handle(object):
    def __init__(self, path):
        lib = _librsvg
        err = POINTER(_GError)()
        self.handle = lib.rsvg_handle_new_from_file(path.encode(), byref(err))
        if self.handle is None:
            gerr = err.contents
            raise Exception(gerr.message)
        self.props = _RsvgProps()
        lib.rsvg_handle_get_dimensions(self.handle, byref(self.props))

    def get_dimension_data(self):
        svgDim = self.RsvgDimensionData()
        _librsvg.rsvg_handle_get_dimensions(self.handle, byref(svgDim))
        return (svgDim.width, svgDim.height)

    def render_cairo(self, ctx):
        """Returns True is drawing succeeded."""
        z = _PycairoContext.from_address(id(ctx))
        return _librsvg.rsvg_handle_render_cairo(self.handle, z.ctx)
Cameron Lowell Palmer
  • 20,467
  • 6
  • 114
  • 123
  • Thanks for this, it proved very useful in a project of mine. Although `Handle.get_dimension_data` didn't work for me. I had to replace it with a simple fetching of `self.props.width` and `self.props.height`. I first tried defining the `RsvgDimensionData` Structure as described on the cairo website, but without success. – JeanOlivier Oct 01 '18 at 19:26
  • I am trying to use this in a project of mine. How do I obtain the dll files required? – Andoo Jun 11 '19 at 13:33
1

Try this python script:

Don't forget to install cairosvg: pip3 install cairosvg

#!/usr/bin/env python3
import os
import cairosvg

for file in os.listdir('.'):
    if os.path.isfile(file) and file.endswith(".svg"):
        name = file.split('.svg')[0]
        cairosvg.svg2png(url=name+'.svg',write_to=name+'.png')

Melroy van den Berg
  • 2,368
  • 25
  • 27
1

Try using Gtk.Image and Gdk.Pixbuf

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')

from gi.repository import Gdk, Gtk
from PIL import Image

image = Gtk.Image()
image.set_from_file("path/to/image.svg")
pb = image.get_pixbuf()
pb.savev("path/to/convented/image.jpeg","jpeg",[],[])
im = Image.open("path/to/convented/image.jpeg")
pix = im.load()
print(pix[1,1])
sulincix
  • 11
  • 1
  • I am sure it would help the community if you explained to us why and how you code would solve the OP's problem – Simas Joneliunas Jan 12 '22 at 11:23
  • This works with SVG files that are not correctly rendered by Cairo, Inkspace but are correctly rendered by Gimp and Image Viewer. – FaST4 Feb 14 '22 at 20:09
0

Actually, I did not want to be dependent of anything else but Python (Cairo, Ink.., etc.) My requirements were to be as simple as possible, at most, a simple pip install "savior" would suffice, that's why any of those above didn't suit for me.

I came through this (going further than Stackoverflow on the research). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Looks good, so far. So I share it in case anyone in the same situation.

Ualter Jr.
  • 2,156
  • 1
  • 22
  • 26