Convert SVG to PNG in Python

Question:

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?

Asked By: ram1

||

Answers:

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.

Answered By: jsbueno

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
Answered By: user732592

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}
Answered By: blj

Here is what I did using cairosvg:

from cairosvg import svg2png

svg_code = """
    <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

Answered By: JWL

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)
Answered By: streetlogics

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.

Answered By: Adam Kerz

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)
Answered By: Martin Thoma

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.)

Answered By: Iron Pillow

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 256×256 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)

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?

Answered By: Sarang

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.

Answered By: Ualter Jr.

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')
Answered By: Moniruzzaman Akash

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')

Answered By: Melroy van den Berg

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])
Answered By: sulincix

All the answer’s here are great, but I figure I’ll mention that I have made a simple library that loads SVG’s files as pillow Image instances which can then be exported. It uses inkscape like in blj’s answer, but renders to stdout so that no temporary files are made. There’s some basic usage stuff in the README.

https://github.com/jlwoolf/pillow-svg

EDIT:
As suggested, here’s a brief explanation, since the link could become invalid:

The library uses inkscape’s command line interface to convert the image to a png of a specific size or dpi using the python subprocess library. By setting --export-filename to -, inkscape redirects the output to the stdout. The first two lines are discarded, and the remaining output is passed to PIL.Image.open, converting it to pillow image instance.

import subprocess
from PIL import Image

options = ["inkscape", "--export-filename=-", "--export-type=png", "file.svg"]

pipe = subprocess.Popen(options, stdout=subprocess.PIPE)

pipe.stdout.readline()
pipe.stdout.readline()

img = Image.open(pipe.stdout)

From there you can do whatever pillow image operations you need (like export as a jpg, resize, crop, etc).

EDIT 2:
Just added support for skia-python (haven’t fully tested it, but seems to work so far). This way you can convert an svg to png with only a single pip install (no need to use inkscape).

Here is an explanation of how the library uses skia-python:

First, the svg file is loaded into a skia.SVGDOM. From there you can grab the SVGDOM’s dimensions, using containerSize. Then a skia.Surface of the desired image output size is made. The canvas is scaled to fit the svg to the surface, and then the svg is rendered. From there, an image snapshot can be made, which can then be fed to PIL.Image.open.

import skia
from PIL import Image

skia_stream = skia.Stream.MakeFromFile("file.svg")
skia_svg = skia.SVGDOM.MakeFromStream(skia_stream)

svg_width, svg_height = skia_svg.containerSize()
surface_width, surface_height = 512, 512

surface = skia.Surface(surface_width, surface_height)
with surface as canvas:
    canvas.scale(surface_width / svg_width, surface_height / svg_height)
    skia_svg.render(canvas)

with io.BytesIO(surface.makeImageSnapshot().encodeToData()) as f:
            img = Image.open(f)
            img.load()

Edit 3:
I have fleshed out the library much much more. There is a command line utility now for easy svg conversion, along with more documentation explaining usage. Hope it helps!

Answered By: Jonathan Woolf

Posting my code from this StackOverflow answer. It’s a workaround to svglib+reportlib not supporting a transparent background and no scaling (see @sarang’s answer and @ualter-jr’s answer as well as these Github issues on scaling not working and this one on transparency)

This uses pyMuPDF to render an intermediate pdf from reportlab to PNG.

The big advantage is that it doesn’t need any external libraries as pymupdf comes with precompiled wheels for Windows, Linux and MacOS.

The whole thing is as easy as

pip install pymupdf svglib

and then executing the following lines

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")
Answered By: M4a1x
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.