Chuyển đổi SVG sang PNG bằng Python


99

Làm cách nào để chuyển đổi một svgsang png, trong Python? Tôi đang lưu trữ svgtrong một ví dụ của StringIO. Tôi có nên sử dụng thư viện pyCairo không? Làm cách nào để viết mã đó?


3
Có thể là bản sao của stackoverflow.com/questions/2932408/…
Optimal Cynic,

2
Chủ đề đó vẫn chưa giải quyết được vấn đề. Câu trả lời được chấp nhận đến từ người hỏi đang chia sẻ lần thử mã không thành công của mình. Câu trả lời khác gợi ý ImageMagick nhưng một người bình luận cho biết ImageMagick thực hiện "một công việc kinh khủng khi diễn giải SVG." Tôi không muốn những chiếc kẹp của mình trông thật kinh khủng nên tôi đang đặt lại câu hỏi.
ram1


Các ví dụ trong liên kết đó dành riêng cho Win32. Tôi đang chạy linux.
ram1

Hãy xem bài đăng trên blog này , có vẻ như nó có thể là thứ bạn cần.
giodamelio

Câu trả lời:


60

Câu trả lời là " pyrsvg " - một liên kết Python cho librsvg .

Có một gói Ubuntu python-rsvg cung cấp nó. Việc tìm kiếm tên của Google không tốt vì mã nguồn của nó dường như được chứa bên trong kho lưu trữ GIT của dự án Gnome "gnome-python-desktop".

Tôi đã tạo một "hello world" tối giản kết xuất SVG lên bề mặt cairo và ghi nó vào đĩa:

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

Cập nhật : như năm 2014 các gói cần thiết để phân phối Fedora Linux là: gnome-python2-rsvg. Danh sách đoạn mã trên vẫn hoạt động như hiện tại.


1
Tuyệt vời, hoạt động tốt. Nhưng có cách nào để tự cairoxác định CHIỀU CAO và CHIỀU RỘNG của bức tranh không? Tôi đã xem xét *.svgtệp, để trích xuất CHIỀU CAO và CHIỀU RỘNG từ đó, nhưng cả hai đều được đặt thành 100%. Tất nhiên, tôi có thể xem xét các thuộc tính của hình ảnh, nhưng vì đây chỉ là một bước trong xử lý hình ảnh nên đây không phải là điều tôi muốn.
quapka

1
Nếu "chiều rộng" và "chiều cao" của tệp của bạn được đặt thành 100%, thì Cairo hoặc rsvg không thể làm gì để đoán kích thước: các tệp SVG như vậy được để kích thước độc lập bởi phần mềm tạo (/ person). Mã HTML xung quanh để nhập tệp SVG sẽ cung cấp kích thước vật lý. Tuy nhiên, đối tượng "Handle" của rsvg có một .get_dimension_data()phương thức hoạt động với tệp ví dụ của tôi (SVG hoạt động tốt) - hãy thử.
jsbueno

1
kể từ năm 2014, đối với ubuntu, bạn có thể sử dụng: apt-get install python-rsvg
t1m0

1
Có lệnh nhanh nào để thêm nền trắng vào hình ảnh nếu nền hiện tại của nó trong suốt không?
fiatjaf

@jsbueno Tôi sử dụng Windows 8.1 và python 2.7.11 Làm cách nào để cài đặt cairo và rsvg và làm cho nó hoạt động. Tôi đã đấu tranh để làm cho việc này thành công. BTW +1 cho lời giải thích chi tiết của bạn.
Marlon Abeykoon

88

Đây là những gì tôi đã làm bằng cách sử dụng 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')

Và nó hoạt động như một sự quyến rũ!

Xem thêm: tài liệu cairosvg


5
Chào. Bạn có biết làm thế nào tôi có thể làm điều tương tự nhưng không ghi vào tệp không? Tôi cần đẩy nội dung png vào trình duyệt từ một máy chủ web để người dùng có thể tải xuống hình ảnh theo cách đó. Lưu tệp png không phải là một tùy chọn hợp lệ trong dự án của chúng tôi, đó là lý do tại sao tôi cần nó theo cách đó. Cảm ơn
estemendoza

4
Tôi đã tự mình làm việc này. Về cơ bản, nó phụ thuộc vào công cụ hoặc khuôn khổ nào bạn có trong tay khi xử lý các yêu cầu web của bạn, nhưng bất kể nó là gì, ý tưởng cơ bản là svg2pngđưa vào một streamđối tượng trong write_totham số và đây có thể là đối tượng HTTP Response của bạn (trong đó hầu hết các khuôn khổ là một đối tượng giống như tệp) hoặc một số luồng khác, mà sau đó bạn phân phát cho trình duyệt bằng cách sử dụng Content-Dispositiontiêu đề. xem ở đây: stackoverflow.com/questions/1012437/…
nemesisfixx

4
Đối với những người gặp sự cố với mã đó, như tôi đã từng: 1). bytestringchấp nhận byte, vì vậy hãy chuyển đổi chuỗi đầu tiên bằng bytestring=bytes(svg,'UTF-8') 2). chế độ tập tin nên nhị phân, vì vậyopen('output.png','wb')
Serj Zaharchenko

3
cairosvg chỉ hỗ trợ Python 3.4+. Họ đã giảm Python 2 hỗ trợ
Marlon Abeykoon

1
Không có một svg2pngcho tôi, tôi phải sử dụng cairosvg.surface.PNGSurface.convert(svg_str, write_to='output.png').
tobltobs

39

Cài đặt Inkscape và gọi nó dưới dạng dòng lệnh:

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

Bạn cũng có thể chụp khu vực hình chữ nhật cụ thể chỉ bằng cách sử dụng tham số -j, ví dụ: tọa độ "0: 125: 451: 217"

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

Nếu bạn chỉ muốn hiển thị một đối tượng trong tệp SVG, bạn có thể chỉ định tham số -ivới id đối tượng mà bạn đã thiết lập trong SVG. Nó ẩn mọi thứ khác.

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

2
+1 vì điều này cũng cực kỳ tiện dụng cho kịch bản shell. Xem inkscape.org/doc/inkscape-man.html để biết tài liệu đầy đủ về dòng lệnh của Inkscape.
Prime

Cảm ơn, đây là cách dễ nhất mà tôi tìm thấy để làm điều này. Trên Windows, để giúp bạn không phải nhập đường dẫn đầy đủ đến Inkscape mỗi lần, bạn có thể thêm nó vào Đường dẫn trong Biến môi trường .
Alex S,

3
Đây không phải là một câu trả lời. Đó là một cách giải quyết. OP đã yêu cầu một giải pháp Python.
lamino

31

Tôi đang sử dụng Wand-py (một triển khai của Wand wrapper xung quanh ImageMagick) để nhập một số SVG khá nâng cao và cho đến nay đã thấy kết quả tuyệt vời! Đây là tất cả mã mà nó cần:

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

Tôi mới phát hiện ra điều này hôm nay và cảm thấy nó đáng để chia sẻ cho bất kỳ ai khác có thể băn khoăn về câu trả lời này vì đã lâu rồi vì hầu hết các câu hỏi này đã được trả lời.

LƯU Ý: Về mặt kỹ thuật, trong quá trình thử nghiệm, tôi phát hiện ra rằng bạn thậm chí không thực sự phải chuyển tham số định dạng cho ImageMagick, vì vậy with wand.image.Image( blob=svg_file.read() ) as image:tất cả những gì thực sự cần thiết.

CHỈNH SỬA: Từ một chỉnh sửa đã cố gắng của qris, đây là một số mã hữu ích cho phép bạn sử dụng ImageMagick với SVG có nền trong suốt:

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 hoạt động tốt hơn nhiều so với Cairo cho các PNG của tôi.
qris

3
Tôi gặp lỗi image.read(blob=svg_file.read(), format="svg") NameError: name 'svg_file' is not defined
adrienlucca.wordpress.com

2
svg_fileđược giả định là một "tập tin" đối tượng trong ví dụ này, thiết lập svg_filesẽ giống như thế:svg_file = File.open(file_name, "r")
streetlogics

2
Cảm ơn, phương pháp cairorsvg'được chấp nhận' không hoạt động với tệp PDF của tôi. pip install wandvà đoạn mã của bạn đã làm các trick;)
nmz787

5
Nếu bạn có một svg strthì trước tiên bạn cần phải mã hóa thành nhị phân như thế này: svg_blob = svg_str.encode('utf-8'). Bây giờ bạn có thể sử dụng phương pháp trên bằng cách thay thế blob=svg_file.read()bằng blob=svg_blob.
asherbar

12

Hãy thử cái này: http://cairosvg.org/

Trang web cho biết:

CairoSVG được viết bằng python thuần túy và chỉ phụ thuộc vào Pycairo. Nó được biết là hoạt động trên Python 2.6 và 2.7.

Cập nhật ngày 25 tháng 11 năm 2016 :

2.0.0 là một phiên bản chính mới, bảng thay đổi của nó bao gồm:

  • Bỏ hỗ trợ Python 2

Có hai vấn đề với điều này, thật không may. Đầu tiên, nó không xử lý <clipPath><rect ... /></clipPath>. Thứ hai, nó không sử dụng tùy chọn -d (DPI).
Ray

2
@Ray, vui lòng gửi báo cáo lỗi / yêu cầu tính năng trên trình theo dõi CairoSVG !
Simon Sapin

@Simon, Bạn có thể làm được không? Tôi bận quá và 1-2 tháng tới tôi sẽ có mặt.
Ray

2
@Ray, thực ra tùy chọn -d / --dpi đã có từ lâu và tôi được thông báo rằng hỗ trợ cho <clippath> đã được thêm vào phiên bản git vài tuần trước.
Simon Sapin

@Simon, tốt đẹp! Cảm ơn bạn! :)
Ray

6

Một giải pháp khác mà tôi vừa tìm thấy ở đây Làm thế nào để hiển thị SVG được chia tỷ lệ thành 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 được cài đặt dễ dàng từ một gói nhị phân trong Windows (và tôi sử dụng nó cho những việc khác nên rất dễ dàng đối với tôi).

Tuy nhiên, tôi nhận thấy một số vấn đề khi chuyển đổi cờ quốc gia từ Wikimedia, vì vậy có lẽ không phải là trình phân tích / kết xuất svg mạnh mẽ nhất.


5

Một chút mở rộng về câu trả lời của 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)

Tôi đã sử dụng trích xuất chiều rộng và chiều cao svg. Tôi không chắc về tiêu chuẩn svg nhưng trong một số tệp svg của tôi, chiều rộng hoặc chiều cao được theo sau bởi một chuỗi không phải số như 'mm' hoặc 'px' (ví dụ: '250mm'). Int ('250mm') ném ra một ngoại lệ và tôi phải thực hiện một số tinh chỉnh bổ sung.
WigglyWorld

3

Tôi không tìm thấy bất kỳ câu trả lời nào thỏa đáng. Tất cả các thư viện được đề cập đều có một số vấn đề hoặc vấn đề khác như Cairo đã bỏ hỗ trợ cho python 3.6 (họ đã bỏ hỗ trợ Python 2 khoảng 3 năm trước!). Ngoài ra, việc cài đặt các thư viện được đề cập trên Mac cũng là một vấn đề khó khăn.

Cuối cùng, tôi tìm thấy giải pháp tốt nhất là svglib + reportlab . Cả hai đều được cài đặt mà không gặp khó khăn bằng cách sử dụng pip và lệnh gọi đầu tiên để chuyển đổi từ svg sang png đều hoạt động tốt! Rất hài lòng với giải pháp.

Chỉ cần 2 lệnh thực hiện thủ thuật:

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

Có bất kỳ hạn chế nào với những điều này tôi nên biết không?


Có, điểm đánh dấu không được hỗ trợ và sẽ không, github.com/deeplook/svglib/issues/177
Rainald62

2

Tỷ lệ SVG và kết xuất PNG

Sử dụng pycairolibrsvg tôi có thể đạt được tỷ lệ SVG và hiển thị thành một bitmap. Giả sử SVG của bạn không chính xác là 256x256 pixel, đầu ra mong muốn, bạn có thể đọc trong SVG sang ngữ cảnh Cairo bằng cách sử dụng rsvg, sau đó chia tỷ lệ và ghi thành 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')

Ràng buộc RSVG C

Từ trang web Cario với một số sửa đổi nhỏ. Cũng là một ví dụ điển hình về cách gọi thư viện C từ 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)

Cảm ơn vì điều này, nó tỏ ra rất hữu ích trong một dự án của tôi. Mặc dù Handle.get_dimension_datakhông làm việc cho tôi. Tôi đã phải thay thế nó bằng cách tìm nạp đơn giản self.props.widthself.props.height. Lần đầu tiên tôi đã thử xác định RsvgDimensionDataCấu trúc như được mô tả trên trang web cairo, nhưng không thành công.
JeanOlivier

Tôi đang cố gắng sử dụng điều này trong một dự án của tôi. Làm cách nào để lấy các tệp dll cần thiết?
Andoo

0

Đây là một cách tiếp cận mà Inkscape được gọi bằng Python.

Lưu ý rằng nó ngăn chặn một số đầu ra quan trọng nhất định mà Inkscape ghi vào bảng điều khiển (cụ thể là stderr và stdout) trong quá trình hoạt động bình thường không có lỗi. Đầu ra được ghi lại trong hai biến chuỗi, outerr.

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

Ví dụ: khi chạy một công việc cụ thể trên hệ thống Mac OS của tôi, outkết quả là:

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

(Tệp svg đầu vào có kích thước 339 x 339 pixel.)


Nó không phải là end-to-end Python nếu bạn dựa vào Inkscape.
Joel,

1
@Joel: Dòng đầu tiên đã được sửa đổi để vượt qua sự phản đối của bạn. Nhưng tất nhiên, ngay cả một giải pháp Python "thuần túy" dựa trên các yếu tố bên ngoài ngôn ngữ cốt lõi, và cuối cùng được chạy bằng ngôn ngữ máy, vì vậy có lẽ không có thứ gì gọi là end-to-end!
Gối sắt

0

Thực ra, tôi không muốn bị phụ thuộc vào bất cứ thứ gì khác ngoài Python (Cairo, Ink .., v.v.) Yêu cầu của tôi là càng đơn giản càng tốt, tối đa, đơn giản là pip install "savior"đủ, đó là lý do tại sao bất kỳ cái nào ở trên đều không ' t phù hợp với tôi.

Tôi đã vượt qua điều này (đi xa hơn Stackoverflow về nghiên cứu). https://www.tutorialexample.com/best-practice-to-python-convert-svg-to-png-with-svglib-python-tutorial/

Nhìn có vẻ ổn. Vì vậy, tôi chia sẻ nó để đề phòng những ai cùng cảnh ngộ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.