Преобразование SVG в PNG в Python

99

Как мне преобразовать svgв pngв Python? Я храню svgв экземпляре StringIO. Стоит ли использовать библиотеку pyCairo? Как мне написать этот код?

ram1
источник
3
Возможно, дубликат stackoverflow.com/questions/2932408/…
Optimal Cynic
2
Эта ветка оставила проблему нерешенной. Принятый ответ пришел от спрашивающего, который поделился своей неудачной попыткой кода. Другой ответ предлагал ImageMagick, но один из комментаторов сказал, что ImageMagick выполняет «ужасную работу по интерпретации SVG». Я не хочу, чтобы мои png выглядели ужасно, поэтому я снова задаю вопрос.
ram1
1
Попробуйте cairographics.org/cookbook/librsvgpython
Optimal Cynic
Примеры в этой ссылке относятся к Win32. Я использую Linux.
ram1
Взгляните на это сообщение в блоге, похоже, это может быть то, что вам нужно.
giodamelio 06

Ответы:

60

Ответ - pyrsvg - привязка Python к librsvg .

Его предоставляет пакет Ubuntu python-rsvg . Поиск в Google по его названию неэффективен, потому что его исходный код, кажется, содержится в репозитории GIT проекта Gnome "gnome-python-desktop".

Я сделал минималистичный "привет мир", который отображает SVG на поверхности cairo и записывает его на диск:

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

Обновление : в 2014 Нужные пакет для распространения Fedora Linux является: gnome-python2-rsvg. Приведенный выше список фрагментов по-прежнему работает как есть.

jsbueno
источник
1
Отлично, отлично работает. Но есть ли способ дать возможность самостоятельно cairoопределять ВЫСОТУ и ШИРИНУ изображения? Я просмотрел *.svgфайл, чтобы извлечь оттуда ВЫСОТУ и ШИРИНУ, но они оба установлены на 100%. Конечно, я могу изучить свойства изображения, но поскольку это всего лишь один шаг в обработке изображения, это не то, что мне нужно.
quapka 09
1
Если для "ширины" и "высоты" ваших файлов установлено значение 100%, Cairo или rsvg не могут сделать ничего волшебного, чтобы угадать размер: такие файлы SVG были оставлены независимо от размера программным обеспечением-создателем (/ person). Окружающий HTML-код для импорта файла SVG предоставит физический размер. Однако у объекта «Handle» в rsvg есть .get_dimension_data()метод, который работал с моим файлом примера (хорошо управляемый SVG) - попробуйте.
jsbueno
1
по состоянию на 2014 год для ubuntu вы можете использовать: apt-get install python-rsvg
t1m0
1
Есть ли быстрая команда для добавления белого фона к изображению, если его текущий фон прозрачный?
fiatjaf 07
@jsbueno Я использую Windows 8.1 и python 2.7.11 Как мне установить cairo и rsvg и заставить их работать. Я изо всех сил пытался заставить эту работу работать. BTW +1 для вашего подробного объяснения.
Марлон Абейкун
88

Вот что я сделал с помощью 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')

И это работает как шарм!

Подробнее: cairosvg document

Nemesisfixx
источник
5
Здравствуй. Вы знаете, как я могу сделать то же самое, но без записи в файл? Мне нужно отправить содержимое png в браузер с веб-сервера, чтобы пользователь мог загрузить изображение. Сохранение файла png не является допустимым вариантом в нашем проекте, поэтому он мне и нужен. Спасибо
estemendoza
4
Я сам этим занимался. Это в основном зависит от того, какие инструменты или фреймворки у вас есть под рукой при обработке ваших веб-запросов, но независимо от того, что это такое, основная идея заключается в том, что он svg2pngпринимает streamобъект в write_toпараметре, и это может быть ваш объект HTTP-ответа (который в большинство фреймворков представляет собой файловый объект) или какой-либо другой поток, который вы затем передаете браузеру с помощью Content-Dispositionзаголовка. см. здесь: stackoverflow.com/questions/1012437/…
nemesisfixx
4
Для тех, кто испытывает проблемы с этим кодом, как и я: 1). bytestringпринимает байты, поэтому сначала преобразуйте строку с помощью bytestring=bytes(svg,'UTF-8') 2). файловый режим должен быть двоичным, поэтомуopen('output.png','wb')
Серж Захарченко
3
cairosvg поддерживает только Python 3.4+. Они отказались от поддержки Python 2
Марлон Абейкун,
1
Для svg2pngменя не было, мне пришлось использовать cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs
39

Установите Inkscape и вызовите его как командную строку:

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

Вы также можете привязать конкретную прямоугольную область только с помощью параметра -j, например, координаты "0: 125: 451: 217"

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

Если вы хотите показать только один объект в файле SVG, вы можете указать параметр -iс идентификатором объекта, который вы установили в SVG. Он скрывает все остальное.

${INKSCAPE_PATH} -z -f ${source_svg} -w ${width} -i ${object} -j -a ${coordinates} -e ${dest_png}
Blj
источник
2
+1, потому что это также очень удобно для написания сценариев оболочки. См inkscape.org/doc/inkscape-man.html полных документаций по командной строке в Inkscape.
Prime
Спасибо, это был самый простой способ сделать это. В Windows, чтобы вам не приходилось каждый раз вводить полный путь к Inkscape, вы можете добавить его в свой путь в переменных среды .
Alex S
3
Это не ответ. Это обходной путь. OP запросил решение Python.
lamino
31

Я использую Wand-py (реализацию оболочки Wand вокруг ImageMagick) для импорта некоторых довольно продвинутых SVG-файлов и пока что добился отличных результатов! Это весь код, который требуется:

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

Я только что обнаружил это сегодня и почувствовал, что стоит поделиться этим для всех, кто может запутаться в этом ответе, поскольку прошло некоторое время с тех пор, как на большинство этих вопросов были даны ответы.

ПРИМЕЧАНИЕ. Технически при тестировании я обнаружил, что вам даже не нужно передавать параметр формата для ImageMagick, так что with wand.image.Image( blob=svg_file.read() ) as image:это все, что действительно нужно.

РЕДАКТИРОВАТЬ: из попытки редактирования qris, вот полезный код, который позволяет вам использовать ImageMagick с SVG с прозрачным фоном:

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)
стритлогика
источник
2
Wand работал с моими PNG намного лучше, чем Cairo.
qris
3
Я получаю сообщение об ошибке image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com
2
svg_fileв этом примере предполагается, что это объект "файл", настройка svg_fileбудет выглядеть примерно так:svg_file = File.open(file_name, "r")
streetlogics
2
Спасибо, метод cairoи rsvg"принято" не сработали для моего PDF-файла. pip install wandи ваш сниппет сделал свое дело;)
nmz787
5
Если у вас есть SVG , strто вам сначала нужно закодировать в двоичном , как это: svg_blob = svg_str.encode('utf-8'). Теперь вы можете использовать описанный выше метод, заменив его blob=svg_file.read()на blob=svg_blob.
asherbar
12

Попробуйте это: http://cairosvg.org/

На сайте написано:

CairoSVG написан на чистом питоне и зависит только от Pycairo. Известно, что он работает на Python 2.6 и 2.7.

Обновление 25 ноября 2016 г . :

2.0.0 - это новая мажорная версия, ее список изменений включает:

  • Отказаться от поддержки Python 2
user732592
источник
К сожалению, здесь есть две проблемы. Во-первых, он не обрабатывает файлы <clipPath><rect ... /></clipPath>. Во-вторых, он не принимает параметр -d (DPI).
Ray
2
@Ray, пожалуйста, отправляйте отчеты об ошибках / запросы функций на трекере CairoSVG !
Саймон Сапин
@Simon, сделай, пожалуйста? Я слишком занят и буду в ближайшие 1-2 месяца.
Ray
2
@Ray, на самом деле параметр -d / --dpi присутствует уже некоторое время, и мне сказали, что поддержка <clippath> была добавлена ​​несколько недель назад в версии git.
Саймон Сапин
@Simon, здорово! Спасибо! :)
Ray
6

Другое решение, которое я только что нашел здесь. Как визуализировать масштабированный SVG в 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 легко устанавливается из бинарного пакета в Windows (и я использую его для других целей, поэтому мне это легко).

Однако я заметил несколько проблем при преобразовании флагов стран из Викимедиа, так что, возможно, это не самый надежный парсер / рендерер svg.

Адам Керц
источник
5

Небольшое расширение ответа 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)
Мартин Тома
источник
Я использовал извлечение ширины и высоты svg. Я не уверен в стандарте svg, но в некоторых моих файлах svg за шириной или высотой следовала нечисловая строка, такая как «мм» или «px» (например, «250 мм»). Int ('250mm') выдает исключение, и мне пришлось внести некоторые дополнительные настройки.
WigglyWorld
5

Я не нашел удовлетворительного ответа. Все упомянутые библиотеки имеют ту или иную проблему, например, отказ Каира от поддержки python 3.6 (они отказались от поддержки Python 2 около 3 лет назад!). Кроме того, установка упомянутых библиотек на Mac была проблемой.

Наконец, я нашел лучшее решение - svglib + reportlab . Оба были установлены без проблем с использованием pip, и первый вызов для преобразования из svg в png работал прекрасно! Очень доволен решением.

Всего две команды делают свое дело:

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

Есть ли какие-то ограничения, о которых я должен знать?

Саранг
источник
Да, маркер-конец не поддерживается и не будет, github.com/deeplook/svglib/issues/177
Rainald62
2

Масштабирование SVG и рендеринг PNG

Используя pycairo и librsvg, я смог добиться масштабирования SVG и рендеринга в растровое изображение. Предполагая, что ваш SVG не совсем 256x256 пикселей, желаемый результат вы можете прочитать в SVG в контексте Cairo с помощью rsvg, а затем масштабировать его и записать в 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

С веб-сайта Cario с небольшими изменениями. Также хороший пример того, как вызвать C-библиотеку из 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)
Кэмерон Лоуэлл Палмер
источник
Спасибо за это, он оказался очень полезным в моем проекте. Хотя Handle.get_dimension_dataу меня не получилось. Мне пришлось заменить его простой загрузкой self.props.widthи self.props.height. Сначала я попытался определить RsvgDimensionDataструктуру, как описано на веб-сайте cairo, но безуспешно.
JeanOlivier 01
Я пытаюсь использовать это в своем проекте. Как мне получить необходимые файлы DLL?
Andoo
0

Вот подход, при котором Inkscape вызывается Python.

Обратите внимание, что он подавляет некоторые неаккуратные выходные данные, которые Inkscape записывает в консоль (в частности, stderr и stdout) во время нормальной безошибочной работы. Вывод фиксируется в двух строковых переменных outи 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 '?')  )

Например, при выполнении определенного задания в моей системе Mac OS outполучилось:

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

(Размер входного файла SVG составлял 339 на 339 пикселей.)

Железная подушка
источник
Это не сквозной Python, если вы полагаетесь на Inkscape.
Joel
1
@Joel: Первая строка была изменена, чтобы преодолеть ваше возражение. Но, конечно, даже «чистое» решение Python основано на элементах за пределами основного языка и, в конечном итоге, работает на машинном языке, так что, возможно, нет такой вещи, как сквозной процесс!
Iron Pillow
0

На самом деле, я не хотел зависеть ни от чего другого, кроме Python (Cairo, Ink ... и т. Д.). Мои требования должны были быть как можно более простыми pip install "savior", в лучшем случае достаточно простого , поэтому ни одно из вышеперечисленных не выполнялось. t подходит для меня.

Я прошел через это (идя дальше, чем Stackoverflow в исследовании). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Пока все выглядит хорошо. Так что я разделяю это на случай, если кто-то окажется в такой же ситуации.

Уалтер-младший
источник